mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-23 17:22:16 -05:00
merge k9mail back into master
This commit is contained in:
commit
de8e1a39d5
34
API.md
Normal file
34
API.md
Normal 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
68
OLD_API.md
Normal 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
|
@ -8,65 +8,72 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/crypto_provider_demo_register"
|
||||
android:layout_width="match_parent"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="registerCryptoProvider"
|
||||
android:text="Register crypto provider" />
|
||||
|
||||
<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)" />
|
||||
android:text="Encrypt User Id"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<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_height="150dip"
|
||||
android:text="message"
|
||||
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
|
||||
android:id="@+id/aidl_demo_ciphertext"
|
||||
android:id="@+id/crypto_provider_demo_ciphertext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dip"
|
||||
android:text="ciphertext"
|
||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/aidl_demo_encrypt"
|
||||
android:id="@+id/crypto_provider_demo_encrypt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="encryptOnClick"
|
||||
android:text="Encrypt" />
|
||||
|
||||
<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_height="wrap_content"
|
||||
android:onClick="decryptOnClick"
|
||||
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>
|
||||
|
||||
</ScrollView>
|
@ -14,7 +14,7 @@
|
||||
* 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.
|
||||
parcelable CryptoError;
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.crypto;
|
||||
package org.openintents.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
@ -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.Context;
|
||||
@ -50,8 +68,8 @@ public class CryptoServiceConnection {
|
||||
Log.d(TAG, "not bound yet");
|
||||
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setAction("com.android.crypto.ICryptoService");
|
||||
serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test
|
||||
serviceIntent.setAction("org.openintents.crypto.ICryptoService");
|
||||
serviceIntent.setPackage(cryptoProviderPackageName);
|
||||
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
|
@ -14,7 +14,7 @@
|
||||
* 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.
|
||||
parcelable CryptoSignatureResult;
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.crypto;
|
||||
package org.openintents.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
@ -14,19 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.crypto;
|
||||
package org.openintents.crypto;
|
||||
|
||||
import com.android.crypto.CryptoSignatureResult;
|
||||
import com.android.crypto.CryptoError;
|
||||
import org.openintents.crypto.CryptoSignatureResult;
|
||||
import org.openintents.crypto.CryptoError;
|
||||
|
||||
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 onActivityRequired(in Intent intent);
|
||||
}
|
@ -14,9 +14,9 @@
|
||||
* 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.
|
||||
@ -29,50 +29,54 @@ interface ICryptoService {
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to encrypt
|
||||
* @param encryptionKeyIds
|
||||
* Ids of public keys used for encryption
|
||||
* @param handler
|
||||
* Results are returned to this Handler after successful encryption
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Encrypt and sign
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to encrypt
|
||||
* @param signatureKeyId
|
||||
* Key id of key to sign with
|
||||
* @param handler
|
||||
* Results are returned to this Handler after successful encryption and signing
|
||||
*/
|
||||
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
|
||||
|
||||
/**
|
||||
* Sign
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param inputBytes
|
||||
* Byte array you want to encrypt
|
||||
* @param signatureId
|
||||
*
|
||||
* @param handler
|
||||
* Results are returned to this Handler after successful encryption and signing
|
||||
* @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 handler
|
||||
* Handler where to return results to after successful encryption
|
||||
* @param callback
|
||||
* 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);
|
||||
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,51 +16,42 @@
|
||||
|
||||
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.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.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
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.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
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.Toast;
|
||||
|
||||
public class CryptoProviderDemoActivity extends Activity {
|
||||
Activity mActivity;
|
||||
|
||||
TextView mMessageTextView;
|
||||
TextView mCiphertextTextView;
|
||||
TextView mDataTextView;
|
||||
EditText mMessage;
|
||||
EditText mCiphertext;
|
||||
EditText mEncryptUserId;
|
||||
EditText mSignUserId;
|
||||
|
||||
KeychainIntentHelper mKeychainIntentHelper;
|
||||
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;
|
||||
}
|
||||
};
|
||||
private CryptoServiceConnection mCryptoServiceConnection;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@ -69,170 +60,208 @@ public class CryptoProviderDemoActivity extends Activity {
|
||||
|
||||
mActivity = this;
|
||||
|
||||
mMessageTextView = (TextView) findViewById(R.id.aidl_demo_message);
|
||||
mCiphertextTextView = (TextView) findViewById(R.id.aidl_demo_ciphertext);
|
||||
mDataTextView = (TextView) findViewById(R.id.aidl_demo_data);
|
||||
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
|
||||
mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext);
|
||||
mEncryptUserId = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id);
|
||||
|
||||
mKeychainIntentHelper = new KeychainIntentHelper(mActivity);
|
||||
mKeychainData = new KeychainData();
|
||||
|
||||
bindService(new Intent(IKeychainApiService.class.getName()), svcConn,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
selectCryptoProvider();
|
||||
}
|
||||
|
||||
public void registerCryptoProvider(View view) {
|
||||
try {
|
||||
startActivityForResult(Intent.createChooser(new Intent("com.android.crypto.REGISTER"),
|
||||
"select crypto provider"), 123);
|
||||
} 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!");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Callback from remote crypto service
|
||||
*/
|
||||
final ICryptoCallback.Stub encryptCallback = new ICryptoCallback.Stub() {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == 123) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
String packageName = data.getStringExtra("packageName");
|
||||
Log.d(Constants.TAG, "packageName: " + packageName);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(final byte[] outputBytes, CryptoSignatureResult signatureResult)
|
||||
throws RemoteException {
|
||||
Log.d(Constants.TAG, "onEncryptSignSuccess");
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mCiphertext.setText(new String(outputBytes));
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data,
|
||||
// mKeychainData);
|
||||
// if (result) {
|
||||
// updateView();
|
||||
// }
|
||||
@Override
|
||||
public void onError(CryptoError error) throws RemoteException {
|
||||
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
|
||||
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) {
|
||||
byte[] inputBytes = mMessageTextView.getText().toString().getBytes();
|
||||
byte[] inputBytes = mMessage.getText().toString().getBytes();
|
||||
|
||||
try {
|
||||
service.encryptAsymmetric(inputBytes, null, true, 0, mKeychainData.getPublicKeys(), 7,
|
||||
encryptHandler);
|
||||
mCryptoServiceConnection.getService().encrypt(inputBytes,
|
||||
new String[] { mEncryptUserId.getText().toString() }, encryptCallback);
|
||||
} 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) {
|
||||
byte[] inputBytes = mCiphertextTextView.getText().toString().getBytes();
|
||||
byte[] inputBytes = mCiphertext.getText().toString().getBytes();
|
||||
|
||||
try {
|
||||
service.decryptAndVerifyAsymmetric(inputBytes, null, null, decryptHandler);
|
||||
mCryptoServiceConnection.getService().decryptAndVerify(inputBytes, decryptCallback);
|
||||
} 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
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
unbindService(svcConn);
|
||||
if (mCryptoServiceConnection != null) {
|
||||
mCryptoServiceConnection.unbindFromService();
|
||||
}
|
||||
}
|
||||
|
||||
private void exceptionImplementation(int exceptionId, String error) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Exception!").setMessage(error).setPositiveButton("OK", null).show();
|
||||
private static class CryptoProviderElement {
|
||||
private String packageName;
|
||||
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
|
||||
public void onException(final int exceptionId, final String message) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
exceptionImplementation(exceptionId, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
final ArrayList<CryptoProviderElement> providerList = new ArrayList<CryptoProviderElement>();
|
||||
|
||||
@Override
|
||||
public void onSuccess(final byte[] outputBytes, String outputUri) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
mKeychainData.setEncryptedData(new String(outputBytes));
|
||||
updateView();
|
||||
}
|
||||
});
|
||||
}
|
||||
List<ResolveInfo> resInfo = getPackageManager().queryIntentServices(intent, 0);
|
||||
if (!resInfo.isEmpty()) {
|
||||
for (ResolveInfo resolveInfo : resInfo) {
|
||||
if (resolveInfo.serviceInfo == null)
|
||||
continue;
|
||||
|
||||
};
|
||||
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
|
||||
public void onException(final int exceptionId, final String message) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
exceptionImplementation(exceptionId, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!providerList.isEmpty()) {
|
||||
|
||||
@Override
|
||||
public void onSuccess(final byte[] outputBytes, String outputUri, boolean signature,
|
||||
long signatureKeyId, String signatureUserId, boolean signatureSuccess,
|
||||
boolean signatureUnknown) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
mKeychainData.setDecryptedData(new String(outputBytes));
|
||||
updateView();
|
||||
// Init ArrayAdapter with Crypto Providers
|
||||
ListAdapter adapter = new ArrayAdapter<CryptoProviderElement>(this,
|
||||
android.R.layout.select_dialog_item, android.R.id.text1, providerList) {
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// User super class to create the View
|
||||
View v = super.getView(position, convertView, parent);
|
||||
TextView tv = (TextView) v.findViewById(android.R.id.text1);
|
||||
|
||||
// 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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -67,24 +67,27 @@
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
|
||||
|
||||
<permission-group
|
||||
android:name="org.sufficientlysecure.keychain.permission-group.keychain"
|
||||
android:description="@string/permission_group_description"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/permission_group_label" />
|
||||
<!-- TODO: disabled, old API -->
|
||||
<!-- <permission-group -->
|
||||
<!-- android:name="org.sufficientlysecure.keychain.permission-group.keychain" -->
|
||||
<!-- android:description="@string/permission_group_description" -->
|
||||
<!-- 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! -->
|
||||
<application
|
||||
@ -253,6 +256,7 @@
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<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" />
|
||||
@ -411,82 +415,92 @@
|
||||
android:exported="false"
|
||||
android:process=":passphrase_cache" />
|
||||
<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
|
||||
android:name="api_version"
|
||||
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>
|
||||
<!-- TODO: disabled, old API! -->
|
||||
<!-- <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
|
||||
android:name="api_version"
|
||||
android:value="3" />
|
||||
</service>
|
||||
|
||||
<!-- <meta-data -->
|
||||
<!-- android:name="api_version" -->
|
||||
<!-- 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
|
||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal"
|
||||
android:authorities="org.sufficientlysecure.keychain.internal"
|
||||
android:exported="false" />
|
||||
<provider
|
||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal"
|
||||
android:authorities="org.sufficientlysecure.keychain"
|
||||
android:exported="true"
|
||||
android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
|
||||
<!-- TODO: disabled, old API -->
|
||||
<!-- <provider -->
|
||||
<!-- android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" -->
|
||||
<!-- android:authorities="org.sufficientlysecure.keychain" -->
|
||||
<!-- android:exported="true" -->
|
||||
<!-- android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
|
||||
|
||||
|
||||
<!-- TODO: authority! -->
|
||||
<provider
|
||||
android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider"
|
||||
android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider"
|
||||
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
|
||||
<!-- <provider -->
|
||||
<!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
|
||||
<!-- android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" -->
|
||||
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
|
||||
|
||||
|
||||
<!-- Remote API internal intents -->
|
||||
|
||||
<!-- Crypto Provider other intents -->
|
||||
<activity
|
||||
android:name=".crypto_provider.CryptoActivity"
|
||||
android:label="TODO crypto activity"
|
||||
android:name="org.sufficientlysecure.keychain.remote_api.CryptoServiceActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:process=":crypto" >
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!-- Don't publish intents, they are only used internally! -->
|
||||
</activity>
|
||||
|
||||
<!-- Crypto Provider API -->
|
||||
<activity
|
||||
android:name=".crypto_provider.RegisterActivity"
|
||||
android:label="TODO reg"
|
||||
android:process=":crypto" >
|
||||
<intent-filter>
|
||||
<action android:name="com.android.crypto.REGISTER" />
|
||||
android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:exported="false"
|
||||
android:label="@string/title_crypto_consumers" />
|
||||
<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" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Remote API -->
|
||||
|
||||
<service
|
||||
android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService"
|
||||
android:name="org.sufficientlysecure.keychain.remote_api.CryptoService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=":crypto" >
|
||||
<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>
|
||||
|
||||
<meta-data
|
||||
|
BIN
OpenPGP-Keychain/res/drawable-hdpi/ic_action_cancel.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-hdpi/ic_action_cancel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
OpenPGP-Keychain/res/drawable-hdpi/ic_action_done.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-hdpi/ic_action_done.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
OpenPGP-Keychain/res/drawable-mdpi/ic_action_cancel.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-mdpi/ic_action_cancel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
OpenPGP-Keychain/res/drawable-mdpi/ic_action_done.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-mdpi/ic_action_done.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
OpenPGP-Keychain/res/drawable-xhdpi/ic_action_cancel.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-xhdpi/ic_action_cancel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
OpenPGP-Keychain/res/drawable-xhdpi/ic_action_done.png
Normal file
BIN
OpenPGP-Keychain/res/drawable-xhdpi/ic_action_done.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
27
OpenPGP-Keychain/res/layout/actionbar_custom_view_done.xml
Normal file
27
OpenPGP-Keychain/res/layout/actionbar_custom_view_done.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
24
OpenPGP-Keychain/res/layout/api_app_register_activity.xml
Normal file
24
OpenPGP-Keychain/res/layout/api_app_register_activity.xml
Normal 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>
|
16
OpenPGP-Keychain/res/layout/api_app_settings_activity.xml
Normal file
16
OpenPGP-Keychain/res/layout/api_app_settings_activity.xml
Normal 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>
|
96
OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml
Normal file
96
OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml
Normal 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>
|
28
OpenPGP-Keychain/res/layout/api_apps_adapter_list_item.xml
Normal file
28
OpenPGP-Keychain/res/layout/api_apps_adapter_list_item.xml
Normal 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>
|
12
OpenPGP-Keychain/res/layout/api_apps_list_activity.xml
Normal file
12
OpenPGP-Keychain/res/layout/api_apps_list_activity.xml
Normal 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>
|
@ -22,7 +22,6 @@
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
@ -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>
|
13
OpenPGP-Keychain/res/menu/api_app_settings.xml
Normal file
13
OpenPGP-Keychain/res/menu/api_app_settings.xml
Normal 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>
|
@ -30,6 +30,7 @@
|
||||
<string name="title_createKey">Create Key</string>
|
||||
<string name="title_editKey">Edit Key</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_changePassPhrase">Change Passphrase</string>
|
||||
<string name="title_setPassPhrase">Set Passphrase</string>
|
||||
@ -87,6 +88,7 @@
|
||||
<string name="menu_managePublicKeys">Manage Public Keys</string>
|
||||
<string name="menu_manageSecretKeys">Manage Secret Keys</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_importFromQrCode">Import from QR Code</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_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>
|
||||
|
20
OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl
Normal file
20
OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl
Normal 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;
|
76
OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java
Normal file
76
OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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];
|
||||
}
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -62,6 +62,7 @@ public final class Id {
|
||||
public static final int import_from_file = 0x21070020;
|
||||
public static final int import_from_qr_code = 0x21070021;
|
||||
public static final int import_from_nfc = 0x21070022;
|
||||
public static final int crypto_consumers = 0x21070023;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -120,28 +120,33 @@ public class OtherHelper {
|
||||
public static void checkPackagePermissionForActions(Activity activity, String pkgName,
|
||||
String permName, String action, String[] restrictedActions) {
|
||||
if (action != null) {
|
||||
PackageManager pkgManager = activity.getPackageManager();
|
||||
// PackageManager pkgManager = activity.getPackageManager();
|
||||
|
||||
for (int i = 0; i < restrictedActions.length; i++) {
|
||||
if (restrictedActions[i].equals(action)) {
|
||||
if (pkgName != null
|
||||
&& (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
|
||||
.equals(Constants.PACKAGE_NAME))) {
|
||||
Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
|
||||
+ action + " was granted!");
|
||||
} else {
|
||||
String error = pkgName + " does NOT have permission " + permName
|
||||
+ ". Action " + action + " was NOT granted!";
|
||||
Log.e(Constants.TAG, error);
|
||||
Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
// end activity
|
||||
activity.setResult(Activity.RESULT_CANCELED, null);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
// for (int i = 0; i < restrictedActions.length; i++) {
|
||||
// if (restrictedActions[i].equals(action)) {
|
||||
// if (pkgName != null
|
||||
// && (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
|
||||
// .equals(Constants.PACKAGE_NAME))) {
|
||||
// Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
|
||||
// + action + " was granted!");
|
||||
// } else {
|
||||
// String error = pkgName + " does NOT have permission " + permName
|
||||
// + ". Action " + action + " was NOT granted!";
|
||||
// Log.e(Constants.TAG, error);
|
||||
// Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
|
||||
// Toast.LENGTH_LONG).show();
|
||||
//
|
||||
// // end activity
|
||||
// activity.setResult(Activity.RESULT_CANCELED, null);
|
||||
// activity.finish();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: currently always cancels! THis is the old API
|
||||
// end activity
|
||||
activity.setResult(Activity.RESULT_CANCELED, null);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class KeychainContract {
|
||||
String CREATION = "creation";
|
||||
String EXPIRY = "expiry";
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
|
||||
String KEY_DATA = "key_data"; // PGPPublicKey / PGPSecretKey blob
|
||||
String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
|
||||
String RANK = "rank";
|
||||
}
|
||||
|
||||
@ -53,8 +53,13 @@ public class KeychainContract {
|
||||
String RANK = "rank";
|
||||
}
|
||||
|
||||
interface CryptoConsumersColumns {
|
||||
interface ApiAppsColumns {
|
||||
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 {
|
||||
@ -82,7 +87,8 @@ public class KeychainContract {
|
||||
public static final String PATH_USER_IDS = "user_ids";
|
||||
public static final String PATH_KEYS = "keys";
|
||||
|
||||
public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers";
|
||||
public static 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 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()
|
||||
.appendPath(BASE_CRYPTO_CONSUMERS).build();
|
||||
.appendPath(BASE_API_APPS).build();
|
||||
|
||||
/** 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 */
|
||||
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 {
|
||||
|
@ -18,7 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
||||
@ -37,7 +37,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
String KEY_RINGS = "key_rings";
|
||||
String KEYS = "keys";
|
||||
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
|
||||
@ -64,10 +64,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS "
|
||||
+ Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID
|
||||
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME
|
||||
+ " TEXT UNIQUE)";
|
||||
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS "
|
||||
+ Tables.API_APPS + " (" + BaseColumns._ID
|
||||
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME
|
||||
+ " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, "
|
||||
+ ApiAppsColumns.ASCII_ARMOR + " INTEGER, "
|
||||
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
||||
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
|
||||
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
|
||||
|
||||
KeychainDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
@ -80,7 +84,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
db.execSQL(CREATE_KEY_RINGS);
|
||||
db.execSQL(CREATE_KEYS);
|
||||
db.execSQL(CREATE_USER_IDS);
|
||||
db.execSQL(CREATE_CRYPTO_CONSUMERS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,7 +112,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
|
||||
break;
|
||||
case 4:
|
||||
db.execSQL(CREATE_CRYPTO_CONSUMERS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -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>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -17,13 +17,11 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||
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.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
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_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;
|
||||
|
||||
@ -227,9 +226,12 @@ public class KeychainProvider extends ContentProvider {
|
||||
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
|
||||
@ -290,8 +292,12 @@ public class KeychainProvider extends ContentProvider {
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
return UserIds.CONTENT_ITEM_TYPE;
|
||||
|
||||
case CRYPTO_CONSUMERS:
|
||||
return CryptoConsumers.CONTENT_TYPE;
|
||||
case API_APPS:
|
||||
return ApiApps.CONTENT_TYPE;
|
||||
|
||||
case API_APPS_BY_ROW_ID:
|
||||
case API_APPS_BY_PACKAGE_NAME:
|
||||
return ApiApps.CONTENT_ITEM_TYPE;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
@ -600,10 +606,23 @@ public class KeychainProvider extends ContentProvider {
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
break;
|
||||
|
||||
case CRYPTO_CONSUMERS:
|
||||
qb.setTables(Tables.CRYPTO_CONSUMERS);
|
||||
|
||||
|
||||
case API_APPS:
|
||||
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;
|
||||
|
||||
default:
|
||||
@ -653,6 +672,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_KEY:
|
||||
@ -660,11 +680,13 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID:
|
||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING:
|
||||
@ -672,6 +694,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING_KEY:
|
||||
@ -679,12 +702,18 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case SECRET_KEY_RING_USER_ID:
|
||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||
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;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
@ -692,7 +721,6 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
} catch (SQLiteConstraintException e) {
|
||||
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,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
break;
|
||||
case PUBLIC_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,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
break;
|
||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||
count = db.delete(Tables.KEYS,
|
||||
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
|
||||
selectionArgs);
|
||||
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:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
return count;
|
||||
}
|
||||
@ -771,6 +809,8 @@ public class KeychainProvider extends ContentProvider {
|
||||
values,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||
selection), selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||
@ -781,6 +821,8 @@ public class KeychainProvider extends ContentProvider {
|
||||
values,
|
||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||
selection), selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case PUBLIC_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,
|
||||
buildDefaultKeysSelection(uri, getKeyType(match), selection),
|
||||
selectionArgs);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
break;
|
||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||
count = db.update(Tables.USER_IDS, values,
|
||||
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
|
||||
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:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
|
||||
// notify of changes in db
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||
|
||||
} catch (SQLiteConstraintException e) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
// int match = mUriMatcher.match(uri);
|
||||
@ -899,10 +973,12 @@ public class KeychainProvider extends ContentProvider {
|
||||
* updated, or deleted
|
||||
*/
|
||||
private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
|
||||
intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
|
||||
intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
|
||||
getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
|
||||
// TODO: Disabled, old API
|
||||
// Intent intent = new Intent();
|
||||
// intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
|
||||
// intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
|
||||
// intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
|
||||
//
|
||||
// getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +31,12 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.helper.PgpMain;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.remote_api.AppSettings;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@ -718,13 +719,13 @@ public class ProviderHelper {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public static ArrayList<String> getCryptoConsumers(Context context) {
|
||||
Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null,
|
||||
null, null);
|
||||
public static ArrayList<String> getRegisteredApiApps(Context context) {
|
||||
Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
|
||||
null);
|
||||
|
||||
ArrayList<String> packageNames = new ArrayList<String>();
|
||||
if (cursor != null) {
|
||||
int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME);
|
||||
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
packageNames.add(cursor.getString(packageNameCol));
|
||||
@ -739,9 +740,53 @@ public class ProviderHelper {
|
||||
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();
|
||||
values.put(CryptoConsumers.PACKAGE_NAME, packageName);
|
||||
context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values);
|
||||
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -48,6 +48,12 @@ import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
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 static final String TAG = Constants.TAG + ": PassphraseCacheService";
|
||||
|
||||
@ -74,9 +80,9 @@ public class PassphraseCacheService extends Service {
|
||||
Context mContext;
|
||||
|
||||
/**
|
||||
* This caches a new passphrase by sending a new command to the service. An android service is
|
||||
* only run once. Thus, when the service is already started, new commands just add new events to
|
||||
* the alarm manager for new passphrases to let them timeout in the future.
|
||||
* This caches a new passphrase in memory by sending a new command to the service. An android
|
||||
* service is only run once. Thus, when the service is already started, new commands just add
|
||||
* new events to the alarm manager for new passphrases to let them timeout in the future.
|
||||
*
|
||||
* @param context
|
||||
* @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 keyId
|
||||
* @return
|
||||
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||
*/
|
||||
public static String getCachedPassphrase(Context context, long keyId) {
|
||||
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
|
||||
|
||||
final Object mutex = new Object();
|
||||
final Bundle returnBundle = new Bundle();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("getPassphrase");
|
||||
HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
|
||||
handlerThread.start();
|
||||
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
||||
@Override
|
||||
@ -121,6 +129,7 @@ public class PassphraseCacheService extends Service {
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
// quit handlerThread
|
||||
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) {
|
||||
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
|
||||
|
||||
@ -163,20 +178,20 @@ public class PassphraseCacheService extends Service {
|
||||
}
|
||||
masterKeyId = masterKey.getKeyID();
|
||||
}
|
||||
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId" + masterKeyId);
|
||||
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
|
||||
|
||||
// get cached passphrase
|
||||
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
|
||||
if (cachedPassphrase == null) {
|
||||
// TODO: fix!
|
||||
// check if secret key has a passphrase
|
||||
// if (!hasPassphrase(context, masterKeyId)) {
|
||||
// // cache empty passphrase
|
||||
// addCachedPassphrase(context, masterKeyId, "");
|
||||
// return "";
|
||||
// } else {
|
||||
return null;
|
||||
// }
|
||||
// if key has no passphrase -> cache and return empty passphrase
|
||||
if (!hasPassphrase(this, masterKeyId)) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
|
||||
|
||||
addCachedPassphrase(this, masterKeyId, "");
|
||||
return "";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(TAG, "Cache passphrase again when getting it!");
|
||||
@ -196,17 +211,10 @@ public class PassphraseCacheService extends Service {
|
||||
try {
|
||||
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
|
||||
|
||||
Log.d(Constants.TAG, "Check if key has no passphrase...");
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
"SC").build("".toCharArray());
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
if (testKey != null) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
|
||||
|
||||
// cache empty passphrase
|
||||
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
|
Binary file not shown.
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
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)
|
||||
.setIcon(R.drawable.ic_menu_settings)
|
||||
.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;
|
||||
}
|
||||
|
||||
@ -91,6 +95,10 @@ public class MainActivity extends SherlockActivity {
|
||||
startActivity(new Intent(this, PreferencesActivity.class));
|
||||
return true;
|
||||
|
||||
case Id.menu.option.crypto_consumers:
|
||||
startActivity(new Intent(this, RegisteredAppsListActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
|
@ -67,7 +67,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
|
||||
// application this would come from a resource.
|
||||
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);
|
||||
|
||||
@ -160,11 +160,12 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_ENCRYPT
|
||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
|
||||
+ now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
|
||||
+ Keys.EXPIRY + " >= '" + now + "')) AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
String inMasterKeyList = null;
|
||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
||||
|
@ -73,7 +73,7 @@ public class SelectSecretKeyFragment extends SherlockListFragment implements
|
||||
// application this would come from a resource.
|
||||
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);
|
||||
|
||||
|
@ -23,19 +23,14 @@ import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -45,11 +40,6 @@ import org.sufficientlysecure.keychain.R;
|
||||
import android.content.Context;
|
||||
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 static final String MAP_ATTR_USER_ID = "user_id";
|
||||
public static final String MAP_ATTR_FINGERPINT = "fingerprint";
|
||||
|
@ -44,9 +44,9 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
public final static String PROJECTION_ROW_AVAILABLE = "available";
|
||||
public final static String PROJECTION_ROW_VALID = "valid";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public SelectKeyCursorAdapter(Context context, ListView listView, Cursor c, int keyType) {
|
||||
super(context, c);
|
||||
public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
|
||||
int keyType) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mListView = listView;
|
||||
@ -65,8 +65,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
boolean valid = cursor.getInt(cursor
|
||||
.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
|
||||
boolean valid = cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknownUserId);
|
||||
@ -101,8 +100,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
status.setText(R.string.canSign);
|
||||
}
|
||||
} else {
|
||||
if (cursor.getInt(cursor
|
||||
.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
|
||||
if (cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
|
||||
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
|
||||
// expired
|
||||
status.setText(R.string.expired);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
66
README.md
66
README.md
@ -68,72 +68,6 @@ See http://docs.oseems.com/general/application/eclipse/fix-gc-overhead-limit-exc
|
||||
1. Open svg file in Inkscape
|
||||
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
|
||||
|
||||
## Code
|
||||
|
Loading…
Reference in New Issue
Block a user