mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-27 19:22:14 -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:layout_height="match_parent"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/crypto_provider_demo_register"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="registerCryptoProvider"
|
android:text="Encrypt User Id"
|
||||||
android:text="Register crypto provider" />
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/aidl_demo_select_secret_key"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:onClick="selectSecretKeyOnClick"
|
|
||||||
android:text="Select secret key" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/aidl_demo_select_encryption_key"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:onClick="selectEncryptionKeysOnClick"
|
|
||||||
android:text="Select encryption key(s)" />
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/aidl_demo_message"
|
android:id="@+id/crypto_provider_demo_encrypt_user_id"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="dominik@dominikschuermann.de"
|
||||||
|
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Message"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/crypto_provider_demo_message"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="150dip"
|
android:layout_height="150dip"
|
||||||
android:text="message"
|
android:text="message"
|
||||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ciphertext"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/aidl_demo_ciphertext"
|
android:id="@+id/crypto_provider_demo_ciphertext"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="150dip"
|
android:layout_height="150dip"
|
||||||
android:text="ciphertext"
|
android:text="ciphertext"
|
||||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/aidl_demo_encrypt"
|
android:id="@+id/crypto_provider_demo_encrypt"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="encryptOnClick"
|
android:onClick="encryptOnClick"
|
||||||
android:text="Encrypt" />
|
android:text="Encrypt" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/aidl_demo_decrypt"
|
android:id="@+id/crypto_provider_demo_sign"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="signOnClick"
|
||||||
|
android:text="Sign" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/crypto_provider_demo_encrypt_and_sign"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="encryptAndSignOnClick"
|
||||||
|
android:text="Encrypt and Sign" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/crypto_provider_demo_decrypt"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="decryptOnClick"
|
android:onClick="decryptOnClick"
|
||||||
android:text="Decrypt" />
|
android:text="Decrypt" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:text="APG Data:" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/aidl_demo_data"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:minLines="10" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
|
// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
|
||||||
parcelable CryptoError;
|
parcelable CryptoError;
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
@ -1,4 +1,22 @@
|
|||||||
package com.android.crypto;
|
/*
|
||||||
|
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openintents.crypto;
|
||||||
|
|
||||||
|
import org.openintents.crypto.ICryptoService;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -50,8 +68,8 @@ public class CryptoServiceConnection {
|
|||||||
Log.d(TAG, "not bound yet");
|
Log.d(TAG, "not bound yet");
|
||||||
|
|
||||||
Intent serviceIntent = new Intent();
|
Intent serviceIntent = new Intent();
|
||||||
serviceIntent.setAction("com.android.crypto.ICryptoService");
|
serviceIntent.setAction("org.openintents.crypto.ICryptoService");
|
||||||
serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test
|
serviceIntent.setPackage(cryptoProviderPackageName);
|
||||||
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
|
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
|
||||||
Context.BIND_AUTO_CREATE);
|
Context.BIND_AUTO_CREATE);
|
||||||
|
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
|
// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
|
||||||
parcelable CryptoSignatureResult;
|
parcelable CryptoSignatureResult;
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
@ -14,19 +14,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
import com.android.crypto.CryptoSignatureResult;
|
import org.openintents.crypto.CryptoSignatureResult;
|
||||||
import com.android.crypto.CryptoError;
|
import org.openintents.crypto.CryptoError;
|
||||||
|
|
||||||
interface ICryptoCallback {
|
interface ICryptoCallback {
|
||||||
|
|
||||||
oneway void onEncryptSignSuccess(in byte[] outputBytes);
|
/**
|
||||||
|
* CryptoSignatureResult is only returned if the Callback was used from decryptAndVerify
|
||||||
oneway void onDecryptVerifySuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
|
*
|
||||||
|
*/
|
||||||
|
oneway void onSuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
|
||||||
|
|
||||||
|
|
||||||
oneway void onError(in CryptoError error);
|
oneway void onError(in CryptoError error);
|
||||||
|
|
||||||
oneway void onActivityRequired(in Intent intent);
|
|
||||||
}
|
}
|
@ -14,9 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.crypto;
|
package org.openintents.crypto;
|
||||||
|
|
||||||
import com.android.crypto.ICryptoCallback;
|
import org.openintents.crypto.ICryptoCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All methods are oneway, which means they are asynchronous and non-blocking.
|
* All methods are oneway, which means they are asynchronous and non-blocking.
|
||||||
@ -29,40 +29,38 @@ interface ICryptoService {
|
|||||||
*
|
*
|
||||||
* @param inputBytes
|
* @param inputBytes
|
||||||
* Byte array you want to encrypt
|
* Byte array you want to encrypt
|
||||||
* @param encryptionKeyIds
|
* @param encryptionUserIds
|
||||||
* Ids of public keys used for encryption
|
* User Ids (emails) of recipients
|
||||||
* @param handler
|
* @param callback
|
||||||
* Results are returned to this Handler after successful encryption
|
* Callback where to return results
|
||||||
*/
|
*/
|
||||||
oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback);
|
oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback);
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt and sign
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param inputBytes
|
|
||||||
* Byte array you want to encrypt
|
|
||||||
* @param signatureKeyId
|
|
||||||
* Key id of key to sign with
|
|
||||||
* @param handler
|
|
||||||
* Results are returned to this Handler after successful encryption and signing
|
|
||||||
*/
|
|
||||||
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign
|
* Sign
|
||||||
*
|
*
|
||||||
*
|
* @param inputBytes
|
||||||
|
* 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
|
* @param inputBytes
|
||||||
* Byte array you want to encrypt
|
* Byte array you want to encrypt
|
||||||
* @param signatureId
|
* @param encryptionUserIds
|
||||||
*
|
* User Ids (emails) of recipients
|
||||||
* @param handler
|
* @param signatureUserId
|
||||||
* Results are returned to this Handler after successful encryption and signing
|
* User Ids (email) of sender
|
||||||
|
* @param callback
|
||||||
|
* Callback where to return results
|
||||||
*/
|
*/
|
||||||
oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback);
|
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts and verifies given input bytes. If no signature is present this method
|
* Decrypts and verifies given input bytes. If no signature is present this method
|
||||||
@ -70,9 +68,15 @@ interface ICryptoService {
|
|||||||
*
|
*
|
||||||
* @param inputBytes
|
* @param inputBytes
|
||||||
* Byte array you want to decrypt and verify
|
* Byte array you want to decrypt and verify
|
||||||
* @param handler
|
* @param callback
|
||||||
* Handler where to return results to after successful encryption
|
* Callback where to return results
|
||||||
*/
|
*/
|
||||||
oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
|
oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens setup using default parameters
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
oneway void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId);
|
||||||
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,51 +16,42 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.demo;
|
package org.sufficientlysecure.keychain.demo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.openintents.crypto.CryptoError;
|
||||||
|
import org.openintents.crypto.CryptoServiceConnection;
|
||||||
|
import org.openintents.crypto.CryptoSignatureResult;
|
||||||
|
import org.openintents.crypto.ICryptoCallback;
|
||||||
|
import org.openintents.crypto.ICryptoService;
|
||||||
import org.sufficientlysecure.keychain.demo.R;
|
import org.sufficientlysecure.keychain.demo.R;
|
||||||
import org.sufficientlysecure.keychain.integration.Constants;
|
import org.sufficientlysecure.keychain.integration.Constants;
|
||||||
import org.sufficientlysecure.keychain.integration.KeychainData;
|
|
||||||
import org.sufficientlysecure.keychain.integration.KeychainIntentHelper;
|
|
||||||
import org.sufficientlysecure.keychain.service.IKeychainApiService;
|
|
||||||
import org.sufficientlysecure.keychain.service.IKeychainKeyService;
|
|
||||||
import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler;
|
|
||||||
import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler;
|
|
||||||
import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.DialogInterface;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
public class CryptoProviderDemoActivity extends Activity {
|
public class CryptoProviderDemoActivity extends Activity {
|
||||||
Activity mActivity;
|
Activity mActivity;
|
||||||
|
|
||||||
TextView mMessageTextView;
|
EditText mMessage;
|
||||||
TextView mCiphertextTextView;
|
EditText mCiphertext;
|
||||||
TextView mDataTextView;
|
EditText mEncryptUserId;
|
||||||
|
EditText mSignUserId;
|
||||||
|
|
||||||
KeychainIntentHelper mKeychainIntentHelper;
|
private CryptoServiceConnection mCryptoServiceConnection;
|
||||||
KeychainData mKeychainData;
|
|
||||||
|
|
||||||
private IKeychainApiService service = null;
|
|
||||||
private ServiceConnection svcConn = new ServiceConnection() {
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
|
||||||
service = IKeychainApiService.Stub.asInterface(binder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
service = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
@ -69,170 +60,208 @@ public class CryptoProviderDemoActivity extends Activity {
|
|||||||
|
|
||||||
mActivity = this;
|
mActivity = this;
|
||||||
|
|
||||||
mMessageTextView = (TextView) findViewById(R.id.aidl_demo_message);
|
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
|
||||||
mCiphertextTextView = (TextView) findViewById(R.id.aidl_demo_ciphertext);
|
mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext);
|
||||||
mDataTextView = (TextView) findViewById(R.id.aidl_demo_data);
|
mEncryptUserId = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id);
|
||||||
|
|
||||||
mKeychainIntentHelper = new KeychainIntentHelper(mActivity);
|
selectCryptoProvider();
|
||||||
mKeychainData = new KeychainData();
|
|
||||||
|
|
||||||
bindService(new Intent(IKeychainApiService.class.getName()), svcConn,
|
|
||||||
Context.BIND_AUTO_CREATE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerCryptoProvider(View view) {
|
/**
|
||||||
try {
|
* Callback from remote crypto service
|
||||||
startActivityForResult(Intent.createChooser(new Intent("com.android.crypto.REGISTER"),
|
*/
|
||||||
"select crypto provider"), 123);
|
final ICryptoCallback.Stub encryptCallback = new ICryptoCallback.Stub() {
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
Toast.makeText(mActivity, "No app that handles com.android.crypto.REGISTER!",
|
@Override
|
||||||
Toast.LENGTH_LONG).show();
|
public void onSuccess(final byte[] outputBytes, CryptoSignatureResult signatureResult)
|
||||||
Log.e(Constants.TAG, "No app that handles com.android.crypto.REGISTER!");
|
throws RemoteException {
|
||||||
|
Log.d(Constants.TAG, "onEncryptSignSuccess");
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mCiphertext.setText(new String(outputBytes));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onError(CryptoError error) throws RemoteException {
|
||||||
if (requestCode == 123) {
|
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
|
||||||
if (resultCode == RESULT_OK) {
|
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
|
||||||
String packageName = data.getStringExtra("packageName");
|
|
||||||
Log.d(Constants.TAG, "packageName: " + packageName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data,
|
};
|
||||||
// mKeychainData);
|
|
||||||
// if (result) {
|
final ICryptoCallback.Stub decryptCallback = new ICryptoCallback.Stub() {
|
||||||
// updateView();
|
|
||||||
// }
|
@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());
|
||||||
|
|
||||||
// continue with other activity results
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(CryptoError error) throws RemoteException {
|
||||||
|
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
|
||||||
|
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
public void encryptOnClick(View view) {
|
public void encryptOnClick(View view) {
|
||||||
byte[] inputBytes = mMessageTextView.getText().toString().getBytes();
|
byte[] inputBytes = mMessage.getText().toString().getBytes();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.encryptAsymmetric(inputBytes, null, true, 0, mKeychainData.getPublicKeys(), 7,
|
mCryptoServiceConnection.getService().encrypt(inputBytes,
|
||||||
encryptHandler);
|
new String[] { mEncryptUserId.getText().toString() }, encryptCallback);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
exceptionImplementation(-1, e.toString());
|
Log.e(Constants.TAG, "CryptoProviderDemo", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void signOnClick(View view) {
|
||||||
|
byte[] inputBytes = mMessage.getText().toString().getBytes();
|
||||||
|
|
||||||
|
try {
|
||||||
|
mCryptoServiceConnection.getService().sign(inputBytes,
|
||||||
|
mSignUserId.getText().toString(), encryptCallback);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(Constants.TAG, "CryptoProviderDemo", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encryptAndSignOnClick(View view) {
|
||||||
|
byte[] inputBytes = mMessage.getText().toString().getBytes();
|
||||||
|
|
||||||
|
try {
|
||||||
|
mCryptoServiceConnection.getService().encryptAndSign(inputBytes,
|
||||||
|
new String[] { mEncryptUserId.getText().toString() },
|
||||||
|
mSignUserId.getText().toString(), encryptCallback);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(Constants.TAG, "CryptoProviderDemo", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decryptOnClick(View view) {
|
public void decryptOnClick(View view) {
|
||||||
byte[] inputBytes = mCiphertextTextView.getText().toString().getBytes();
|
byte[] inputBytes = mCiphertext.getText().toString().getBytes();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.decryptAndVerifyAsymmetric(inputBytes, null, null, decryptHandler);
|
mCryptoServiceConnection.getService().decryptAndVerify(inputBytes, decryptCallback);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
exceptionImplementation(-1, e.toString());
|
Log.e(Constants.TAG, "CryptoProviderDemo", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateView() {
|
|
||||||
if (mKeychainData.getDecryptedData() != null) {
|
|
||||||
mMessageTextView.setText(mKeychainData.getDecryptedData());
|
|
||||||
}
|
|
||||||
if (mKeychainData.getEncryptedData() != null) {
|
|
||||||
mCiphertextTextView.setText(mKeychainData.getEncryptedData());
|
|
||||||
}
|
|
||||||
mDataTextView.setText(mKeychainData.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
unbindService(svcConn);
|
if (mCryptoServiceConnection != null) {
|
||||||
|
mCryptoServiceConnection.unbindFromService();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exceptionImplementation(int exceptionId, String error) {
|
private static class CryptoProviderElement {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
private String packageName;
|
||||||
builder.setTitle("Exception!").setMessage(error).setPositiveButton("OK", null).show();
|
private String simpleName;
|
||||||
}
|
private Drawable icon;
|
||||||
|
|
||||||
private final IKeychainEncryptHandler.Stub encryptHandler = new IKeychainEncryptHandler.Stub() {
|
public CryptoProviderElement(String packageName, String simpleName, Drawable icon) {
|
||||||
|
this.packageName = packageName;
|
||||||
@Override
|
this.simpleName = simpleName;
|
||||||
public void onException(final int exceptionId, final String message) throws RemoteException {
|
this.icon = icon;
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
exceptionImplementation(exceptionId, message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final byte[] outputBytes, String outputUri) throws RemoteException {
|
public String toString() {
|
||||||
runOnUiThread(new Runnable() {
|
return simpleName;
|
||||||
public void run() {
|
|
||||||
mKeychainData.setEncryptedData(new String(outputBytes));
|
|
||||||
updateView();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void selectCryptoProvider() {
|
||||||
|
Intent intent = new Intent(ICryptoService.class.getName());
|
||||||
|
|
||||||
|
final ArrayList<CryptoProviderElement> providerList = new ArrayList<CryptoProviderElement>();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
alert.setTitle("Select Crypto Provider!");
|
||||||
|
alert.setCancelable(false);
|
||||||
|
|
||||||
|
if (!providerList.isEmpty()) {
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final IKeychainDecryptHandler.Stub decryptHandler = new IKeychainDecryptHandler.Stub() {
|
alert.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
@Override
|
public void onClick(DialogInterface dialog, int position) {
|
||||||
public void onException(final int exceptionId, final String message) throws RemoteException {
|
String packageName = providerList.get(position).packageName;
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
// bind to service
|
||||||
exceptionImplementation(exceptionId, message);
|
mCryptoServiceConnection = new CryptoServiceConnection(
|
||||||
|
CryptoProviderDemoActivity.this, packageName);
|
||||||
|
mCryptoServiceConnection.bindToService();
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
alert.setMessage("No Crypto Provider installed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
public void onSuccess(final byte[] outputBytes, String outputUri, boolean signature,
|
|
||||||
long signatureKeyId, String signatureUserId, boolean signatureSuccess,
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
boolean signatureUnknown) throws RemoteException {
|
dialog.cancel();
|
||||||
runOnUiThread(new Runnable() {
|
finish();
|
||||||
public void run() {
|
|
||||||
mKeychainData.setDecryptedData(new String(outputBytes));
|
|
||||||
updateView();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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="android.permission.NFC" />
|
||||||
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
|
<uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" />
|
||||||
|
|
||||||
<permission-group
|
<!-- TODO: disabled, old API -->
|
||||||
android:name="org.sufficientlysecure.keychain.permission-group.keychain"
|
<!-- <permission-group -->
|
||||||
android:description="@string/permission_group_description"
|
<!-- android:name="org.sufficientlysecure.keychain.permission-group.keychain" -->
|
||||||
android:icon="@drawable/icon"
|
<!-- android:description="@string/permission_group_description" -->
|
||||||
android:label="@string/permission_group_label" />
|
<!-- android:icon="@drawable/icon" -->
|
||||||
|
<!-- android:label="@string/permission_group_label" /> -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <permission -->
|
||||||
|
<!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" -->
|
||||||
|
<!-- android:description="@string/permission_access_keys_description" -->
|
||||||
|
<!-- android:label="@string/permission_access_keys_label" -->
|
||||||
|
<!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" -->
|
||||||
|
<!-- android:protectionLevel="dangerous" /> -->
|
||||||
|
<!-- <permission -->
|
||||||
|
<!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_API" -->
|
||||||
|
<!-- android:description="@string/permission_access_api_description" -->
|
||||||
|
<!-- android:label="@string/permission_access_api_label" -->
|
||||||
|
<!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" -->
|
||||||
|
<!-- android:protectionLevel="dangerous" /> -->
|
||||||
|
|
||||||
<permission
|
|
||||||
android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS"
|
|
||||||
android:description="@string/permission_access_keys_description"
|
|
||||||
android:label="@string/permission_access_keys_label"
|
|
||||||
android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain"
|
|
||||||
android:protectionLevel="dangerous" />
|
|
||||||
<permission
|
|
||||||
android:name="org.sufficientlysecure.keychain.permission.ACCESS_API"
|
|
||||||
android:description="@string/permission_access_api_description"
|
|
||||||
android:label="@string/permission_access_api_label"
|
|
||||||
android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain"
|
|
||||||
android:protectionLevel="dangerous" />
|
|
||||||
|
|
||||||
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
|
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
|
||||||
<application
|
<application
|
||||||
@ -253,6 +256,7 @@
|
|||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
|
<!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg -->
|
||||||
<data android:pathPattern=".*\\.gpg" />
|
<data android:pathPattern=".*\\.gpg" />
|
||||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||||
@ -411,82 +415,92 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":passphrase_cache" />
|
android:process=":passphrase_cache" />
|
||||||
<service android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" />
|
<service android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" />
|
||||||
<service
|
|
||||||
android:name="org.sufficientlysecure.keychain.service.KeychainApiService"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API"
|
|
||||||
android:process=":remoteapi" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
<!-- TODO: disabled, old API! -->
|
||||||
android:name="api_version"
|
<!-- <service -->
|
||||||
android:value="3" />
|
<!-- android:name="org.sufficientlysecure.keychain.service.KeychainApiService" -->
|
||||||
</service>
|
<!-- android:enabled="true" -->
|
||||||
<service
|
<!-- android:exported="true" -->
|
||||||
android:name="org.sufficientlysecure.keychain.service.KeychainKeyService"
|
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" -->
|
||||||
android:enabled="true"
|
<!-- android:process=":remoteapi" > -->
|
||||||
android:exported="true"
|
<!-- <intent-filter> -->
|
||||||
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS"
|
<!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" /> -->
|
||||||
android:process=":remotekeys" >
|
<!-- </intent-filter> -->
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="api_version"
|
<!-- <meta-data -->
|
||||||
android:value="3" />
|
<!-- android:name="api_version" -->
|
||||||
</service>
|
<!-- android:value="3" /> -->
|
||||||
|
<!-- </service> -->
|
||||||
|
<!-- <service -->
|
||||||
|
<!-- android:name="org.sufficientlysecure.keychain.service.KeychainKeyService" -->
|
||||||
|
<!-- android:enabled="true" -->
|
||||||
|
<!-- android:exported="true" -->
|
||||||
|
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" -->
|
||||||
|
<!-- android:process=":remotekeys" > -->
|
||||||
|
<!-- <intent-filter> -->
|
||||||
|
<!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" /> -->
|
||||||
|
<!-- </intent-filter> -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <meta-data -->
|
||||||
|
<!-- android:name="api_version" -->
|
||||||
|
<!-- android:value="3" /> -->
|
||||||
|
<!-- </service> -->
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal"
|
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal"
|
||||||
android:authorities="org.sufficientlysecure.keychain.internal"
|
android:authorities="org.sufficientlysecure.keychain.internal"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<provider
|
<!-- TODO: disabled, old API -->
|
||||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal"
|
<!-- <provider -->
|
||||||
android:authorities="org.sufficientlysecure.keychain"
|
<!-- android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" -->
|
||||||
android:exported="true"
|
<!-- android:authorities="org.sufficientlysecure.keychain" -->
|
||||||
android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
|
<!-- android:exported="true" -->
|
||||||
|
<!-- android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
|
||||||
|
|
||||||
|
|
||||||
<!-- TODO: authority! -->
|
<!-- TODO: authority! -->
|
||||||
<provider
|
<!-- <provider -->
|
||||||
android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider"
|
<!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" -->
|
||||||
android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider"
|
<!-- android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" -->
|
||||||
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
|
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Remote API internal intents -->
|
||||||
|
|
||||||
<!-- Crypto Provider other intents -->
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".crypto_provider.CryptoActivity"
|
android:name="org.sufficientlysecure.keychain.remote_api.CryptoServiceActivity"
|
||||||
android:label="TODO crypto activity"
|
android:exported="false"
|
||||||
|
android:label="@string/app_name"
|
||||||
android:process=":crypto" >
|
android:process=":crypto" >
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<!-- Don't publish intents, they are only used internally! -->
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Crypto Provider API -->
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".crypto_provider.RegisterActivity"
|
android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity"
|
||||||
android:label="TODO reg"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:process=":crypto" >
|
android:exported="false"
|
||||||
<intent-filter>
|
android:label="@string/title_crypto_consumers" />
|
||||||
<action android:name="com.android.crypto.REGISTER" />
|
<activity
|
||||||
|
android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<!-- Remote API -->
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService"
|
android:name="org.sufficientlysecure.keychain.remote_api.CryptoService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:process=":crypto" >
|
android:process=":crypto" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.android.crypto.ICryptoService" />
|
<action android:name="org.openintents.crypto.ICryptoService" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<!-- Can only be used from OpenPGP Keychain (internal): -->
|
||||||
|
<action android:name="org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
|
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" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
@ -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_createKey">Create Key</string>
|
||||||
<string name="title_editKey">Edit Key</string>
|
<string name="title_editKey">Edit Key</string>
|
||||||
<string name="title_preferences">Preferences</string>
|
<string name="title_preferences">Preferences</string>
|
||||||
|
<string name="title_crypto_consumers">Registered Applications</string>
|
||||||
<string name="title_keyServerPreference">Key Server Preference</string>
|
<string name="title_keyServerPreference">Key Server Preference</string>
|
||||||
<string name="title_changePassPhrase">Change Passphrase</string>
|
<string name="title_changePassPhrase">Change Passphrase</string>
|
||||||
<string name="title_setPassPhrase">Set Passphrase</string>
|
<string name="title_setPassPhrase">Set Passphrase</string>
|
||||||
@ -87,6 +88,7 @@
|
|||||||
<string name="menu_managePublicKeys">Manage Public Keys</string>
|
<string name="menu_managePublicKeys">Manage Public Keys</string>
|
||||||
<string name="menu_manageSecretKeys">Manage Secret Keys</string>
|
<string name="menu_manageSecretKeys">Manage Secret Keys</string>
|
||||||
<string name="menu_preferences">Settings</string>
|
<string name="menu_preferences">Settings</string>
|
||||||
|
<string name="menu_apiAppSettings">Registered Apps</string>
|
||||||
<string name="menu_importFromFile">Import from file</string>
|
<string name="menu_importFromFile">Import from file</string>
|
||||||
<string name="menu_importFromQrCode">Import from QR Code</string>
|
<string name="menu_importFromQrCode">Import from QR Code</string>
|
||||||
<string name="menu_importFromNfc">Import from NFC</string>
|
<string name="menu_importFromNfc">Import from NFC</string>
|
||||||
@ -362,4 +364,18 @@
|
|||||||
<string name="intent_send_encrypt">OpenPGP: Encrypt</string>
|
<string name="intent_send_encrypt">OpenPGP: Encrypt</string>
|
||||||
<string name="intent_send_decrypt">OpenPGP: Decrypt</string>
|
<string name="intent_send_decrypt">OpenPGP: Decrypt</string>
|
||||||
|
|
||||||
|
<!-- Remote API -->
|
||||||
|
<string name="api_no_apps">No registered applications!</string>
|
||||||
|
<string name="api_settings_show_advanced">Show advanced settings…</string>
|
||||||
|
<string name="api_settings_hide_advanced">Hide advanced settings…</string>
|
||||||
|
<string name="api_settings_no_key">No key selected</string>
|
||||||
|
<string name="api_settings_select_key">Select key</string>
|
||||||
|
<string name="api_settings_save">Save</string>
|
||||||
|
<string name="api_settings_cancel">Cancel</string>
|
||||||
|
<string name="api_settings_revoke">Revoke access</string>
|
||||||
|
<string name="api_register_text">The following application requests access to OpenPGP Keychain\'s API.\n\nAllow permanent access?</string>
|
||||||
|
<string name="api_register_allow">Allow access</string>
|
||||||
|
<string name="api_register_disallow">Disallow access</string>
|
||||||
|
<string name="api_register_error_select_key">Please select a key!</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
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_file = 0x21070020;
|
||||||
public static final int import_from_qr_code = 0x21070021;
|
public static final int import_from_qr_code = 0x21070021;
|
||||||
public static final int import_from_nfc = 0x21070022;
|
public static final int import_from_nfc = 0x21070022;
|
||||||
|
public static final int crypto_consumers = 0x21070023;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,30 +120,35 @@ public class OtherHelper {
|
|||||||
public static void checkPackagePermissionForActions(Activity activity, String pkgName,
|
public static void checkPackagePermissionForActions(Activity activity, String pkgName,
|
||||||
String permName, String action, String[] restrictedActions) {
|
String permName, String action, String[] restrictedActions) {
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
PackageManager pkgManager = activity.getPackageManager();
|
// PackageManager pkgManager = activity.getPackageManager();
|
||||||
|
|
||||||
for (int i = 0; i < restrictedActions.length; i++) {
|
// for (int i = 0; i < restrictedActions.length; i++) {
|
||||||
if (restrictedActions[i].equals(action)) {
|
// if (restrictedActions[i].equals(action)) {
|
||||||
if (pkgName != null
|
// if (pkgName != null
|
||||||
&& (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
|
// && (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
|
||||||
.equals(Constants.PACKAGE_NAME))) {
|
// .equals(Constants.PACKAGE_NAME))) {
|
||||||
Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
|
// Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
|
||||||
+ action + " was granted!");
|
// + action + " was granted!");
|
||||||
} else {
|
// } else {
|
||||||
String error = pkgName + " does NOT have permission " + permName
|
// String error = pkgName + " does NOT have permission " + permName
|
||||||
+ ". Action " + action + " was NOT granted!";
|
// + ". Action " + action + " was NOT granted!";
|
||||||
Log.e(Constants.TAG, error);
|
// Log.e(Constants.TAG, error);
|
||||||
Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
|
// Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
|
||||||
Toast.LENGTH_LONG).show();
|
// Toast.LENGTH_LONG).show();
|
||||||
|
//
|
||||||
|
// // end activity
|
||||||
|
// activity.setResult(Activity.RESULT_CANCELED, null);
|
||||||
|
// activity.finish();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: currently always cancels! THis is the old API
|
||||||
// end activity
|
// end activity
|
||||||
activity.setResult(Activity.RESULT_CANCELED, null);
|
activity.setResult(Activity.RESULT_CANCELED, null);
|
||||||
activity.finish();
|
activity.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits userId string into naming part and email part
|
* Splits userId string into naming part and email part
|
||||||
|
@ -53,8 +53,13 @@ public class KeychainContract {
|
|||||||
String RANK = "rank";
|
String RANK = "rank";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CryptoConsumersColumns {
|
interface ApiAppsColumns {
|
||||||
String PACKAGE_NAME = "package_name";
|
String PACKAGE_NAME = "package_name";
|
||||||
|
String KEY_ID = "key_id"; // not a database id
|
||||||
|
String ASCII_ARMOR = "ascii_armor";
|
||||||
|
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
|
||||||
|
String HASH_ALORITHM = "hash_algorithm";
|
||||||
|
String COMPRESSION = "compression";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class KeyTypes {
|
public static final class KeyTypes {
|
||||||
@ -82,7 +87,8 @@ public class KeychainContract {
|
|||||||
public static final String PATH_USER_IDS = "user_ids";
|
public static final String PATH_USER_IDS = "user_ids";
|
||||||
public static final String PATH_KEYS = "keys";
|
public static final String PATH_KEYS = "keys";
|
||||||
|
|
||||||
public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers";
|
public static final String BASE_API_APPS = "api_apps";
|
||||||
|
public static final String PATH_BY_PACKAGE_NAME = "package_name";
|
||||||
|
|
||||||
public static class KeyRings implements KeyRingsColumns, BaseColumns {
|
public static class KeyRings implements KeyRingsColumns, BaseColumns {
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
@ -213,15 +219,24 @@ public class KeychainContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CryptoConsumers implements CryptoConsumersColumns, BaseColumns {
|
public static class ApiApps implements ApiAppsColumns, BaseColumns {
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
.appendPath(BASE_CRYPTO_CONSUMERS).build();
|
.appendPath(BASE_API_APPS).build();
|
||||||
|
|
||||||
/** Use if multiple items get returned */
|
/** Use if multiple items get returned */
|
||||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.crypto_consumers";
|
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
|
||||||
|
|
||||||
/** Use if a single item is returned */
|
/** Use if a single item is returned */
|
||||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.crypto_consumers";
|
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
|
||||||
|
|
||||||
|
public static Uri buildIdUri(String rowId) {
|
||||||
|
return CONTENT_URI.buildUpon().appendPath(rowId).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri buildByPackageNameUri(String packageName) {
|
||||||
|
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DataStream {
|
public static class DataStream {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package org.sufficientlysecure.keychain.provider;
|
package org.sufficientlysecure.keychain.provider;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
||||||
@ -37,7 +37,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
String KEY_RINGS = "key_rings";
|
String KEY_RINGS = "key_rings";
|
||||||
String KEYS = "keys";
|
String KEYS = "keys";
|
||||||
String USER_IDS = "user_ids";
|
String USER_IDS = "user_ids";
|
||||||
String CRYPTO_CONSUMERS = "crypto_consumers";
|
String API_APPS = "api_apps";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
|
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
|
||||||
@ -64,10 +64,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
||||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS "
|
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS "
|
||||||
+ Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID
|
+ Tables.API_APPS + " (" + BaseColumns._ID
|
||||||
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME
|
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME
|
||||||
+ " TEXT UNIQUE)";
|
+ " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, "
|
||||||
|
+ ApiAppsColumns.ASCII_ARMOR + " INTEGER, "
|
||||||
|
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
||||||
|
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
|
||||||
|
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
|
||||||
|
|
||||||
KeychainDatabase(Context context) {
|
KeychainDatabase(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
@ -80,7 +84,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
db.execSQL(CREATE_KEY_RINGS);
|
db.execSQL(CREATE_KEY_RINGS);
|
||||||
db.execSQL(CREATE_KEYS);
|
db.execSQL(CREATE_KEYS);
|
||||||
db.execSQL(CREATE_USER_IDS);
|
db.execSQL(CREATE_USER_IDS);
|
||||||
db.execSQL(CREATE_CRYPTO_CONSUMERS);
|
db.execSQL(CREATE_API_APPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -108,7 +112,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
|
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
db.execSQL(CREATE_CRYPTO_CONSUMERS);
|
db.execSQL(CREATE_API_APPS);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -17,13 +17,11 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.provider;
|
package org.sufficientlysecure.keychain.provider;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
|
||||||
@ -44,7 +42,6 @@ import android.database.sqlite.SQLiteConstraintException;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
@ -81,7 +78,9 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
private static final int SECRET_KEY_RING_USER_ID = 221;
|
private static final int SECRET_KEY_RING_USER_ID = 221;
|
||||||
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
|
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
|
||||||
|
|
||||||
private static final int CRYPTO_CONSUMERS = 301;
|
private static final int API_APPS = 301;
|
||||||
|
private static final int API_APPS_BY_ROW_ID = 302;
|
||||||
|
private static final int API_APPS_BY_PACKAGE_NAME = 303;
|
||||||
|
|
||||||
// private static final int DATA_STREAM = 401;
|
// private static final int DATA_STREAM = 401;
|
||||||
|
|
||||||
@ -227,9 +226,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
SECRET_KEY_RING_USER_ID_BY_ROW_ID);
|
SECRET_KEY_RING_USER_ID_BY_ROW_ID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crypto Consumers
|
* API apps
|
||||||
*/
|
*/
|
||||||
matcher.addURI(authority, KeychainContract.BASE_CRYPTO_CONSUMERS, CRYPTO_CONSUMERS);
|
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
|
||||||
|
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* data stream
|
* data stream
|
||||||
@ -290,8 +292,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
return UserIds.CONTENT_ITEM_TYPE;
|
return UserIds.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
case CRYPTO_CONSUMERS:
|
case API_APPS:
|
||||||
return CryptoConsumers.CONTENT_TYPE;
|
return ApiApps.CONTENT_TYPE;
|
||||||
|
|
||||||
|
case API_APPS_BY_ROW_ID:
|
||||||
|
case API_APPS_BY_PACKAGE_NAME:
|
||||||
|
return ApiApps.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
@ -601,8 +607,21 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CRYPTO_CONSUMERS:
|
case API_APPS:
|
||||||
qb.setTables(Tables.CRYPTO_CONSUMERS);
|
qb.setTables(Tables.API_APPS);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case API_APPS_BY_ROW_ID:
|
||||||
|
qb.setTables(Tables.API_APPS);
|
||||||
|
|
||||||
|
qb.appendWhere(BaseColumns._ID + " = ");
|
||||||
|
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||||
|
|
||||||
|
break;
|
||||||
|
case API_APPS_BY_PACKAGE_NAME:
|
||||||
|
qb.setTables(Tables.API_APPS);
|
||||||
|
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
|
||||||
|
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -653,6 +672,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||||
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_KEY:
|
case PUBLIC_KEY_RING_KEY:
|
||||||
@ -660,11 +680,13 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||||
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
|
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_USER_ID:
|
case PUBLIC_KEY_RING_USER_ID:
|
||||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||||
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
|
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SECRET_KEY_RING:
|
case SECRET_KEY_RING:
|
||||||
@ -672,6 +694,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
|
||||||
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SECRET_KEY_RING_KEY:
|
case SECRET_KEY_RING_KEY:
|
||||||
@ -679,12 +702,18 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
rowId = db.insertOrThrow(Tables.KEYS, null, values);
|
||||||
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
|
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SECRET_KEY_RING_USER_ID:
|
case SECRET_KEY_RING_USER_ID:
|
||||||
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
|
||||||
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
|
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case API_APPS:
|
||||||
|
rowId = db.insertOrThrow(Tables.API_APPS, null, values);
|
||||||
|
rowUri = ApiApps.buildIdUri(Long.toString(rowId));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
@ -692,7 +721,6 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
|
|
||||||
// notify of changes in db
|
// notify of changes in db
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
|
||||||
|
|
||||||
} catch (SQLiteConstraintException e) {
|
} catch (SQLiteConstraintException e) {
|
||||||
Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
|
Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
|
||||||
@ -720,6 +748,7 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
count = db.delete(Tables.KEY_RINGS,
|
count = db.delete(Tables.KEY_RINGS,
|
||||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||||
@ -728,24 +757,33 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
count = db.delete(Tables.KEY_RINGS,
|
count = db.delete(Tables.KEY_RINGS,
|
||||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||||
count = db.delete(Tables.KEYS,
|
count = db.delete(Tables.KEYS,
|
||||||
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
|
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
|
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
break;
|
break;
|
||||||
|
case API_APPS_BY_ROW_ID:
|
||||||
|
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
|
||||||
|
selectionArgs);
|
||||||
|
break;
|
||||||
|
case API_APPS_BY_PACKAGE_NAME:
|
||||||
|
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
|
||||||
|
selectionArgs);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify of changes in db
|
// notify of changes in db
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@ -771,6 +809,8 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
values,
|
values,
|
||||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||||
selection), selectionArgs);
|
selection), selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
|
||||||
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
|
||||||
@ -781,6 +821,8 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
values,
|
values,
|
||||||
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
|
||||||
selection), selectionArgs);
|
selection), selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
|
||||||
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
case SECRET_KEY_RING_KEY_BY_ROW_ID:
|
||||||
@ -788,19 +830,28 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
.update(Tables.KEYS, values,
|
.update(Tables.KEYS, values,
|
||||||
buildDefaultKeysSelection(uri, getKeyType(match), selection),
|
buildDefaultKeysSelection(uri, getKeyType(match), selection),
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
|
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
|
||||||
count = db.update(Tables.USER_IDS, values,
|
count = db.update(Tables.USER_IDS, values,
|
||||||
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
|
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
|
||||||
break;
|
break;
|
||||||
|
case API_APPS_BY_ROW_ID:
|
||||||
|
count = db.update(Tables.API_APPS, values,
|
||||||
|
buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
|
||||||
|
break;
|
||||||
|
case API_APPS_BY_PACKAGE_NAME:
|
||||||
|
count = db.update(Tables.API_APPS, values,
|
||||||
|
buildDefaultApiAppsSelection(uri, true, selection), selectionArgs);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify of changes in db
|
// notify of changes in db
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
|
|
||||||
|
|
||||||
} catch (SQLiteConstraintException e) {
|
} catch (SQLiteConstraintException e) {
|
||||||
Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
|
Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
|
||||||
@ -883,6 +934,29 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
|
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build default selection statement for API apps. If no extra selection is specified only build
|
||||||
|
* where clause with rowId
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* @param selection
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) {
|
||||||
|
String lastPathSegment = uri.getLastPathSegment();
|
||||||
|
|
||||||
|
String andSelection = "";
|
||||||
|
if (!TextUtils.isEmpty(selection)) {
|
||||||
|
andSelection = " AND (" + selection + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageSelection) {
|
||||||
|
return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
|
||||||
|
} else {
|
||||||
|
return BaseColumns._ID + "=" + lastPathSegment + andSelection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @Override
|
// @Override
|
||||||
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
// int match = mUriMatcher.match(uri);
|
// int match = mUriMatcher.match(uri);
|
||||||
@ -899,10 +973,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
* updated, or deleted
|
* updated, or deleted
|
||||||
*/
|
*/
|
||||||
private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
|
private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
|
||||||
Intent intent = new Intent();
|
// TODO: Disabled, old API
|
||||||
intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
|
// Intent intent = new Intent();
|
||||||
intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
|
// intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
|
||||||
intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
|
// intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
|
||||||
getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
|
// intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
|
||||||
|
//
|
||||||
|
// getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,12 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
|
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.PgpMain;
|
import org.sufficientlysecure.keychain.helper.PgpMain;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
|
import org.sufficientlysecure.keychain.remote_api.AppSettings;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
@ -718,13 +719,13 @@ public class ProviderHelper {
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<String> getCryptoConsumers(Context context) {
|
public static ArrayList<String> getRegisteredApiApps(Context context) {
|
||||||
Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null,
|
Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
|
||||||
null, null);
|
null);
|
||||||
|
|
||||||
ArrayList<String> packageNames = new ArrayList<String>();
|
ArrayList<String> packageNames = new ArrayList<String>();
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME);
|
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
do {
|
do {
|
||||||
packageNames.add(cursor.getString(packageNameCol));
|
packageNames.add(cursor.getString(packageNameCol));
|
||||||
@ -739,9 +740,53 @@ public class ProviderHelper {
|
|||||||
return packageNames;
|
return packageNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addCryptoConsumer(Context context, String packageName) {
|
private static void contentValueForApiApps() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void insertApiApp(Context context, AppSettings appSettings) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(CryptoConsumers.PACKAGE_NAME, packageName);
|
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
||||||
context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values);
|
values.put(ApiApps.KEY_ID, appSettings.getKeyId());
|
||||||
|
values.put(ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
|
||||||
|
// TODO: other parameters
|
||||||
|
context.getContentResolver().insert(ApiApps.CONTENT_URI, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
|
||||||
|
final ContentValues cv = new ContentValues();
|
||||||
|
cv.put(KeychainContract.ApiApps.KEY_ID, appSettings.getKeyId());
|
||||||
|
|
||||||
|
cv.put(KeychainContract.ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
|
||||||
|
// TODO: other parameters
|
||||||
|
|
||||||
|
if (context.getContentResolver().update(uri, cv, null, null) <= 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppSettings getApiAppSettings(Context context, Uri uri) {
|
||||||
|
AppSettings settings = new AppSettings();
|
||||||
|
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
|
||||||
|
if (cur == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (cur.moveToFirst()) {
|
||||||
|
settings.setPackageName(cur.getString(cur
|
||||||
|
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
||||||
|
|
||||||
|
settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
|
||||||
|
|
||||||
|
settings.setAsciiArmor(cur.getInt(cur
|
||||||
|
.getColumnIndexOrThrow(KeychainContract.ApiApps.ASCII_ARMOR)) == 1);
|
||||||
|
|
||||||
|
settings.setPackageName(cur.getString(cur
|
||||||
|
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
||||||
|
|
||||||
|
settings.setPackageName(cur.getString(cur
|
||||||
|
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service runs in its own process, but is available to all other processes as the main
|
||||||
|
* passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
|
||||||
|
* convenience.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class PassphraseCacheService extends Service {
|
public class PassphraseCacheService extends Service {
|
||||||
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
|
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
|
||||||
|
|
||||||
@ -74,9 +80,9 @@ public class PassphraseCacheService extends Service {
|
|||||||
Context mContext;
|
Context mContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This caches a new passphrase by sending a new command to the service. An android service is
|
* This caches a new passphrase in memory by sending a new command to the service. An android
|
||||||
* only run once. Thus, when the service is already started, new commands just add new events to
|
* service is only run once. Thus, when the service is already started, new commands just add
|
||||||
* the alarm manager for new passphrases to let them timeout in the future.
|
* new events to the alarm manager for new passphrases to let them timeout in the future.
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
* @param keyId
|
* @param keyId
|
||||||
@ -95,21 +101,23 @@ public class PassphraseCacheService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a cached passphrase from memory, blocking method
|
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||||
|
* designed to wait until the service returns the passphrase.
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
* @param keyId
|
* @param keyId
|
||||||
* @return
|
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||||
*/
|
*/
|
||||||
public static String getCachedPassphrase(Context context, long keyId) {
|
public static String getCachedPassphrase(Context context, long keyId) {
|
||||||
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
|
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
|
||||||
|
|
||||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||||
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
|
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
|
||||||
|
|
||||||
final Object mutex = new Object();
|
final Object mutex = new Object();
|
||||||
final Bundle returnBundle = new Bundle();
|
final Bundle returnBundle = new Bundle();
|
||||||
|
|
||||||
HandlerThread handlerThread = new HandlerThread("getPassphrase");
|
HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
|
||||||
handlerThread.start();
|
handlerThread.start();
|
||||||
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
||||||
@Override
|
@Override
|
||||||
@ -121,6 +129,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
synchronized (mutex) {
|
synchronized (mutex) {
|
||||||
mutex.notify();
|
mutex.notify();
|
||||||
}
|
}
|
||||||
|
// quit handlerThread
|
||||||
getLooper().quit();
|
getLooper().quit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -147,6 +156,12 @@ public class PassphraseCacheService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal implementation to get cached passphrase.
|
||||||
|
*
|
||||||
|
* @param keyId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private String getCachedPassphraseImpl(long keyId) {
|
private String getCachedPassphraseImpl(long keyId) {
|
||||||
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
|
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
|
||||||
|
|
||||||
@ -168,15 +183,15 @@ public class PassphraseCacheService extends Service {
|
|||||||
// get cached passphrase
|
// get cached passphrase
|
||||||
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
|
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
|
||||||
if (cachedPassphrase == null) {
|
if (cachedPassphrase == null) {
|
||||||
// TODO: fix!
|
// if key has no passphrase -> cache and return empty passphrase
|
||||||
// check if secret key has a passphrase
|
if (!hasPassphrase(this, masterKeyId)) {
|
||||||
// if (!hasPassphrase(context, masterKeyId)) {
|
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
|
||||||
// // cache empty passphrase
|
|
||||||
// addCachedPassphrase(context, masterKeyId, "");
|
addCachedPassphrase(this, masterKeyId, "");
|
||||||
// return "";
|
return "";
|
||||||
// } else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
// set it again to reset the cache life cycle
|
// set it again to reset the cache life cycle
|
||||||
Log.d(TAG, "Cache passphrase again when getting it!");
|
Log.d(TAG, "Cache passphrase again when getting it!");
|
||||||
@ -196,17 +211,10 @@ public class PassphraseCacheService extends Service {
|
|||||||
try {
|
try {
|
||||||
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
|
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
|
||||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
|
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
|
||||||
|
|
||||||
Log.d(Constants.TAG, "Check if key has no passphrase...");
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
"SC").build("".toCharArray());
|
"SC").build("".toCharArray());
|
||||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||||
if (testKey != null) {
|
if (testKey != null) {
|
||||||
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
|
|
||||||
|
|
||||||
// cache empty passphrase
|
|
||||||
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
|
Binary file not shown.
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity;
|
||||||
|
|
||||||
import com.actionbarsherlock.app.ActionBar;
|
import com.actionbarsherlock.app.ActionBar;
|
||||||
import com.actionbarsherlock.app.SherlockActivity;
|
import com.actionbarsherlock.app.SherlockActivity;
|
||||||
@ -80,6 +81,9 @@ public class MainActivity extends SherlockActivity {
|
|||||||
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences)
|
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences)
|
||||||
.setIcon(R.drawable.ic_menu_settings)
|
.setIcon(R.drawable.ic_menu_settings)
|
||||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
menu.add(0, Id.menu.option.crypto_consumers, 0, R.string.menu_apiAppSettings)
|
||||||
|
.setIcon(R.drawable.ic_menu_settings)
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +95,10 @@ public class MainActivity extends SherlockActivity {
|
|||||||
startActivity(new Intent(this, PreferencesActivity.class));
|
startActivity(new Intent(this, PreferencesActivity.class));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case Id.menu.option.crypto_consumers:
|
||||||
|
startActivity(new Intent(this, RegisteredAppsListActivity.class));
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
|
|||||||
// application this would come from a resource.
|
// application this would come from a resource.
|
||||||
setEmptyText(getString(R.string.listEmpty));
|
setEmptyText(getString(R.string.listEmpty));
|
||||||
|
|
||||||
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.public_key);
|
mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key);
|
||||||
|
|
||||||
setListAdapter(mAdapter);
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
@ -160,11 +160,12 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
|
|||||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_ENCRYPT
|
+ " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
|
||||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
+ Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
|
||||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
+ now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
|
||||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
+ Keys.EXPIRY + " >= '" + now + "')) AS "
|
||||||
|
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||||
|
|
||||||
String inMasterKeyList = null;
|
String inMasterKeyList = null;
|
||||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
||||||
|
@ -73,7 +73,7 @@ public class SelectSecretKeyFragment extends SherlockListFragment implements
|
|||||||
// application this would come from a resource.
|
// application this would come from a resource.
|
||||||
setEmptyText(getString(R.string.listEmpty));
|
setEmptyText(getString(R.string.listEmpty));
|
||||||
|
|
||||||
mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.secret_key);
|
mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.secret_key);
|
||||||
|
|
||||||
setListAdapter(mAdapter);
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
@ -23,19 +23,14 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPKeyRing;
|
import org.spongycastle.openpgp.PGPKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
|
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
|
|
||||||
import org.spongycastle.openpgp.PGPUtil;
|
import org.spongycastle.openpgp.PGPUtil;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
|
|
||||||
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
import org.sufficientlysecure.keychain.helper.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -45,11 +40,6 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom Loader to search for bad adware apps, based on
|
|
||||||
* https://github.com/brosmike/AirPush-Detector. Daniel Bjorge licensed it under Apachev2 after
|
|
||||||
* asking him by mail.
|
|
||||||
*/
|
|
||||||
public class ImportKeysListLoader extends AsyncTaskLoader<List<Map<String, String>>> {
|
public class ImportKeysListLoader extends AsyncTaskLoader<List<Map<String, String>>> {
|
||||||
public static final String MAP_ATTR_USER_ID = "user_id";
|
public static final String MAP_ATTR_USER_ID = "user_id";
|
||||||
public static final String MAP_ATTR_FINGERPINT = "fingerprint";
|
public static final String MAP_ATTR_FINGERPINT = "fingerprint";
|
||||||
|
@ -44,9 +44,9 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
|||||||
public final static String PROJECTION_ROW_AVAILABLE = "available";
|
public final static String PROJECTION_ROW_AVAILABLE = "available";
|
||||||
public final static String PROJECTION_ROW_VALID = "valid";
|
public final static String PROJECTION_ROW_VALID = "valid";
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
|
||||||
public SelectKeyCursorAdapter(Context context, ListView listView, Cursor c, int keyType) {
|
int keyType) {
|
||||||
super(context, c);
|
super(context, c, flags);
|
||||||
|
|
||||||
mInflater = LayoutInflater.from(context);
|
mInflater = LayoutInflater.from(context);
|
||||||
mListView = listView;
|
mListView = listView;
|
||||||
@ -65,8 +65,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
boolean valid = cursor.getInt(cursor
|
boolean valid = cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
|
||||||
.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
|
|
||||||
|
|
||||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||||
mainUserId.setText(R.string.unknownUserId);
|
mainUserId.setText(R.string.unknownUserId);
|
||||||
@ -101,8 +100,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
|
|||||||
status.setText(R.string.canSign);
|
status.setText(R.string.canSign);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cursor.getInt(cursor
|
if (cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
|
||||||
.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
|
|
||||||
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
|
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
|
||||||
// expired
|
// expired
|
||||||
status.setText(R.string.expired);
|
status.setText(R.string.expired);
|
||||||
|
@ -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
|
1. Open svg file in Inkscape
|
||||||
2. Extensions -> Color -> darker (2 times!)
|
2. Extensions -> Color -> darker (2 times!)
|
||||||
|
|
||||||
# Security Model
|
|
||||||
|
|
||||||
## Basic goals
|
|
||||||
|
|
||||||
* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog)
|
|
||||||
|
|
||||||
Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL
|
|
||||||
|
|
||||||
## Possible Permissions
|
|
||||||
|
|
||||||
* ACCESS_API: Encrypt/Sign/Decrypt/Create keys without user interaction (intents, remote service), Read key information (not the actual keys)(content provider)
|
|
||||||
* ACCESS_KEYS: get and import actual public and secret keys (remote service)
|
|
||||||
|
|
||||||
## Without Permissions
|
|
||||||
|
|
||||||
### Intents
|
|
||||||
All Intents start with org.sufficientlysecure.keychain.action.
|
|
||||||
|
|
||||||
* android.intent.action.VIEW connected to .gpg and .asc files: Import Key and Decrypt
|
|
||||||
* android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt
|
|
||||||
* IMPORT
|
|
||||||
* IMPORT_FROM_FILE
|
|
||||||
* IMPORT_FROM_QR_CODE
|
|
||||||
* IMPORT_FROM_NFC
|
|
||||||
* SHARE_KEYRING
|
|
||||||
* SHARE_KEYRING_WITH_QR_CODE
|
|
||||||
* SHARE_KEYRING_WITH_NFC
|
|
||||||
* EDIT_KEYRING
|
|
||||||
* SELECT_PUBLIC_KEYRINGS
|
|
||||||
* SELECT_SECRET_KEYRING
|
|
||||||
* ENCRYPT
|
|
||||||
* ENCRYPT_FILE
|
|
||||||
* DECRYPT
|
|
||||||
* DECRYPT_FILE
|
|
||||||
|
|
||||||
## With permission ACCESS_API
|
|
||||||
|
|
||||||
### Intents
|
|
||||||
|
|
||||||
* CREATE_KEYRING
|
|
||||||
* ENCRYPT_AND_RETURN
|
|
||||||
* ENCRYPT_STREAM_AND_RETURN
|
|
||||||
* GENERATE_SIGNATURE_AND_RETURN
|
|
||||||
* DECRYPT_AND_RETURN
|
|
||||||
* DECRYPT_STREAM_AND_RETURN
|
|
||||||
|
|
||||||
### Broadcast Receiver
|
|
||||||
On change of database the following broadcast is send.
|
|
||||||
* DATABASE_CHANGE
|
|
||||||
|
|
||||||
### Content Provider
|
|
||||||
|
|
||||||
* The whole content provider requires a permission (only read)
|
|
||||||
* Don't give out blobs (keys can be accessed by ACCESS_KEYS via remote service)
|
|
||||||
* Make an internal and external content provider (or pathes with <path-permission>)
|
|
||||||
* Look at android:grantUriPermissions especially for ApgServiceBlobProvider
|
|
||||||
* Only give out android:readPermission
|
|
||||||
|
|
||||||
### ApgApiService (Remote Service)
|
|
||||||
AIDL service
|
|
||||||
|
|
||||||
## With permission ACCESS_KEYS
|
|
||||||
|
|
||||||
### ApgKeyService (Remote Service)
|
|
||||||
AIDL service to access actual private keyring objects
|
|
||||||
|
|
||||||
# Coding Style
|
# Coding Style
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
Loading…
Reference in New Issue
Block a user