merge k9mail back into master

This commit is contained in:
Dominik Schürmann 2013-09-06 13:52:57 +02:00
commit de8e1a39d5
65 changed files with 2857 additions and 871 deletions

34
API.md Normal file
View File

@ -0,0 +1,34 @@
# Security Model
## Basic goals
* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog)
Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL
## Without Permissions
### Intents
All Intents start with ``org.sufficientlysecure.keychain.action.``
* ``android.intent.action.VIEW`` connected to .gpg and .asc files: Import Key and Decrypt
* ``android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt
* ``IMPORT``
* ``IMPORT_FROM_FILE``
* ``IMPORT_FROM_QR_CODE``
* ``IMPORT_FROM_NFC``
* ``SHARE_KEYRING``
* ``SHARE_KEYRING_WITH_QR_CODE``
* ``SHARE_KEYRING_WITH_NFC``
* ``EDIT_KEYRING``
* ``SELECT_PUBLIC_KEYRINGS``
* ``SELECT_SECRET_KEYRING``
* ``ENCRYPT``
* ``ENCRYPT_FILE``
* ``DECRYPT``
* ``DECRYPT_FILE``
TODO:
- remove IMPORT, SHARE intents, simplify ENCRYPT and DECRYPT intents (include _FILE derivates like done in SEND based on file type)
- EDIT_KEYRING and CREATE_KEYRING, should be available via for registered apps
- new intent REGISTER_APP?

68
OLD_API.md Normal file
View File

@ -0,0 +1,68 @@
This is the old API. Currently disabled!
# Security Model
## Basic goals
* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog)
Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL
## Possible Permissions
* ACCESS_API: Encrypt/Sign/Decrypt/Create keys without user interaction (intents, remote service), Read key information (not the actual keys)(content provider)
* ACCESS_KEYS: get and import actual public and secret keys (remote service)
## Without Permissions
### Intents
All Intents start with org.sufficientlysecure.keychain.action.
* android.intent.action.VIEW connected to .gpg and .asc files: Import Key and Decrypt
* android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt
* IMPORT
* IMPORT_FROM_FILE
* IMPORT_FROM_QR_CODE
* IMPORT_FROM_NFC
* SHARE_KEYRING
* SHARE_KEYRING_WITH_QR_CODE
* SHARE_KEYRING_WITH_NFC
* EDIT_KEYRING
* SELECT_PUBLIC_KEYRINGS
* SELECT_SECRET_KEYRING
* ENCRYPT
* ENCRYPT_FILE
* DECRYPT
* DECRYPT_FILE
## With permission ACCESS_API
### Intents
* CREATE_KEYRING
* ENCRYPT_AND_RETURN
* ENCRYPT_STREAM_AND_RETURN
* GENERATE_SIGNATURE_AND_RETURN
* DECRYPT_AND_RETURN
* DECRYPT_STREAM_AND_RETURN
### Broadcast Receiver
On change of database the following broadcast is send.
* DATABASE_CHANGE
### Content Provider
* The whole content provider requires a permission (only read)
* Don't give out blobs (keys can be accessed by ACCESS_KEYS via remote service)
* Make an internal and external content provider (or pathes with <path-permission>)
* Look at android:grantUriPermissions especially for ApgServiceBlobProvider
* Only give out android:readPermission
### ApgApiService (Remote Service)
AIDL service
## With permission ACCESS_KEYS
### ApgKeyService (Remote Service)
AIDL service to access actual private keyring objects

View File

@ -8,65 +8,72 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" > android:orientation="vertical" >
<Button <TextView
android:id="@+id/crypto_provider_demo_register" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="registerCryptoProvider" android:text="Encrypt User Id"
android:text="Register crypto provider" /> android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/aidl_demo_select_secret_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="selectSecretKeyOnClick"
android:text="Select secret key" />
<Button
android:id="@+id/aidl_demo_select_encryption_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="selectEncryptionKeysOnClick"
android:text="Select encryption key(s)" />
<EditText <EditText
android:id="@+id/aidl_demo_message" android:id="@+id/crypto_provider_demo_encrypt_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="dominik@dominikschuermann.de"
android:textAppearance="@android:style/TextAppearance.Small" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Message"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/crypto_provider_demo_message"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dip" android:layout_height="150dip"
android:text="message" android:text="message"
android:textAppearance="@android:style/TextAppearance.Small" /> android:textAppearance="@android:style/TextAppearance.Small" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ciphertext"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText <EditText
android:id="@+id/aidl_demo_ciphertext" android:id="@+id/crypto_provider_demo_ciphertext"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dip" android:layout_height="150dip"
android:text="ciphertext" android:text="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" /> android:textAppearance="@android:style/TextAppearance.Small" />
<Button <Button
android:id="@+id/aidl_demo_encrypt" android:id="@+id/crypto_provider_demo_encrypt"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="encryptOnClick" android:onClick="encryptOnClick"
android:text="Encrypt" /> android:text="Encrypt" />
<Button <Button
android:id="@+id/aidl_demo_decrypt" android:id="@+id/crypto_provider_demo_sign"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="signOnClick"
android:text="Sign" />
<Button
android:id="@+id/crypto_provider_demo_encrypt_and_sign"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="encryptAndSignOnClick"
android:text="Encrypt and Sign" />
<Button
android:id="@+id/crypto_provider_demo_decrypt"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:onClick="decryptOnClick" android:onClick="decryptOnClick"
android:text="Decrypt" /> android:text="Decrypt" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="APG Data:" />
<TextView
android:id="@+id/aidl_demo_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minLines="10" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol. // Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoError; parcelable CryptoError;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;

View File

@ -1,4 +1,22 @@
package com.android.crypto; /*
* 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.openintents.crypto;
import org.openintents.crypto.ICryptoService;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -50,8 +68,8 @@ public class CryptoServiceConnection {
Log.d(TAG, "not bound yet"); Log.d(TAG, "not bound yet");
Intent serviceIntent = new Intent(); Intent serviceIntent = new Intent();
serviceIntent.setAction("com.android.crypto.ICryptoService"); serviceIntent.setAction("org.openintents.crypto.ICryptoService");
serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test serviceIntent.setPackage(cryptoProviderPackageName);
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol. // Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoSignatureResult; parcelable CryptoSignatureResult;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
import com.android.crypto.CryptoSignatureResult; import org.openintents.crypto.CryptoSignatureResult;
import com.android.crypto.CryptoError; import org.openintents.crypto.CryptoError;
interface ICryptoCallback { interface ICryptoCallback {
oneway void onEncryptSignSuccess(in byte[] outputBytes);
oneway void onDecryptVerifySuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult); /**
* CryptoSignatureResult is only returned if the Callback was used from decryptAndVerify
*
*/
oneway void onSuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
oneway void onError(in CryptoError error); oneway void onError(in CryptoError error);
oneway void onActivityRequired(in Intent intent);
} }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.crypto; package org.openintents.crypto;
import com.android.crypto.ICryptoCallback; import org.openintents.crypto.ICryptoCallback;
/** /**
* All methods are oneway, which means they are asynchronous and non-blocking. * All methods are oneway, which means they are asynchronous and non-blocking.
@ -29,50 +29,54 @@ interface ICryptoService {
* *
* @param inputBytes * @param inputBytes
* Byte array you want to encrypt * Byte array you want to encrypt
* @param encryptionKeyIds * @param encryptionUserIds
* Ids of public keys used for encryption * User Ids (emails) of recipients
* @param handler * @param callback
* Results are returned to this Handler after successful encryption * Callback where to return results
*/ */
oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback); 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 * Sign
* *
*
*
* @param inputBytes * @param inputBytes
* Byte array you want to encrypt * Byte array you want to encrypt
* @param signatureId * @param signatureUserId
* * User Ids (email) of sender
* @param handler * @param callback
* Results are returned to this Handler after successful encryption and signing * Callback where to return results
*/ */
oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback); oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback);
/**
* Encrypt and sign
*
* @param inputBytes
* Byte array you want to encrypt
* @param encryptionUserIds
* User Ids (emails) of recipients
* @param signatureUserId
* User Ids (email) of sender
* @param callback
* Callback where to return results
*/
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
/** /**
* Decrypts and verifies given input bytes. If no signature is present this method * Decrypts and verifies given input bytes. If no signature is present this method
* will only decrypt. * will only decrypt.
* *
* @param inputBytes * @param inputBytes
* Byte array you want to decrypt and verify * Byte array you want to decrypt and verify
* @param handler * @param callback
* Handler where to return results to after successful encryption * Callback where to return results
*/ */
oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback); oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
/**
* Opens setup using default parameters
*
*/
oneway void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,51 +16,42 @@
package org.sufficientlysecure.keychain.demo; package org.sufficientlysecure.keychain.demo;
import java.util.ArrayList;
import java.util.List;
import org.openintents.crypto.CryptoError;
import org.openintents.crypto.CryptoServiceConnection;
import org.openintents.crypto.CryptoSignatureResult;
import org.openintents.crypto.ICryptoCallback;
import org.openintents.crypto.ICryptoService;
import org.sufficientlysecure.keychain.demo.R; import org.sufficientlysecure.keychain.demo.R;
import org.sufficientlysecure.keychain.integration.Constants; import org.sufficientlysecure.keychain.integration.Constants;
import org.sufficientlysecure.keychain.integration.KeychainData;
import org.sufficientlysecure.keychain.integration.KeychainIntentHelper;
import org.sufficientlysecure.keychain.service.IKeychainApiService;
import org.sufficientlysecure.keychain.service.IKeychainKeyService;
import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ActivityNotFoundException; import android.content.DialogInterface;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
public class CryptoProviderDemoActivity extends Activity { public class CryptoProviderDemoActivity extends Activity {
Activity mActivity; Activity mActivity;
TextView mMessageTextView; EditText mMessage;
TextView mCiphertextTextView; EditText mCiphertext;
TextView mDataTextView; EditText mEncryptUserId;
EditText mSignUserId;
KeychainIntentHelper mKeychainIntentHelper; private CryptoServiceConnection mCryptoServiceConnection;
KeychainData mKeychainData;
private IKeychainApiService service = null;
private ServiceConnection svcConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = IKeychainApiService.Stub.asInterface(binder);
}
public void onServiceDisconnected(ComponentName className) {
service = null;
}
};
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@ -69,170 +60,208 @@ public class CryptoProviderDemoActivity extends Activity {
mActivity = this; mActivity = this;
mMessageTextView = (TextView) findViewById(R.id.aidl_demo_message); mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
mCiphertextTextView = (TextView) findViewById(R.id.aidl_demo_ciphertext); mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext);
mDataTextView = (TextView) findViewById(R.id.aidl_demo_data); mEncryptUserId = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id);
mKeychainIntentHelper = new KeychainIntentHelper(mActivity); selectCryptoProvider();
mKeychainData = new KeychainData();
bindService(new Intent(IKeychainApiService.class.getName()), svcConn,
Context.BIND_AUTO_CREATE);
} }
public void registerCryptoProvider(View view) { /**
try { * Callback from remote crypto service
startActivityForResult(Intent.createChooser(new Intent("com.android.crypto.REGISTER"), */
"select crypto provider"), 123); final ICryptoCallback.Stub encryptCallback = new ICryptoCallback.Stub() {
} catch (ActivityNotFoundException e) {
Toast.makeText(mActivity, "No app that handles com.android.crypto.REGISTER!",
Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "No app that handles com.android.crypto.REGISTER!");
}
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { public void onSuccess(final byte[] outputBytes, CryptoSignatureResult signatureResult)
if (requestCode == 123) { throws RemoteException {
if (resultCode == RESULT_OK) { Log.d(Constants.TAG, "onEncryptSignSuccess");
String packageName = data.getStringExtra("packageName");
Log.d(Constants.TAG, "packageName: " + packageName); runOnUiThread(new Runnable() {
}
@Override
public void run() {
mCiphertext.setText(new String(outputBytes));
}
});
} }
// boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data, @Override
// mKeychainData); public void onError(CryptoError error) throws RemoteException {
// if (result) { Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
// updateView(); Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
// } }
// continue with other activity results };
super.onActivityResult(requestCode, resultCode, data);
} final ICryptoCallback.Stub decryptCallback = new ICryptoCallback.Stub() {
@Override
public void onSuccess(final byte[] outputBytes, final CryptoSignatureResult signatureResult)
throws RemoteException {
Log.d(Constants.TAG, "onDecryptVerifySuccess");
runOnUiThread(new Runnable() {
@Override
public void run() {
mMessage.setText(new String(outputBytes) + "\n\n" + signatureResult.toString());
}
});
}
@Override
public void onError(CryptoError error) throws RemoteException {
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
}
};
public void encryptOnClick(View view) { public void encryptOnClick(View view) {
byte[] inputBytes = mMessageTextView.getText().toString().getBytes(); byte[] inputBytes = mMessage.getText().toString().getBytes();
try { try {
service.encryptAsymmetric(inputBytes, null, true, 0, mKeychainData.getPublicKeys(), 7, mCryptoServiceConnection.getService().encrypt(inputBytes,
encryptHandler); new String[] { mEncryptUserId.getText().toString() }, encryptCallback);
} catch (RemoteException e) { } catch (RemoteException e) {
exceptionImplementation(-1, e.toString()); Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
public void signOnClick(View view) {
byte[] inputBytes = mMessage.getText().toString().getBytes();
try {
mCryptoServiceConnection.getService().sign(inputBytes,
mSignUserId.getText().toString(), encryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
public void encryptAndSignOnClick(View view) {
byte[] inputBytes = mMessage.getText().toString().getBytes();
try {
mCryptoServiceConnection.getService().encryptAndSign(inputBytes,
new String[] { mEncryptUserId.getText().toString() },
mSignUserId.getText().toString(), encryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
} }
} }
public void decryptOnClick(View view) { public void decryptOnClick(View view) {
byte[] inputBytes = mCiphertextTextView.getText().toString().getBytes(); byte[] inputBytes = mCiphertext.getText().toString().getBytes();
try { try {
service.decryptAndVerifyAsymmetric(inputBytes, null, null, decryptHandler); mCryptoServiceConnection.getService().decryptAndVerify(inputBytes, decryptCallback);
} catch (RemoteException e) { } catch (RemoteException e) {
exceptionImplementation(-1, e.toString()); Log.e(Constants.TAG, "CryptoProviderDemo", e);
} }
} }
private void updateView() {
if (mKeychainData.getDecryptedData() != null) {
mMessageTextView.setText(mKeychainData.getDecryptedData());
}
if (mKeychainData.getEncryptedData() != null) {
mCiphertextTextView.setText(mKeychainData.getEncryptedData());
}
mDataTextView.setText(mKeychainData.toString());
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
unbindService(svcConn); if (mCryptoServiceConnection != null) {
mCryptoServiceConnection.unbindFromService();
}
} }
private void exceptionImplementation(int exceptionId, String error) { private static class CryptoProviderElement {
AlertDialog.Builder builder = new AlertDialog.Builder(this); private String packageName;
builder.setTitle("Exception!").setMessage(error).setPositiveButton("OK", null).show(); private String simpleName;
private Drawable icon;
public CryptoProviderElement(String packageName, String simpleName, Drawable icon) {
this.packageName = packageName;
this.simpleName = simpleName;
this.icon = icon;
}
@Override
public String toString() {
return simpleName;
}
} }
private final IKeychainEncryptHandler.Stub encryptHandler = new IKeychainEncryptHandler.Stub() { private void selectCryptoProvider() {
Intent intent = new Intent(ICryptoService.class.getName());
@Override final ArrayList<CryptoProviderElement> providerList = new ArrayList<CryptoProviderElement>();
public void onException(final int exceptionId, final String message) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
exceptionImplementation(exceptionId, message);
}
});
}
@Override List<ResolveInfo> resInfo = getPackageManager().queryIntentServices(intent, 0);
public void onSuccess(final byte[] outputBytes, String outputUri) throws RemoteException { if (!resInfo.isEmpty()) {
runOnUiThread(new Runnable() { for (ResolveInfo resolveInfo : resInfo) {
public void run() { if (resolveInfo.serviceInfo == null)
mKeychainData.setEncryptedData(new String(outputBytes)); continue;
updateView();
}
});
}
}; String packageName = resolveInfo.serviceInfo.packageName;
String simpleName = String.valueOf(resolveInfo.serviceInfo
.loadLabel(getPackageManager()));
Drawable icon = resolveInfo.serviceInfo.loadIcon(getPackageManager());
providerList.add(new CryptoProviderElement(packageName, simpleName, icon));
}
private final IKeychainDecryptHandler.Stub decryptHandler = new IKeychainDecryptHandler.Stub() { AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Select Crypto Provider!");
alert.setCancelable(false);
@Override if (!providerList.isEmpty()) {
public void onException(final int exceptionId, final String message) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
exceptionImplementation(exceptionId, message);
}
});
}
@Override // Init ArrayAdapter with Crypto Providers
public void onSuccess(final byte[] outputBytes, String outputUri, boolean signature, ListAdapter adapter = new ArrayAdapter<CryptoProviderElement>(this,
long signatureKeyId, String signatureUserId, boolean signatureSuccess, android.R.layout.select_dialog_item, android.R.id.text1, providerList) {
boolean signatureUnknown) throws RemoteException { public View getView(int position, View convertView, ViewGroup parent) {
runOnUiThread(new Runnable() { // User super class to create the View
public void run() { View v = super.getView(position, convertView, parent);
mKeychainData.setDecryptedData(new String(outputBytes)); TextView tv = (TextView) v.findViewById(android.R.id.text1);
updateView();
// Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(providerList.get(position).icon,
null, null, null);
// Add margin between image and text (support various screen densities)
int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f);
tv.setCompoundDrawablePadding(dp5);
return v;
}
};
alert.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int position) {
String packageName = providerList.get(position).packageName;
// bind to service
mCryptoServiceConnection = new CryptoServiceConnection(
CryptoProviderDemoActivity.this, packageName);
mCryptoServiceConnection.bindToService();
dialog.dismiss();
}
});
} else {
alert.setMessage("No Crypto Provider installed!");
}
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
finish();
} }
}); });
AlertDialog ad = alert.create();
ad.show();
} }
};
private final IKeychainGetDecryptionKeyIdHandler.Stub helperHandler = new IKeychainGetDecryptionKeyIdHandler.Stub() {
@Override
public void onException(final int exceptionId, final String message) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
exceptionImplementation(exceptionId, message);
}
});
}
@Override
public void onSuccess(long arg0, boolean arg1) throws RemoteException {
// TODO Auto-generated method stub
}
};
/**
* Selection is done with Intents, not AIDL!
*
* @param view
*/
public void selectSecretKeyOnClick(View view) {
mKeychainIntentHelper.selectSecretKey();
} }
public void selectEncryptionKeysOnClick(View view) {
mKeychainIntentHelper.selectPublicKeys("user@example.com");
}
} }

View File

@ -67,24 +67,27 @@
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" /> <uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
<permission-group <!-- TODO: disabled, old API -->
android:name="org.sufficientlysecure.keychain.permission-group.keychain" <!-- <permission-group -->
android:description="@string/permission_group_description" <!-- android:name="org.sufficientlysecure.keychain.permission-group.keychain" -->
android:icon="@drawable/icon" <!-- android:description="@string/permission_group_description" -->
android:label="@string/permission_group_label" /> <!-- android:icon="@drawable/icon" -->
<!-- android:label="@string/permission_group_label" /> -->
<!-- <permission -->
<!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" -->
<!-- android:description="@string/permission_access_keys_description" -->
<!-- android:label="@string/permission_access_keys_label" -->
<!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" -->
<!-- android:protectionLevel="dangerous" /> -->
<!-- <permission -->
<!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_API" -->
<!-- android:description="@string/permission_access_api_description" -->
<!-- android:label="@string/permission_access_api_label" -->
<!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" -->
<!-- android:protectionLevel="dangerous" /> -->
<permission
android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS"
android:description="@string/permission_access_keys_description"
android:label="@string/permission_access_keys_label"
android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain"
android:protectionLevel="dangerous" />
<permission
android:name="org.sufficientlysecure.keychain.permission.ACCESS_API"
android:description="@string/permission_access_api_description"
android:label="@string/permission_access_api_label"
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 <application
@ -253,6 +256,7 @@
<data android:scheme="file" /> <data android:scheme="file" />
<data android:scheme="content" /> <data android:scheme="content" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
<!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg -->
<data android:pathPattern=".*\\.gpg" /> <data android:pathPattern=".*\\.gpg" />
<data android:pathPattern=".*\\..*\\.gpg" /> <data android:pathPattern=".*\\..*\\.gpg" />
<data android:pathPattern=".*\\..*\\..*\\.gpg" /> <data android:pathPattern=".*\\..*\\..*\\.gpg" />
@ -411,82 +415,92 @@
android:exported="false" android:exported="false"
android:process=":passphrase_cache" /> android:process=":passphrase_cache" />
<service android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" /> <service android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" />
<service
android:name="org.sufficientlysecure.keychain.service.KeychainApiService"
android:enabled="true"
android:exported="true"
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API"
android:process=":remoteapi" >
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" />
</intent-filter>
<meta-data <!-- TODO: disabled, old API! -->
android:name="api_version" <!-- <service -->
android:value="3" /> <!-- android:name="org.sufficientlysecure.keychain.service.KeychainApiService" -->
</service> <!-- android:enabled="true" -->
<service <!-- android:exported="true" -->
android:name="org.sufficientlysecure.keychain.service.KeychainKeyService" <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" -->
android:enabled="true" <!-- android:process=":remoteapi" > -->
android:exported="true" <!-- <intent-filter> -->
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" <!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" /> -->
android:process=":remotekeys" > <!-- </intent-filter> -->
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" />
</intent-filter>
<meta-data
android:name="api_version" <!-- <meta-data -->
android:value="3" /> <!-- android:name="api_version" -->
</service> <!-- android:value="3" /> -->
<!-- </service> -->
<!-- <service -->
<!-- android:name="org.sufficientlysecure.keychain.service.KeychainKeyService" -->
<!-- android:enabled="true" -->
<!-- android:exported="true" -->
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" -->
<!-- android:process=":remotekeys" > -->
<!-- <intent-filter> -->
<!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" /> -->
<!-- </intent-filter> -->
<!-- <meta-data -->
<!-- android:name="api_version" -->
<!-- android:value="3" /> -->
<!-- </service> -->
<provider <provider
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal" android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal"
android:authorities="org.sufficientlysecure.keychain.internal" android:authorities="org.sufficientlysecure.keychain.internal"
android:exported="false" /> android:exported="false" />
<provider <!-- TODO: disabled, old API -->
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" <!-- <provider -->
android:authorities="org.sufficientlysecure.keychain" <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" -->
android:exported="true" <!-- android:authorities="org.sufficientlysecure.keychain" -->
android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> <!-- android:exported="true" -->
<!-- android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
<!-- TODO: authority! --> <!-- TODO: authority! -->
<provider <!-- <provider -->
android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" <!-- android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" -->
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
<!-- Remote API internal intents -->
<!-- Crypto Provider other intents -->
<activity <activity
android:name=".crypto_provider.CryptoActivity" android:name="org.sufficientlysecure.keychain.remote_api.CryptoServiceActivity"
android:label="TODO crypto activity" android:exported="false"
android:label="@string/app_name"
android:process=":crypto" > android:process=":crypto" >
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" />
<category android:name="android.intent.category.DEFAULT" /> <!-- Don't publish intents, they are only used internally! -->
</intent-filter>
</activity> </activity>
<!-- Crypto Provider API -->
<activity <activity
android:name=".crypto_provider.RegisterActivity" android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity"
android:label="TODO reg" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:process=":crypto" > android:exported="false"
<intent-filter> android:label="@string/title_crypto_consumers" />
<action android:name="com.android.crypto.REGISTER" /> <activity
android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false" />
<category android:name="android.intent.category.DEFAULT" /> <!-- Remote API -->
</intent-filter>
</activity>
<service <service
android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService" android:name="org.sufficientlysecure.keychain.remote_api.CryptoService"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:process=":crypto" > android:process=":crypto" >
<intent-filter> <intent-filter>
<action android:name="com.android.crypto.ICryptoService" /> <action android:name="org.openintents.crypto.ICryptoService" />
</intent-filter>
<intent-filter>
<!-- Can only be used from OpenPGP Keychain (internal): -->
<action android:name="org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback" />
</intent-filter> </intent-filter>
<meta-data <meta-data

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,27 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/abs__list_divider_holo_light"
android:dividerPadding="12dp"
android:orientation="horizontal"
android:showDividers="end" >
<include layout="@layout/actionbar_include_done_button" />
</LinearLayout>

View File

@ -0,0 +1,29 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/abs__list_divider_holo_light"
android:dividerPadding="12dp"
android:orientation="horizontal"
android:showDividers="middle" >
<include layout="@layout/actionbar_include_cancel_button" />
<include layout="@layout/actionbar_include_done_button" />
</LinearLayout>

View File

@ -0,0 +1,36 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actionbar_cancel"
style="@style/Widget.Sherlock.ActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/actionbar_cancel_text"
style="@style/Widget.Sherlock.ActionBar.TabText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_cancel"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="Cancel (set in-code!)" />
</FrameLayout>

View File

@ -0,0 +1,36 @@
<!--
Copyright 2013 The Android Open Source Project
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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actionbar_done"
style="@style/Widget.Sherlock.ActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
android:id="@+id/actionbar_done_text"
style="@style/Widget.Sherlock.ActionBar.TabText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="Done (set in-code!)" />
</FrameLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:id="@+id/api_register_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="3dip"
android:text="@string/api_register_text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<fragment
android:id="@+id/api_app_settings_fragment"
android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/api_app_settings_fragment" />
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="8dp" >
<fragment
android:id="@+id/api_app_settings_fragment"
android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/api_app_settings_fragment" />
</LinearLayout>

View File

@ -0,0 +1,96 @@
<?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" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:paddingBottom="3dip" >
<ImageView
android:id="@+id/api_app_settings_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dp"
android:src="@drawable/icon" />
<TextView
android:id="@+id/api_app_settings_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/api_app_settings_app_icon"
android:gravity="center_vertical"
android:orientation="vertical"
android:text="Name (set in-code)"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/api_app_settings_select_key_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/api_settings_select_key" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp" >
<TextView
android:id="@+id/api_app_settings_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/api_settings_no_key"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/api_app_settings_user_id_rest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:ellipsize="end"
android:singleLine="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/api_app_settings_advanced_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/api_settings_show_advanced" />
<LinearLayout
android:id="@+id/api_app_settings_advanced"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="invisible" >
<CheckBox
android:id="@+id/api_app_ascii_armor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_asciiArmour" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,28 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="left"
android:orientation="horizontal" >
<ImageView
android:id="@+id/api_apps_adapter_item_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:src="@drawable/icon" />
<TextView
android:id="@+id/api_apps_adapter_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/api_apps_adapter_item_icon"
android:gravity="center_vertical"
android:orientation="vertical"
android:text="Set in-code!"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/crypto_consumers_list_fragment"
android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -22,7 +22,6 @@
android:orientation="vertical" > android:orientation="vertical" >
<ScrollView <ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >

View File

@ -1,25 +0,0 @@
<?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>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_api_settings_revoke"
android:showAsAction="never"
android:title="@string/api_settings_revoke"/>
<item
android:id="@+id/menu_api_settings_cancel"
android:showAsAction="never"
android:title="@string/api_settings_cancel"/>
</menu>

View File

@ -30,6 +30,7 @@
<string name="title_createKey">Create Key</string> <string name="title_createKey">Create Key</string>
<string name="title_editKey">Edit Key</string> <string name="title_editKey">Edit Key</string>
<string name="title_preferences">Preferences</string> <string name="title_preferences">Preferences</string>
<string name="title_crypto_consumers">Registered Applications</string>
<string name="title_keyServerPreference">Key Server Preference</string> <string name="title_keyServerPreference">Key Server Preference</string>
<string name="title_changePassPhrase">Change Passphrase</string> <string name="title_changePassPhrase">Change Passphrase</string>
<string name="title_setPassPhrase">Set Passphrase</string> <string name="title_setPassPhrase">Set Passphrase</string>
@ -87,6 +88,7 @@
<string name="menu_managePublicKeys">Manage Public Keys</string> <string name="menu_managePublicKeys">Manage Public Keys</string>
<string name="menu_manageSecretKeys">Manage Secret Keys</string> <string name="menu_manageSecretKeys">Manage Secret Keys</string>
<string name="menu_preferences">Settings</string> <string name="menu_preferences">Settings</string>
<string name="menu_apiAppSettings">Registered Apps</string>
<string name="menu_importFromFile">Import from file</string> <string name="menu_importFromFile">Import from file</string>
<string name="menu_importFromQrCode">Import from QR Code</string> <string name="menu_importFromQrCode">Import from QR Code</string>
<string name="menu_importFromNfc">Import from NFC</string> <string name="menu_importFromNfc">Import from NFC</string>
@ -362,4 +364,18 @@
<string name="intent_send_encrypt">OpenPGP: Encrypt</string> <string name="intent_send_encrypt">OpenPGP: Encrypt</string>
<string name="intent_send_decrypt">OpenPGP: Decrypt</string> <string name="intent_send_decrypt">OpenPGP: Decrypt</string>
<!-- Remote API -->
<string name="api_no_apps">No registered applications!</string>
<string name="api_settings_show_advanced">Show advanced settings…</string>
<string name="api_settings_hide_advanced">Hide advanced settings…</string>
<string name="api_settings_no_key">No key selected</string>
<string name="api_settings_select_key">Select key</string>
<string name="api_settings_save">Save</string>
<string name="api_settings_cancel">Cancel</string>
<string name="api_settings_revoke">Revoke access</string>
<string name="api_register_text">The following application requests access to OpenPGP Keychain\'s API.\n\nAllow permanent access?</string>
<string name="api_register_allow">Allow access</string>
<string name="api_register_disallow">Disallow access</string>
<string name="api_register_error_select_key">Please select a key!</string>
</resources> </resources>

View File

@ -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 org.openintents.crypto;
// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoError;

View File

@ -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 org.openintents.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];
}
};
}

View File

@ -0,0 +1,91 @@
/*
* 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.openintents.crypto;
import org.openintents.crypto.ICryptoService;
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("org.openintents.crypto.ICryptoService");
serviceIntent.setPackage(cryptoProviderPackageName);
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);
}
}

View File

@ -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 org.openintents.crypto;
// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoSignatureResult;

View File

@ -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 org.openintents.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];
}
};
}

View File

@ -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 org.openintents.crypto;
import org.openintents.crypto.CryptoSignatureResult;
import org.openintents.crypto.CryptoError;
interface ICryptoCallback {
/**
* CryptoSignatureResult is only returned if the Callback was used from decryptAndVerify
*
*/
oneway void onSuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
oneway void onError(in CryptoError error);
}

View File

@ -0,0 +1,82 @@
/*
* 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.openintents.crypto;
import org.openintents.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 encryptionUserIds
* User Ids (emails) of recipients
* @param callback
* Callback where to return results
*/
oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback);
/**
* Sign
*
* @param inputBytes
* Byte array you want to encrypt
* @param signatureUserId
* User Ids (email) of sender
* @param callback
* Callback where to return results
*/
oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback);
/**
* Encrypt and sign
*
* @param inputBytes
* Byte array you want to encrypt
* @param encryptionUserIds
* User Ids (emails) of recipients
* @param signatureUserId
* User Ids (email) of sender
* @param callback
* Callback where to return results
*/
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, 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 callback
* Callback where to return results
*/
oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
/**
* Opens setup using default parameters
*
*/
oneway void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId);
}

View File

@ -62,6 +62,7 @@ public final class Id {
public static final int import_from_file = 0x21070020; public static final int import_from_file = 0x21070020;
public static final int import_from_qr_code = 0x21070021; public static final int import_from_qr_code = 0x21070021;
public static final int import_from_nfc = 0x21070022; public static final int import_from_nfc = 0x21070022;
public static final int crypto_consumers = 0x21070023;
} }
} }

View File

@ -1,92 +0,0 @@
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);
}
}
}

View File

@ -1,196 +0,0 @@
/*
* 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;
// }
// }
}

View File

@ -1,74 +0,0 @@
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();
}
}
}

View File

@ -120,28 +120,33 @@ public class OtherHelper {
public static void checkPackagePermissionForActions(Activity activity, String pkgName, public static void checkPackagePermissionForActions(Activity activity, String pkgName,
String permName, String action, String[] restrictedActions) { String permName, String action, String[] restrictedActions) {
if (action != null) { if (action != null) {
PackageManager pkgManager = activity.getPackageManager(); // PackageManager pkgManager = activity.getPackageManager();
for (int i = 0; i < restrictedActions.length; i++) { // for (int i = 0; i < restrictedActions.length; i++) {
if (restrictedActions[i].equals(action)) { // if (restrictedActions[i].equals(action)) {
if (pkgName != null // if (pkgName != null
&& (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName // && (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
.equals(Constants.PACKAGE_NAME))) { // .equals(Constants.PACKAGE_NAME))) {
Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action " // Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
+ action + " was granted!"); // + action + " was granted!");
} else { // } else {
String error = pkgName + " does NOT have permission " + permName // String error = pkgName + " does NOT have permission " + permName
+ ". Action " + action + " was NOT granted!"; // + ". Action " + action + " was NOT granted!";
Log.e(Constants.TAG, error); // Log.e(Constants.TAG, error);
Toast.makeText(activity, activity.getString(R.string.errorMessage, error), // Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
Toast.LENGTH_LONG).show(); // Toast.LENGTH_LONG).show();
//
// end activity // // end activity
activity.setResult(Activity.RESULT_CANCELED, null); // activity.setResult(Activity.RESULT_CANCELED, null);
activity.finish(); // activity.finish();
} // }
} // }
} // }
// TODO: currently always cancels! THis is the old API
// end activity
activity.setResult(Activity.RESULT_CANCELED, null);
activity.finish();
} }
} }

View File

@ -43,7 +43,7 @@ public class KeychainContract {
String CREATION = "creation"; String CREATION = "creation";
String EXPIRY = "expiry"; String EXPIRY = "expiry";
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
String KEY_DATA = "key_data"; // PGPPublicKey / PGPSecretKey blob String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
String RANK = "rank"; String RANK = "rank";
} }
@ -53,8 +53,13 @@ public class KeychainContract {
String RANK = "rank"; String RANK = "rank";
} }
interface CryptoConsumersColumns { interface ApiAppsColumns {
String PACKAGE_NAME = "package_name"; String PACKAGE_NAME = "package_name";
String KEY_ID = "key_id"; // not a database id
String ASCII_ARMOR = "ascii_armor";
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
String HASH_ALORITHM = "hash_algorithm";
String COMPRESSION = "compression";
} }
public static final class KeyTypes { public static final class KeyTypes {
@ -82,7 +87,8 @@ public class KeychainContract {
public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys"; public static final String PATH_KEYS = "keys";
public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers"; public static final String BASE_API_APPS = "api_apps";
public static final String PATH_BY_PACKAGE_NAME = "package_name";
public static class KeyRings implements KeyRingsColumns, BaseColumns { public static class KeyRings implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
@ -213,15 +219,24 @@ public class KeychainContract {
} }
} }
public static class CryptoConsumers implements CryptoConsumersColumns, BaseColumns { public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_CRYPTO_CONSUMERS).build(); .appendPath(BASE_API_APPS).build();
/** Use if multiple items get returned */ /** Use if multiple items get returned */
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.crypto_consumers"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
/** Use if a single item is returned */ /** Use if a single item is returned */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.crypto_consumers"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
public static Uri buildIdUri(String rowId) {
return CONTENT_URI.buildUpon().appendPath(rowId).build();
}
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
.build();
}
} }
public static class DataStream { public static class DataStream {

View File

@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
@ -37,7 +37,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS = "key_rings"; String KEY_RINGS = "key_rings";
String KEYS = "keys"; String KEYS = "keys";
String USER_IDS = "user_ids"; String USER_IDS = "user_ids";
String CRYPTO_CONSUMERS = "crypto_consumers"; String API_APPS = "api_apps";
} }
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
@ -64,10 +64,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "(" + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)"; + BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS " private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS "
+ Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID + Tables.API_APPS + " (" + BaseColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME
+ " TEXT UNIQUE)"; + " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, "
+ ApiAppsColumns.ASCII_ARMOR + " INTEGER, "
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
KeychainDatabase(Context context) { KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -80,7 +84,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEY_RINGS); db.execSQL(CREATE_KEY_RINGS);
db.execSQL(CREATE_KEYS); db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS); db.execSQL(CREATE_USER_IDS);
db.execSQL(CREATE_CRYPTO_CONSUMERS); db.execSQL(CREATE_API_APPS);
} }
@Override @Override
@ -108,7 +112,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;"); + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
break; break;
case 4: case 4:
db.execSQL(CREATE_CRYPTO_CONSUMERS); db.execSQL(CREATE_API_APPS);
default: default:
break; break;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org> * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,13 +17,11 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@ -44,7 +42,6 @@ import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import android.text.TextUtils; import android.text.TextUtils;
@ -81,7 +78,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 = 221;
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222; private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
private static final int CRYPTO_CONSUMERS = 301; private static final int API_APPS = 301;
private static final int API_APPS_BY_ROW_ID = 302;
private static final int API_APPS_BY_PACKAGE_NAME = 303;
// private static final int DATA_STREAM = 401; // private static final int DATA_STREAM = 401;
@ -227,9 +226,12 @@ public class KeychainProvider extends ContentProvider {
SECRET_KEY_RING_USER_ID_BY_ROW_ID); SECRET_KEY_RING_USER_ID_BY_ROW_ID);
/** /**
* Crypto Consumers * API apps
*/ */
matcher.addURI(authority, KeychainContract.BASE_CRYPTO_CONSUMERS, CRYPTO_CONSUMERS); matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
/** /**
* data stream * data stream
@ -290,8 +292,12 @@ public class KeychainProvider extends ContentProvider {
case SECRET_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
return UserIds.CONTENT_ITEM_TYPE; return UserIds.CONTENT_ITEM_TYPE;
case CRYPTO_CONSUMERS: case API_APPS:
return CryptoConsumers.CONTENT_TYPE; return ApiApps.CONTENT_TYPE;
case API_APPS_BY_ROW_ID:
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
@ -600,10 +606,23 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment()); qb.appendWhereEscapeString(uri.getLastPathSegment());
break; break;
case CRYPTO_CONSUMERS: case API_APPS:
qb.setTables(Tables.CRYPTO_CONSUMERS); qb.setTables(Tables.API_APPS);
break;
case API_APPS_BY_ROW_ID:
qb.setTables(Tables.API_APPS);
qb.appendWhere(BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_APPS_BY_PACKAGE_NAME:
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
break; break;
default: default:
@ -653,6 +672,7 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)); rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_KEY: case PUBLIC_KEY_RING_KEY:
@ -660,11 +680,13 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEYS, null, values); rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId)); rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId)); rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case SECRET_KEY_RING: case SECRET_KEY_RING:
@ -672,6 +694,7 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case SECRET_KEY_RING_KEY: case SECRET_KEY_RING_KEY:
@ -679,12 +702,18 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEYS, null, values); rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId)); rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case SECRET_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId)); rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
break;
case API_APPS:
rowId = db.insertOrThrow(Tables.API_APPS, null, values);
rowUri = ApiApps.buildIdUri(Long.toString(rowId));
break; break;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
@ -692,7 +721,6 @@ public class KeychainProvider extends ContentProvider {
// notify of changes in db // notify of changes in db
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
} catch (SQLiteConstraintException e) { } catch (SQLiteConstraintException e) {
Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?"); Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
@ -720,6 +748,7 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEY_RINGS, count = db.delete(Tables.KEY_RINGS,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
selectionArgs); selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID:
@ -728,24 +757,33 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEY_RINGS, count = db.delete(Tables.KEY_RINGS,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
selectionArgs); selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID:
count = db.delete(Tables.KEYS, count = db.delete(Tables.KEYS,
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs); buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection), count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
selectionArgs); selectionArgs);
break; break;
case API_APPS_BY_ROW_ID:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
selectionArgs);
break;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
} }
// notify of changes in db // notify of changes in db
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
return count; return count;
} }
@ -771,6 +809,8 @@ public class KeychainProvider extends ContentProvider {
values, values,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
selection), selectionArgs); selection), selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID:
@ -781,6 +821,8 @@ public class KeychainProvider extends ContentProvider {
values, values,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
selection), selectionArgs); selection), selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID:
@ -788,19 +830,28 @@ public class KeychainProvider extends ContentProvider {
.update(Tables.KEYS, values, .update(Tables.KEYS, values,
buildDefaultKeysSelection(uri, getKeyType(match), selection), buildDefaultKeysSelection(uri, getKeyType(match), selection),
selectionArgs); selectionArgs);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
count = db.update(Tables.USER_IDS, values, count = db.update(Tables.USER_IDS, values,
buildDefaultUserIdsSelection(uri, selection), selectionArgs); buildDefaultUserIdsSelection(uri, selection), selectionArgs);
break; break;
case API_APPS_BY_ROW_ID:
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME:
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, true, selection), selectionArgs);
break;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
} }
// notify of changes in db // notify of changes in db
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
} catch (SQLiteConstraintException e) { } catch (SQLiteConstraintException e) {
Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?"); Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
@ -883,6 +934,29 @@ public class KeychainProvider extends ContentProvider {
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection; return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
} }
/**
* Build default selection statement for API apps. If no extra selection is specified only build
* where clause with rowId
*
* @param uri
* @param selection
* @return
*/
private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) {
String lastPathSegment = uri.getLastPathSegment();
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
if (packageSelection) {
return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
} else {
return BaseColumns._ID + "=" + lastPathSegment + andSelection;
}
}
// @Override // @Override
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// int match = mUriMatcher.match(uri); // int match = mUriMatcher.match(uri);
@ -899,10 +973,12 @@ public class KeychainProvider extends ContentProvider {
* updated, or deleted * updated, or deleted
*/ */
private void sendBroadcastDatabaseChange(int keyType, String contentItemType) { private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
Intent intent = new Intent(); // TODO: Disabled, old API
intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE); // Intent intent = new Intent();
intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType); // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType); // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API); // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
//
// getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
} }
} }

View File

@ -31,11 +31,12 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.PgpConversionHelper; import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper; import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.helper.PgpMain; import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.remote_api.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -718,13 +719,13 @@ public class ProviderHelper {
return cursor; return cursor;
} }
public static ArrayList<String> getCryptoConsumers(Context context) { public static ArrayList<String> getRegisteredApiApps(Context context) {
Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null, Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
null, null); null);
ArrayList<String> packageNames = new ArrayList<String>(); ArrayList<String> packageNames = new ArrayList<String>();
if (cursor != null) { if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME); int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
packageNames.add(cursor.getString(packageNameCol)); packageNames.add(cursor.getString(packageNameCol));
@ -739,9 +740,53 @@ public class ProviderHelper {
return packageNames; return packageNames;
} }
public static void addCryptoConsumer(Context context, String packageName) { private static void contentValueForApiApps() {
}
public static void insertApiApp(Context context, AppSettings appSettings) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(CryptoConsumers.PACKAGE_NAME, packageName); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values); values.put(ApiApps.KEY_ID, appSettings.getKeyId());
values.put(ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
// TODO: other parameters
context.getContentResolver().insert(ApiApps.CONTENT_URI, values);
}
public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
final ContentValues cv = new ContentValues();
cv.put(KeychainContract.ApiApps.KEY_ID, appSettings.getKeyId());
cv.put(KeychainContract.ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
// TODO: other parameters
if (context.getContentResolver().update(uri, cv, null, null) <= 0) {
throw new RuntimeException();
}
}
public static AppSettings getApiAppSettings(Context context, Uri uri) {
AppSettings settings = new AppSettings();
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur == null) {
return null;
}
if (cur.moveToFirst()) {
settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
settings.setAsciiArmor(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.ApiApps.ASCII_ARMOR)) == 1);
settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
}
return settings;
} }
} }

View File

@ -0,0 +1,70 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.Id;
public class AppSettings {
private String packageName;
private long keyId = Id.key.none;
private boolean asciiArmor;
private int encryptionAlgorithm = 7; // AES-128
private int hashAlgorithm = 10; // SHA-512
private int compression = 2; // zlib
public AppSettings() {
}
public AppSettings(String packageName) {
super();
this.packageName = packageName;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public long getKeyId() {
return keyId;
}
public void setKeyId(long scretKeyId) {
this.keyId = scretKeyId;
}
public boolean isAsciiArmor() {
return asciiArmor;
}
public void setAsciiArmor(boolean asciiArmor) {
this.asciiArmor = asciiArmor;
}
public int getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
public void setEncryptionAlgorithm(int encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
}
public int getHashAlgorithm() {
return hashAlgorithm;
}
public void setHashAlgorithm(int hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm;
}
public int getCompression() {
return compression;
}
public void setCompression(int compression) {
this.compression = compression;
}
}

View File

@ -0,0 +1,107 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
public class AppSettingsActivity extends SherlockFragmentActivity {
private Uri mAppUri;
private AppSettingsFragment settingsFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
.getSystemService(LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater
.inflate(R.layout.actionbar_custom_view_done, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text))
.setText(R.string.api_settings_save);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// "Done"
save();
}
});
// Show the custom action bar view and hide the normal Home icon and title.
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM
| ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setCustomView(customActionBarView);
setContentView(R.layout.api_app_settings_activity);
settingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment);
Intent intent = getIntent();
mAppUri = intent.getData();
if (mAppUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
finish();
return;
} else {
Log.d(Constants.TAG, "uri: " + mAppUri);
loadData(mAppUri);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.api_app_settings, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_api_settings_revoke:
revokeAccess();
return true;
case R.id.menu_api_settings_cancel:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void loadData(Uri appUri) {
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
settingsFragment.setAppSettings(settings);
}
private void revokeAccess() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
throw new RuntimeException();
}
finish();
}
private void save() {
ProviderHelper.updateApiApp(this, settingsFragment.getAppSettings(), mAppUri);
finish();
}
}

View File

@ -0,0 +1,208 @@
package org.sufficientlysecure.keychain.remote_api;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyActivity;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class AppSettingsFragment extends Fragment {
// model
private AppSettings appSettings;
// view
private LinearLayout mAdvancedSettingsContainer;
private Button mAdvancedSettingsButton;
private TextView mAppNameView;
private ImageView mAppIconView;
private TextView mKeyUserId;
private TextView mKeyUserIdRest;
private Button mSelectKeyButton;
private CheckBox mAsciiArmorCheckBox;
public AppSettings getAppSettings() {
return appSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.appSettings = appSettings;
setPackage(appSettings.getPackageName());
updateSelectedKeyView(appSettings.getKeyId());
mAsciiArmorCheckBox.setChecked(appSettings.isAsciiArmor());
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
initView(view);
return view;
}
private void initView(View view) {
mAdvancedSettingsButton = (Button) view.findViewById(R.id.api_app_settings_advanced_button);
mAdvancedSettingsContainer = (LinearLayout) view
.findViewById(R.id.api_app_settings_advanced);
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
mKeyUserId = (TextView) view.findViewById(R.id.api_app_settings_user_id);
mKeyUserIdRest = (TextView) view.findViewById(R.id.api_app_settings_user_id_rest);
mSelectKeyButton = (Button) view.findViewById(R.id.api_app_settings_select_key_button);
mAsciiArmorCheckBox = (CheckBox) view.findViewById(R.id.api_app_ascii_armor);
mSelectKeyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
selectSecretKey();
}
});
mAsciiArmorCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
appSettings.setAsciiArmor(isChecked);
}
});
final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
visibleAnimation.setDuration(250);
final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
invisibleAnimation.setDuration(250);
// TODO: Better: collapse/expand animation
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
// Animation.RELATIVE_TO_SELF, 0.0f);
// animation2.setDuration(150);
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
mAdvancedSettingsContainer.setVisibility(View.INVISIBLE);
mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced);
} else {
mAdvancedSettingsContainer.startAnimation(visibleAnimation);
mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
mAdvancedSettingsButton.setText(R.string.api_settings_hide_advanced);
}
}
});
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
private void selectSecretKey() {
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
startActivityForResult(intent, Id.request.secret_keys);
}
private void setPackage(String packageName) {
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
// get application name and icon from package manager
String appName = null;
Drawable appIcon = null;
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (final NameNotFoundException e) {
// fallback
appName = packageName;
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
}
private void updateSelectedKeyView(long secretKeyId) {
if (secretKeyId == Id.key.none) {
mKeyUserId.setText(R.string.api_settings_no_key);
mKeyUserIdRest.setText("");
} else {
String uid = getResources().getString(R.string.unknownUserId);
String uidExtra = "";
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
getActivity(), secretKeyId);
if (keyRing != null) {
PGPSecretKey key = PgpHelper.getMasterKey(keyRing);
if (key != null) {
String userId = PgpHelper.getMainUserIdSafe(getActivity(), key);
String chunks[] = userId.split(" <", 2);
uid = chunks[0];
if (chunks.length > 1) {
uidExtra = "<" + chunks[1];
}
}
}
mKeyUserId.setText(uid);
mKeyUserIdRest.setText(uidExtra);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(Constants.TAG, "onactivityresult " + requestCode + " " + resultCode);
switch (requestCode) {
case Id.request.secret_keys: {
long secretKeyId;
if (resultCode == Activity.RESULT_OK) {
Bundle bundle = data.getExtras();
secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
} else {
secretKeyId = Id.key.none;
}
appSettings.setKeyId(secretKeyId);
updateSelectedKeyView(secretKeyId);
break;
}
default: {
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
}

View File

@ -0,0 +1,409 @@
/*
* 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.remote_api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.prefs.Preferences;
import org.openintents.crypto.CryptoError;
import org.openintents.crypto.CryptoSignatureResult;
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.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote_api.IServiceActivityCallback;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
import org.openintents.crypto.ICryptoCallback;
import org.openintents.crypto.ICryptoService;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
public class CryptoService extends Service {
Context mContext;
// just one pool of 4 threads, pause on every user action needed
final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(20);
PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
TimeUnit.SECONDS, mPoolQueue);
public static final String ACTION_SERVICE_ACTIVITY = "org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback";
@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 different binder for connections from internal service activity
if (ACTION_SERVICE_ACTIVITY.equals(intent.getAction())) {
// this binder can only be used from OpenPGP Keychain
if (isCallerAllowed(true)) {
return mBinderServiceActivity;
} else {
Log.e(Constants.TAG, "This binder can only be used from " + Constants.PACKAGE_NAME);
return null;
}
} else {
return mBinder;
}
}
private String getCachedPassphrase(long keyId) {
String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId);
if (passphrase == null) {
Log.d(Constants.TAG, "No passphrase! Activity required!");
// start passphrase dialog
Bundle extras = new Bundle();
extras.putLong(CryptoServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
pauseQueueAndStartServiceActivity(CryptoServiceActivity.ACTION_CACHE_PASSPHRASE, extras);
}
return passphrase;
}
private synchronized void encryptSafe(byte[] inputBytes, String[] encryptionUserIds,
AppSettings appSettings, 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 = getCachedPassphrase(appSettings.getKeyId());
PgpMain.encryptAndSign(mContext, null, inputData, outputStream,
appSettings.isAsciiArmor(), appSettings.getCompression(), new long[] {},
"test", appSettings.getEncryptionAlgorithm(), Id.key.none,
appSettings.getHashAlgorithm(), true, passphrase);
// PgpMain.encryptAndSign(this, this, inputData, outputStream,
// appSettings.isAsciiArmor(),
// appSettings.getCompression(), encryptionKeyIds, encryptionPassphrase,
// appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
// appSettings.getHashAlgorithm(), true, passphrase);
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// return over handler on client side
callback.onSuccess(outputBytes, null);
} 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 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();
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
// app, Fix this?
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 = getCachedPassphrase(secretKeyId);
// 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.onSuccess(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(final byte[] inputBytes, final String[] encryptionUserIds,
final ICryptoCallback callback) throws RemoteException {
final AppSettings settings = getAppSettings();
Runnable r = new Runnable() {
@Override
public void run() {
try {
encryptSafe(inputBytes, encryptionUserIds, settings, callback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
}
}
};
checkAndEnqueue(r);
}
@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(final byte[] inputBytes, final ICryptoCallback callback)
throws RemoteException {
Runnable r = new Runnable() {
@Override
public void run() {
try {
decryptAndVerifySafe(inputBytes, callback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoService", e);
}
}
};
checkAndEnqueue(r);
}
@Override
public void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId)
throws RemoteException {
// TODO Auto-generated method stub
}
};
private final IServiceActivityCallback.Stub mBinderServiceActivity = new IServiceActivityCallback.Stub() {
@Override
public void onRegistered(boolean success, String packageName) throws RemoteException {
if (success) {
// resume threads
if (isPackageAllowed(packageName, false)) {
mThreadPool.resume();
} else {
// TODO: should not happen?
}
} else {
// TODO
mPoolQueue.clear();
}
}
@Override
public void onCachedPassphrase(boolean success) throws RemoteException {
}
};
private void checkAndEnqueue(Runnable r) {
if (isCallerAllowed(false)) {
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…");
} else {
String[] callingPackages = getPackageManager()
.getPackagesForUid(Binder.getCallingUid());
Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!");
Bundle extras = new Bundle();
// TODO: currently simply uses first entry
extras.putString(CryptoServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]);
pauseQueueAndStartServiceActivity(CryptoServiceActivity.ACTION_REGISTER, extras);
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…");
}
}
/**
* Checks if process that binds to this service (i.e. the package name corresponding to the
* process) is in the list of allowed package names.
*
* @param allowOnlySelf
* allow only Keychain app itself
* @return true if process is allowed to use this service
*/
private boolean isCallerAllowed(boolean allowOnlySelf) {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
// is calling package allowed to use this service?
for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i];
if (isPackageAllowed(currentPkg, allowOnlySelf)) {
return true;
}
}
Log.d(Constants.TAG, "Caller is NOT allowed!");
return false;
}
private AppSettings getAppSettings() {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
// is calling package allowed to use this service?
for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i];
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
return settings;
}
return null;
}
/**
* Checks if packageName is a registered app for the API.
*
* @param packageName
* @param allowOnlySelf
* allow only Keychain app itself
* @return
*/
private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) {
Log.d(Constants.TAG, "packageName: " + packageName);
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext);
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
// check if package is allowed to use our service
if (allowedPkgs.contains(packageName) && (!allowOnlySelf)) {
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
return true;
} else if (Constants.PACKAGE_NAME.equals(packageName)) {
Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!");
return true;
}
return false;
}
private void pauseQueueAndStartServiceActivity(String action, Bundle extras) {
mThreadPool.pause();
Log.d(Constants.TAG, "starting activity...");
Intent intent = new Intent(getBaseContext(), CryptoServiceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(action);
if (extras != null) {
intent.putExtras(extras);
}
getApplication().startActivity(intent);
}
}

View File

@ -0,0 +1,267 @@
/*
* 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.remote_api;
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 android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
public class CryptoServiceActivity extends SherlockFragmentActivity {
public static final String ACTION_REGISTER = "org.sufficientlysecure.keychain.remote_api.REGISTER";
public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.remote_api.CRYPTO_CACHE_PASSPHRASE";
public static final String EXTRA_SECRET_KEY_ID = "secretKeyId";
public static final String EXTRA_PACKAGE_NAME = "packageName";
private IServiceActivityCallback mServiceCallback;
private boolean mServiceBound;
// view
AppSettingsFragment settingsFragment;
private ServiceConnection mServiceActivityConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mServiceCallback = IServiceActivityCallback.Stub.asInterface(service);
Log.d(Constants.TAG, "connected to ICryptoServiceActivity");
mServiceBound = true;
}
public void onServiceDisconnected(ComponentName name) {
mServiceCallback = null;
Log.d(Constants.TAG, "disconnected from ICryptoServiceActivity");
mServiceBound = false;
}
};
/**
* If not already bound, bind!
*
* @return
*/
public boolean bindToService() {
if (mServiceCallback == null && !mServiceBound) { // if not already connected
try {
Log.d(Constants.TAG, "not bound yet");
Intent serviceIntent = new Intent();
serviceIntent
.setAction("org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback");
bindService(serviceIntent, mServiceActivityConnection, Context.BIND_AUTO_CREATE);
return true;
} catch (Exception e) {
Log.d(Constants.TAG, "Exception", e);
return false;
}
} else { // already connected
Log.d(Constants.TAG, "already bound... ");
return true;
}
}
public void unbindFromService() {
unbindService(mServiceActivityConnection);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Constants.TAG, "onCreate…");
// bind to our own crypto service
bindToService();
handleActions(getIntent());
}
@Override
protected void onDestroy() {
super.onDestroy();
// unbind from our crypto service
if (mServiceActivityConnection != null) {
unbindFromService();
}
}
protected void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
/**
* com.android.crypto actions
*/
if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
// Inflate a "Done"/"Cancel" custom action bar view
final LayoutInflater inflater = (LayoutInflater) getSupportActionBar()
.getThemedContext().getSystemService(LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate(
R.layout.actionbar_custom_view_done_cancel, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text))
.setText(R.string.api_register_allow);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Allow
// user needs to select a key!
if (settingsFragment.getAppSettings().getKeyId() == Id.key.none) {
Toast.makeText(CryptoServiceActivity.this,
R.string.api_register_error_select_key, Toast.LENGTH_LONG)
.show();
} else {
ProviderHelper.insertApiApp(CryptoServiceActivity.this,
settingsFragment.getAppSettings());
try {
mServiceCallback.onRegistered(true, packageName);
} catch (RemoteException e) {
Log.e(Constants.TAG, "ServiceActivity");
}
finish();
}
}
});
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text))
.setText(R.string.api_register_disallow);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Disallow
try {
mServiceCallback.onRegistered(false, packageName);
} catch (RemoteException e) {
Log.e(Constants.TAG, "ServiceActivity");
}
finish();
}
});
// Show the custom action bar view and hide the normal Home icon and title.
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME
| ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(R.layout.api_app_register_activity);
settingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment);
AppSettings settings = new AppSettings(packageName);
settingsFragment.setAppSettings(settings);
// TODO: handle if app is already registered
// LinearLayout layoutRegister = (LinearLayout)
// findViewById(R.id.register_crypto_consumer_register_layout);
// LinearLayout layoutEdit = (LinearLayout)
// findViewById(R.id.register_crypto_consumer_edit_layout);
//
// // if already registered show edit buttons
// ArrayList<String> allowedPkgs = ProviderHelper.getCryptoConsumers(this);
// if (allowedPkgs.contains(packageName)) {
// Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
// layoutRegister.setVisibility(View.GONE);
// layoutEdit.setVisibility(View.VISIBLE);
// } else {
// layoutRegister.setVisibility(View.VISIBLE);
// layoutEdit.setVisibility(View.GONE);
// }
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
showPassphraseDialog(secretKeyId);
} else {
Log.e(Constants.TAG, "Wrong action!");
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) {
try {
mServiceCallback.onCachedPassphrase(true);
} catch (RemoteException e) {
Log.e(Constants.TAG, "ServiceActivity");
}
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);
}
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.remote_api;
interface IServiceActivityCallback {
oneway void onRegistered(in boolean success, in String packageName);
oneway void onCachedPassphrase(in boolean success);
}

View File

@ -0,0 +1,75 @@
/*
* 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.remote_api;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
public class RegisteredAppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager pm;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
pm = context.getApplicationContext().getPackageManager();
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
if (packageName != null) {
// get application name
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
text.setText(pm.getApplicationLabel(ai));
icon.setImageDrawable(pm.getApplicationIcon(ai));
} catch (final NameNotFoundException e) {
// fallback
text.setText(packageName);
}
} else {
// fallback
text.setText(packageName);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}

View File

@ -0,0 +1,44 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.MainActivity;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
import android.content.Intent;
import android.os.Bundle;
public class RegisteredAppsListActivity extends SherlockFragmentActivity {
private ActionBar mActionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActionBar = getSupportActionBar();
setContentView(R.layout.api_apps_list_activity);
mActionBar.setDisplayShowTitleEnabled(true);
mActionBar.setDisplayHomeAsUpEnabled(true);
}
/**
* Menu Options
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in Action Bar clicked; go home
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -0,0 +1,90 @@
package org.sufficientlysecure.keychain.remote_api;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import com.actionbarsherlock.app.SherlockListFragment;
public class RegisteredAppsListFragment extends SherlockListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
RegisteredAppsAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
// edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
startActivity(intent);
}
});
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.api_no_apps));
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONSUMERS_SUMMARY_PROJECTION = new String[] { ApiApps._ID,
ApiApps.PACKAGE_NAME };
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri = ApiApps.CONTENT_URI;
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), baseUri, CONSUMERS_SUMMARY_PROJECTION, null, null,
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}

View File

@ -48,6 +48,12 @@ import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
/**
* This service runs in its own process, but is available to all other processes as the main
* passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
* convenience.
*
*/
public class PassphraseCacheService extends Service { public class PassphraseCacheService extends Service {
public static final String TAG = Constants.TAG + ": PassphraseCacheService"; public static final String TAG = Constants.TAG + ": PassphraseCacheService";
@ -74,9 +80,9 @@ public class PassphraseCacheService extends Service {
Context mContext; Context mContext;
/** /**
* This caches a new passphrase by sending a new command to the service. An android service is * This caches a new passphrase in memory by sending a new command to the service. An android
* only run once. Thus, when the service is already started, new commands just add new events to * service is only run once. Thus, when the service is already started, new commands just add
* the alarm manager for new passphrases to let them timeout in the future. * new events to the alarm manager for new passphrases to let them timeout in the future.
* *
* @param context * @param context
* @param keyId * @param keyId
@ -95,21 +101,23 @@ public class PassphraseCacheService extends Service {
} }
/** /**
* Gets a cached passphrase from memory, blocking method * Gets a cached passphrase from memory by sending an intent to the service. This method is
* designed to wait until the service returns the passphrase.
* *
* @param context * @param context
* @param keyId * @param keyId
* @return * @return passphrase or null (if no passphrase is cached for this keyId)
*/ */
public static String getCachedPassphrase(Context context, long keyId) { public static String getCachedPassphrase(Context context, long keyId) {
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId); Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
Intent intent = new Intent(context, PassphraseCacheService.class); Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_GET); intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
final Object mutex = new Object(); final Object mutex = new Object();
final Bundle returnBundle = new Bundle(); final Bundle returnBundle = new Bundle();
HandlerThread handlerThread = new HandlerThread("getPassphrase"); HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
handlerThread.start(); handlerThread.start();
Handler returnHandler = new Handler(handlerThread.getLooper()) { Handler returnHandler = new Handler(handlerThread.getLooper()) {
@Override @Override
@ -121,6 +129,7 @@ public class PassphraseCacheService extends Service {
synchronized (mutex) { synchronized (mutex) {
mutex.notify(); mutex.notify();
} }
// quit handlerThread
getLooper().quit(); getLooper().quit();
} }
}; };
@ -147,6 +156,12 @@ public class PassphraseCacheService extends Service {
} }
} }
/**
* Internal implementation to get cached passphrase.
*
* @param keyId
* @return
*/
private String getCachedPassphraseImpl(long keyId) { private String getCachedPassphraseImpl(long keyId) {
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId); Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
@ -163,20 +178,20 @@ public class PassphraseCacheService extends Service {
} }
masterKeyId = masterKey.getKeyID(); masterKeyId = masterKey.getKeyID();
} }
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId" + masterKeyId); Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
// get cached passphrase // get cached passphrase
String cachedPassphrase = mPassphraseCache.get(masterKeyId); String cachedPassphrase = mPassphraseCache.get(masterKeyId);
if (cachedPassphrase == null) { if (cachedPassphrase == null) {
// TODO: fix! // if key has no passphrase -> cache and return empty passphrase
// check if secret key has a passphrase if (!hasPassphrase(this, masterKeyId)) {
// if (!hasPassphrase(context, masterKeyId)) { Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
// // cache empty passphrase
// addCachedPassphrase(context, masterKeyId, ""); addCachedPassphrase(this, masterKeyId, "");
// return ""; return "";
// } else { } else {
return null; return null;
// } }
} }
// set it again to reset the cache life cycle // set it again to reset the cache life cycle
Log.d(TAG, "Cache passphrase again when getting it!"); Log.d(TAG, "Cache passphrase again when getting it!");
@ -196,17 +211,10 @@ public class PassphraseCacheService extends Service {
try { try {
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
.getPGPSecretKeyRingByKeyId(context, secretKeyId)); .getPGPSecretKeyRingByKeyId(context, secretKeyId));
Log.d(Constants.TAG, "Check if key has no passphrase...");
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray()); "SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor); PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
if (testKey != null) { if (testKey != null) {
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
// cache empty passphrase
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
return false; return false;
} }
} catch (PGPException e) { } catch (PGPException e) {

View File

@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity;
import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.app.SherlockActivity;
@ -80,6 +81,9 @@ public class MainActivity extends SherlockActivity {
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences)
.setIcon(R.drawable.ic_menu_settings) .setIcon(R.drawable.ic_menu_settings)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, Id.menu.option.crypto_consumers, 0, R.string.menu_apiAppSettings)
.setIcon(R.drawable.ic_menu_settings)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
return true; return true;
} }
@ -91,6 +95,10 @@ public class MainActivity extends SherlockActivity {
startActivity(new Intent(this, PreferencesActivity.class)); startActivity(new Intent(this, PreferencesActivity.class));
return true; return true;
case Id.menu.option.crypto_consumers:
startActivity(new Intent(this, RegisteredAppsListActivity.class));
return true;
default: default:
break; break;

View File

@ -67,7 +67,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// application this would come from a resource. // application this would come from a resource.
setEmptyText(getString(R.string.listEmpty)); setEmptyText(getString(R.string.listEmpty));
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.public_key); mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key);
setListAdapter(mAdapter); setListAdapter(mAdapter);
@ -160,11 +160,12 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE, + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS "(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = " + " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys." + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_ENCRYPT + " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND " + Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY + now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, }; + Keys.EXPIRY + " >= '" + now + "')) AS "
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
String inMasterKeyList = null; String inMasterKeyList = null;
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {

View File

@ -73,7 +73,7 @@ public class SelectSecretKeyFragment extends SherlockListFragment implements
// application this would come from a resource. // application this would come from a resource.
setEmptyText(getString(R.string.listEmpty)); setEmptyText(getString(R.string.listEmpty));
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.secret_key); mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.secret_key);
setListAdapter(mAdapter); setListAdapter(mAdapter);

View File

@ -23,19 +23,14 @@ import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper; import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -45,11 +40,6 @@ import org.sufficientlysecure.keychain.R;
import android.content.Context; import android.content.Context;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
/**
* A custom Loader to search for bad adware apps, based on
* https://github.com/brosmike/AirPush-Detector. Daniel Bjorge licensed it under Apachev2 after
* asking him by mail.
*/
public class ImportKeysListLoader extends AsyncTaskLoader<List<Map<String, String>>> { public class ImportKeysListLoader extends AsyncTaskLoader<List<Map<String, String>>> {
public static final String MAP_ATTR_USER_ID = "user_id"; public static final String MAP_ATTR_USER_ID = "user_id";
public static final String MAP_ATTR_FINGERPINT = "fingerprint"; public static final String MAP_ATTR_FINGERPINT = "fingerprint";

View File

@ -44,9 +44,9 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
public final static String PROJECTION_ROW_AVAILABLE = "available"; public final static String PROJECTION_ROW_AVAILABLE = "available";
public final static String PROJECTION_ROW_VALID = "valid"; public final static String PROJECTION_ROW_VALID = "valid";
@SuppressWarnings("deprecation") public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
public SelectKeyCursorAdapter(Context context, ListView listView, Cursor c, int keyType) { int keyType) {
super(context, c); super(context, c, flags);
mInflater = LayoutInflater.from(context); mInflater = LayoutInflater.from(context);
mListView = listView; mListView = listView;
@ -65,8 +65,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
boolean valid = cursor.getInt(cursor boolean valid = cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
mainUserId.setText(R.string.unknownUserId); mainUserId.setText(R.string.unknownUserId);
@ -101,8 +100,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
status.setText(R.string.canSign); status.setText(R.string.canSign);
} }
} else { } else {
if (cursor.getInt(cursor if (cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or // has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
// expired // expired
status.setText(R.string.expired); status.setText(R.string.expired);

View File

@ -0,0 +1,89 @@
/*
* 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.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Example from
* http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.html
*/
public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused)
unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}

View File

@ -68,72 +68,6 @@ See http://docs.oseems.com/general/application/eclipse/fix-gc-overhead-limit-exc
1. Open svg file in Inkscape 1. Open svg file in Inkscape
2. Extensions -> Color -> darker (2 times!) 2. Extensions -> Color -> darker (2 times!)
# Security Model
## Basic goals
* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog)
Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL
## Possible Permissions
* ACCESS_API: Encrypt/Sign/Decrypt/Create keys without user interaction (intents, remote service), Read key information (not the actual keys)(content provider)
* ACCESS_KEYS: get and import actual public and secret keys (remote service)
## Without Permissions
### Intents
All Intents start with org.sufficientlysecure.keychain.action.
* android.intent.action.VIEW connected to .gpg and .asc files: Import Key and Decrypt
* android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt
* IMPORT
* IMPORT_FROM_FILE
* IMPORT_FROM_QR_CODE
* IMPORT_FROM_NFC
* SHARE_KEYRING
* SHARE_KEYRING_WITH_QR_CODE
* SHARE_KEYRING_WITH_NFC
* EDIT_KEYRING
* SELECT_PUBLIC_KEYRINGS
* SELECT_SECRET_KEYRING
* ENCRYPT
* ENCRYPT_FILE
* DECRYPT
* DECRYPT_FILE
## With permission ACCESS_API
### Intents
* CREATE_KEYRING
* ENCRYPT_AND_RETURN
* ENCRYPT_STREAM_AND_RETURN
* GENERATE_SIGNATURE_AND_RETURN
* DECRYPT_AND_RETURN
* DECRYPT_STREAM_AND_RETURN
### Broadcast Receiver
On change of database the following broadcast is send.
* DATABASE_CHANGE
### Content Provider
* The whole content provider requires a permission (only read)
* Don't give out blobs (keys can be accessed by ACCESS_KEYS via remote service)
* Make an internal and external content provider (or pathes with <path-permission>)
* Look at android:grantUriPermissions especially for ApgServiceBlobProvider
* Only give out android:readPermission
### ApgApiService (Remote Service)
AIDL service
## With permission ACCESS_KEYS
### ApgKeyService (Remote Service)
AIDL service to access actual private keyring objects
# Coding Style # Coding Style
## Code ## Code