Crypto Provider implementation start

This commit is contained in:
Dominik Schürmann 2013-05-28 15:10:36 +02:00
parent 8c537d3367
commit b221c0c905
23 changed files with 1492 additions and 86 deletions

View File

@ -27,6 +27,10 @@
android:name=".IntentDemoActivity"
android:label="Intent Demo 1"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".CryptoProviderDemoActivity"
android:label="Crypto Provider"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".AidlDemoActivity"
android:label="Aidl Demo (ACCESS_API permission)"

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/crypto_provider_demo_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="registerCryptoProvider"
android:text="Register crypto provider" />
<Button
android:id="@+id/aidl_demo_select_secret_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="selectSecretKeyOnClick"
android:text="Select secret key" />
<Button
android:id="@+id/aidl_demo_select_encryption_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="selectEncryptionKeysOnClick"
android:text="Select encryption key(s)" />
<EditText
android:id="@+id/aidl_demo_message"
android:layout_width="match_parent"
android:layout_height="150dip"
android:text="message"
android:textAppearance="@android:style/TextAppearance.Small" />
<EditText
android:id="@+id/aidl_demo_ciphertext"
android:layout_width="match_parent"
android:layout_height="150dip"
android:text="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" />
<Button
android:id="@+id/aidl_demo_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="encryptOnClick"
android:text="Encrypt" />
<Button
android:id="@+id/aidl_demo_decrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="decryptOnClick"
android:text="Decrypt" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="APG Data:" />
<TextView
android:id="@+id/aidl_demo_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minLines="10" />
</LinearLayout>
</ScrollView>

View File

@ -19,5 +19,10 @@
android:key="aidl_demo2"
android:title="AIDL Demo (ACCESS_KEYS permission)" />
</PreferenceCategory>
<PreferenceCategory android:title="Crypto Provider" >
<Preference
android:key="crypto_provider_demo"
android:title="Crypto Provider" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -30,6 +30,7 @@ public class BaseActivity extends PreferenceActivity {
private Preference mIntentDemo;
private Preference mContentProviderDemo;
private Preference mCryptoProvider;
private Preference mAidlDemo;
private Preference mAidlDemo2;
@ -48,6 +49,7 @@ public class BaseActivity extends PreferenceActivity {
// find preferences
mIntentDemo = (Preference) findPreference("intent_demo");
mContentProviderDemo = (Preference) findPreference("content_provider_demo");
mCryptoProvider = (Preference) findPreference("crypto_provider_demo");
mAidlDemo = (Preference) findPreference("aidl_demo");
mAidlDemo2 = (Preference) findPreference("aidl_demo2");
@ -68,6 +70,15 @@ public class BaseActivity extends PreferenceActivity {
return false;
}
});
mCryptoProvider.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(mActivity, CryptoProviderDemoActivity.class));
return false;
}
});
mAidlDemo.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override

View File

@ -0,0 +1,238 @@
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.demo;
import org.sufficientlysecure.keychain.demo.R;
import org.sufficientlysecure.keychain.integration.Constants;
import org.sufficientlysecure.keychain.integration.KeychainData;
import org.sufficientlysecure.keychain.integration.KeychainIntentHelper;
import org.sufficientlysecure.keychain.service.IKeychainApiService;
import org.sufficientlysecure.keychain.service.IKeychainKeyService;
import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class CryptoProviderDemoActivity extends Activity {
Activity mActivity;
TextView mMessageTextView;
TextView mCiphertextTextView;
TextView mDataTextView;
KeychainIntentHelper mKeychainIntentHelper;
KeychainData mKeychainData;
private IKeychainApiService service = null;
private ServiceConnection svcConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = IKeychainApiService.Stub.asInterface(binder);
}
public void onServiceDisconnected(ComponentName className) {
service = null;
}
};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.crypto_provider_demo);
mActivity = this;
mMessageTextView = (TextView) findViewById(R.id.aidl_demo_message);
mCiphertextTextView = (TextView) findViewById(R.id.aidl_demo_ciphertext);
mDataTextView = (TextView) findViewById(R.id.aidl_demo_data);
mKeychainIntentHelper = new KeychainIntentHelper(mActivity);
mKeychainData = new KeychainData();
bindService(new Intent(IKeychainApiService.class.getName()), svcConn,
Context.BIND_AUTO_CREATE);
}
public void registerCryptoProvider(View view) {
try {
startActivityForResult(Intent.createChooser(new Intent("com.android.crypto.REGISTER"),
"select crypto provider"), 123);
} catch (ActivityNotFoundException e) {
Toast.makeText(mActivity, "No app that handles com.android.crypto.REGISTER!",
Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "No app that handles com.android.crypto.REGISTER!");
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 123) {
if (resultCode == RESULT_OK) {
String packageName = data.getStringExtra("packageName");
Log.d(Constants.TAG, "packageName: " + packageName);
}
}
// boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data,
// mKeychainData);
// if (result) {
// updateView();
// }
// continue with other activity results
super.onActivityResult(requestCode, resultCode, data);
}
public void encryptOnClick(View view) {
byte[] inputBytes = mMessageTextView.getText().toString().getBytes();
try {
service.encryptAsymmetric(inputBytes, null, true, 0, mKeychainData.getPublicKeys(), 7,
encryptHandler);
} catch (RemoteException e) {
exceptionImplementation(-1, e.toString());
}
}
public void decryptOnClick(View view) {
byte[] inputBytes = mCiphertextTextView.getText().toString().getBytes();
try {
service.decryptAndVerifyAsymmetric(inputBytes, null, null, decryptHandler);
} catch (RemoteException e) {
exceptionImplementation(-1, e.toString());
}
}
private void updateView() {
if (mKeychainData.getDecryptedData() != null) {
mMessageTextView.setText(mKeychainData.getDecryptedData());
}
if (mKeychainData.getEncryptedData() != null) {
mCiphertextTextView.setText(mKeychainData.getEncryptedData());
}
mDataTextView.setText(mKeychainData.toString());
}
@Override
public void onDestroy() {
super.onDestroy();
unbindService(svcConn);
}
private void exceptionImplementation(int exceptionId, String error) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Exception!").setMessage(error).setPositiveButton("OK", null).show();
}
private final IKeychainEncryptHandler.Stub encryptHandler = new IKeychainEncryptHandler.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(final byte[] outputBytes, String outputUri) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
mKeychainData.setEncryptedData(new String(outputBytes));
updateView();
}
});
}
};
private final IKeychainDecryptHandler.Stub decryptHandler = new IKeychainDecryptHandler.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(final byte[] outputBytes, String outputUri, boolean signature,
long signatureKeyId, String signatureUserId, boolean signatureSuccess,
boolean signatureUnknown) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
mKeychainData.setDecryptedData(new String(outputBytes));
updateView();
}
});
}
};
private final IKeychainGetDecryptionKeyIdHandler.Stub helperHandler = new IKeychainGetDecryptionKeyIdHandler.Stub() {
@Override
public void onException(final int exceptionId, final String message) throws RemoteException {
runOnUiThread(new Runnable() {
public void run() {
exceptionImplementation(exceptionId, message);
}
});
}
@Override
public void onSuccess(long arg0, boolean arg1) throws RemoteException {
// TODO Auto-generated method stub
}
};
/**
* Selection is done with Intents, not AIDL!
*
* @param view
*/
public void selectSecretKeyOnClick(View view) {
mKeychainIntentHelper.selectSecretKey();
}
public void selectEncryptionKeysOnClick(View view) {
mKeychainIntentHelper.selectPublicKeys("user@example.com");
}
}

View File

@ -86,9 +86,7 @@
android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain"
android:protectionLevel="dangerous" />
<!--
android:allowBackup="false": Don't allow backup over adb backup or other apps!
-->
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
<application
android:name=".KeychainApplication"
android:allowBackup="false"
@ -454,6 +452,42 @@
android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider"
android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider"
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
<!-- Crypto Provider other intents -->
<activity
android:name=".crypto_provider.CryptoActivity"
android:label="TODO crypto activity" >
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Crypto Provider API -->
<activity
android:name=".crypto_provider.RegisterActivity"
android:label="TODO reg" >
<intent-filter>
<action android:name="com.android.crypto.REGISTER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService"
android:enabled="true"
android:exported="true"
android:process=":crypto" >
<intent-filter>
<action android:name="com.android.crypto.ICryptoService" />
</intent-filter>
<meta-data
android:name="api_version"
android:value="1" />
</service>
</application>
</manifest>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Register?" />
<Button
android:id="@+id/register_crypto_consumer_allow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Allow access" />
<Button
android:id="@+id/register_crypto_consumer_disallow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Disallow" />
</LinearLayout>

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoError;

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
import android.os.Parcel;
import android.os.Parcelable;
public class CryptoError implements Parcelable {
int errorId;
String message;
public CryptoError() {
}
public CryptoError(int errorId, String message) {
this.errorId = errorId;
this.message = message;
}
public CryptoError(CryptoError b) {
this.errorId = b.errorId;
this.message = b.message;
}
public int getErrorId() {
return errorId;
}
public void setErrorId(int errorId) {
this.errorId = errorId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(errorId);
dest.writeString(message);
}
public static final Creator<CryptoError> CREATOR = new Creator<CryptoError>() {
public CryptoError createFromParcel(final Parcel source) {
CryptoError error = new CryptoError();
error.errorId = source.readInt();
error.message = source.readString();
return error;
}
public CryptoError[] newArray(final int size) {
return new CryptoError[size];
}
};
}

View File

@ -0,0 +1,73 @@
package com.android.crypto;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
public class CryptoServiceConnection {
private Context mApplicationContext;
private ICryptoService mService;
private boolean bound;
private String cryptoProviderPackageName;
private static final String TAG = "CryptoConnection";
public CryptoServiceConnection(Context context, String cryptoProviderPackageName) {
mApplicationContext = context.getApplicationContext();
this.cryptoProviderPackageName = cryptoProviderPackageName;
}
public ICryptoService getService() {
return mService;
}
private ServiceConnection mCryptoServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ICryptoService.Stub.asInterface(service);
Log.d(TAG, "connected to service");
bound = true;
}
public void onServiceDisconnected(ComponentName name) {
mService = null;
Log.d(TAG, "disconnected from service");
bound = false;
}
};
/**
* If not already bound, bind!
*
* @return
*/
public boolean bindToService() {
if (mService == null && !bound) { // if not already connected
try {
Log.d(TAG, "not bound yet");
Intent serviceIntent = new Intent();
serviceIntent.setAction("com.android.crypto.ICryptoService");
serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
Context.BIND_AUTO_CREATE);
return true;
} catch (Exception e) {
Log.d(TAG, "Exception", e);
return false;
}
} else { // already connected
Log.d(TAG, "already bound... ");
return true;
}
}
public void unbindFromService() {
mApplicationContext.unbindService(mCryptoServiceConnection);
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable CryptoSignatureResult;

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
import android.os.Parcel;
import android.os.Parcelable;
public class CryptoSignatureResult implements Parcelable {
String signatureUserId;
boolean signature;
boolean signatureSuccess;
boolean signatureUnknown;
public CryptoSignatureResult() {
}
public CryptoSignatureResult(String signatureUserId, boolean signature,
boolean signatureSuccess, boolean signatureUnknown) {
this.signatureUserId = signatureUserId;
this.signature = signature;
this.signatureSuccess = signatureSuccess;
this.signatureUnknown = signatureUnknown;
}
public CryptoSignatureResult(CryptoSignatureResult b) {
this.signatureUserId = b.signatureUserId;
this.signature = b.signature;
this.signatureSuccess = b.signatureSuccess;
this.signatureUnknown = b.signatureUnknown;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(signatureUserId);
dest.writeByte((byte) (signature ? 1 : 0));
dest.writeByte((byte) (signatureSuccess ? 1 : 0));
dest.writeByte((byte) (signatureUnknown ? 1 : 0));
}
public static final Creator<CryptoSignatureResult> CREATOR = new Creator<CryptoSignatureResult>() {
public CryptoSignatureResult createFromParcel(final Parcel source) {
CryptoSignatureResult vr = new CryptoSignatureResult();
vr.signatureUserId = source.readString();
vr.signature = source.readByte() == 1;
vr.signatureSuccess = source.readByte() == 1;
vr.signatureUnknown = source.readByte() == 1;
return vr;
}
public CryptoSignatureResult[] newArray(final int size) {
return new CryptoSignatureResult[size];
}
};
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
import com.android.crypto.CryptoSignatureResult;
import com.android.crypto.CryptoError;
interface ICryptoCallback {
oneway void onEncryptSignSuccess(in byte[] outputBytes);
oneway void onDecryptVerifySuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
oneway void onError(in CryptoError error);
oneway void onActivityRequired(in Intent intent);
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.crypto;
import com.android.crypto.ICryptoCallback;
/**
* All methods are oneway, which means they are asynchronous and non-blocking.
* Results are returned to the callback, which has to be implemented on client side.
*/
interface ICryptoService {
/**
* Encrypt
*
* @param inputBytes
* Byte array you want to encrypt
* @param encryptionKeyIds
* Ids of public keys used for encryption
* @param handler
* Results are returned to this Handler after successful encryption
*/
oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback);
/**
* Encrypt and sign
*
*
*
* @param inputBytes
* Byte array you want to encrypt
* @param signatureKeyId
* Key id of key to sign with
* @param handler
* Results are returned to this Handler after successful encryption and signing
*/
oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
/**
* Sign
*
*
*
* @param inputBytes
* Byte array you want to encrypt
* @param signatureId
*
* @param handler
* Results are returned to this Handler after successful encryption and signing
*/
oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback);
/**
* Decrypts and verifies given input bytes. If no signature is present this method
* will only decrypt.
*
* @param inputBytes
* Byte array you want to decrypt and verify
* @param handler
* Handler where to return results to after successful encryption
*/
oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
}

View File

@ -0,0 +1,92 @@
package org.sufficientlysecure.keychain.crypto_provider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class CryptoActivity extends SherlockFragmentActivity {
public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE";
public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleActions(getIntent());
}
protected void handleActions(Intent intent) {
// TODO: Important: Check if calling package is in list!
String action = intent.getAction();
Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
/**
* com.android.crypto actions
*/
if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
showPassphraseDialog(secretKeyId);
} else {
Log.e(Constants.TAG, "Wrong action!");
setResult(RESULT_CANCELED);
finish();
}
}
/**
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
* for a symmetric passphrase
*/
private void showPassphraseDialog(long secretKeyId) {
// Message is received after passphrase is cached
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
setResult(RESULT_OK);
finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
try {
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
messenger, secretKeyId);
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
} catch (PgpMain.PgpGeneralException e) {
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
// send message to handler to start encryption directly
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
}
}
}

View File

@ -0,0 +1,391 @@
/*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.crypto_provider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import org.spongycastle.openpgp.PGPException;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.helper.PgpMain.PgpGeneralException;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.IKeychainApiService;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler;
import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler;
import com.android.crypto.CryptoError;
import com.android.crypto.ICryptoCallback;
import com.android.crypto.ICryptoService;
import com.android.crypto.CryptoSignatureResult;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
public class CryptoService extends Service {
Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = this;
Log.d(Constants.TAG, "KeychainApiService, onCreate()");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(Constants.TAG, "KeychainApiService, onDestroy()");
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private synchronized void encryptAndSignSafe(byte[] inputBytes, String inputUri,
boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
String encryptionPassphrase, int symmetricEncryptionAlgorithm, long signatureKeyId,
int signatureHashAlgorithm, boolean signatureForceV3, String signaturePassphrase,
IKeychainEncryptHandler handler) throws RemoteException {
try {
// build InputData and write into OutputStream
InputStream inputStream = new ByteArrayInputStream(inputBytes);
long inputLength = inputBytes.length;
InputData input = new InputData(inputStream, inputLength);
OutputStream output = new ByteArrayOutputStream();
PgpMain.encryptAndSign(mContext, null, input, output, useAsciiArmor, compression,
encryptionKeyIds, encryptionPassphrase, symmetricEncryptionAlgorithm,
signatureKeyId, signatureHashAlgorithm, signatureForceV3, signaturePassphrase);
output.close();
// start activity from service, TOOD: Test!
// Intent dialogIntent = new Intent(getBaseContext(), myActivity.class);
// dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// getApplication().startActivity(dialogIntent);
byte[] outputBytes = ((ByteArrayOutputStream) output).toByteArray();
// return over handler on client side
handler.onSuccess(outputBytes, null);
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
handler.onException(getExceptionId(e), e.getMessage());
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
private synchronized void decryptAndVerifySafe(byte[] inputBytes, String passphrase,
boolean assumeSymmetric, IKeychainDecryptHandler handler) throws RemoteException {
try {
// build InputData and write into OutputStream
InputStream inputStream = new ByteArrayInputStream(inputBytes);
long inputLength = inputBytes.length;
InputData inputData = new InputData(inputStream, inputLength);
OutputStream outputStream = new ByteArrayOutputStream();
Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData, outputStream,
passphrase, assumeSymmetric);
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
long signatureKeyId = outputBundle
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
String signatureUserId = outputBundle
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
boolean signatureSuccess = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
boolean signatureUnknown = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
// return over handler on client side
handler.onSuccess(outputBytes, null, signature, signatureKeyId, signatureUserId,
signatureSuccess, signatureUnknown);
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
handler.onException(getExceptionId(e), e.getMessage());
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
private synchronized void getDecryptionKeySafe(byte[] inputBytes, String inputUri,
IKeychainGetDecryptionKeyIdHandler handler) {
// TODO: implement inputUri
try {
InputStream inputStream = new ByteArrayInputStream(inputBytes);
long secretKeyId = Id.key.none;
boolean symmetric;
try {
secretKeyId = PgpMain.getDecryptionKeyId(CryptoService.this, inputStream);
if (secretKeyId == Id.key.none) {
throw new PgpGeneralException(getString(R.string.error_noSecretKeyFound));
}
symmetric = false;
} catch (PgpMain.NoAsymmetricEncryptionException e) {
secretKeyId = Id.key.symmetric;
if (!PgpMain.hasSymmetricEncryption(CryptoService.this, inputStream)) {
throw new PgpGeneralException(getString(R.string.error_noKnownEncryptionFound));
}
symmetric = true;
}
handler.onSuccess(secretKeyId, symmetric);
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
handler.onException(getExceptionId(e), e.getMessage());
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
private final ICryptoService.Stub mBinder = new ICryptoService.Stub() {
@Override
public void encrypt(byte[] inputBytes, String[] encryptionUserIds, ICryptoCallback callback)
throws RemoteException {
// TODO Auto-generated method stub
}
@Override
public void encryptAndSign(byte[] inputBytes, String[] encryptionUserIds,
String signatureUserId, ICryptoCallback callback) throws RemoteException {
// TODO Auto-generated method stub
}
@Override
public void sign(byte[] inputBytes, String signatureUserId, ICryptoCallback callback)
throws RemoteException {
// TODO Auto-generated method stub
}
@Override
public void decryptAndVerify(byte[] inputBytes, ICryptoCallback callback)
throws RemoteException {
try {
// build InputData and write into OutputStream
InputStream inputStream = new ByteArrayInputStream(inputBytes);
long inputLength = inputBytes.length;
InputData inputData = new InputData(inputStream, inputLength);
OutputStream outputStream = new ByteArrayOutputStream();
// String passphrase = "";
long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
if (secretKeyId == Id.key.none) {
throw new PgpMain.PgpGeneralException(
getString(R.string.error_noSecretKeyFound));
}
String passphrase = PassphraseCacheService.getCachedPassphrase(mContext,
secretKeyId);
if (passphrase == null) {
// No passphrase cached for this ciphertext! Intent required to cache
// passphrase!
Intent intent = new Intent(CryptoActivity.ACTION_CACHE_PASSPHRASE);
intent.putExtra(CryptoActivity.EXTRA_SECRET_KEY_ID, secretKeyId);
callback.onActivityRequired(intent);
return;
}
// if (signedOnly) {
// resultData = PgpMain.verifyText(this, this, inputData, outStream,
// lookupUnknownKey);
// } else {
// resultData = PgpMain.decryptAndVerify(this, this, inputData, outStream,
// PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
// assumeSymmetricEncryption);
// }
Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData,
outputStream, passphrase, false);
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
long signatureKeyId = outputBundle
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
String signatureUserId = outputBundle
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
boolean signatureSuccess = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
boolean signatureUnknown = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
CryptoSignatureResult sigResult = new CryptoSignatureResult(signatureUserId,
signature, signatureSuccess, signatureUnknown);
// return over handler on client side
callback.onDecryptVerifySuccess(outputBytes, sigResult);
// handler.onSuccess(outputBytes, null, signature, signatureKeyId, signatureUserId,
// signatureSuccess, signatureUnknown);
} catch (Exception e) {
Log.e(Constants.TAG, "KeychainService, Exception!", e);
try {
callback.onError(new CryptoError(getExceptionId(e), e.getMessage()));
} catch (Exception t) {
Log.e(Constants.TAG, "Error returning exception to client", t);
}
}
}
//
// @Override
// public void encryptAsymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
// int compression, long[] encryptionKeyIds, int symmetricEncryptionAlgorithm,
// IKeychainEncryptHandler handler) throws RemoteException {
//
// encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, encryptionKeyIds,
// null, symmetricEncryptionAlgorithm, Id.key.none, 0, false, null, handler);
// }
//
// @Override
// public void encryptSymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
// int compression, String encryptionPassphrase, int symmetricEncryptionAlgorithm,
// IKeychainEncryptHandler handler) throws RemoteException {
//
// encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, null,
// encryptionPassphrase, symmetricEncryptionAlgorithm, Id.key.none, 0, false,
// null, handler);
// }
//
// @Override
// public void encryptAndSignAsymmetric(byte[] inputBytes, String inputUri,
// boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
// int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
// boolean signatureForceV3, String signaturePassphrase,
// IKeychainEncryptHandler handler) throws RemoteException {
//
// encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, encryptionKeyIds,
// null, symmetricEncryptionAlgorithm, signatureKeyId, signatureHashAlgorithm,
// signatureForceV3, signaturePassphrase, handler);
// }
//
// @Override
// public void encryptAndSignSymmetric(byte[] inputBytes, String inputUri,
// boolean useAsciiArmor, int compression, String encryptionPassphrase,
// int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
// boolean signatureForceV3, String signaturePassphrase,
// IKeychainEncryptHandler handler) throws RemoteException {
//
// encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, null,
// encryptionPassphrase, symmetricEncryptionAlgorithm, signatureKeyId,
// signatureHashAlgorithm, signatureForceV3, signaturePassphrase, handler);
// }
//
// @Override
// public void decryptAndVerifyAsymmetric(byte[] inputBytes, String inputUri,
// String keyPassphrase, IKeychainDecryptHandler handler) throws RemoteException {
//
// decryptAndVerifySafe(inputBytes, inputUri, keyPassphrase, false, handler);
// }
//
// @Override
// public void decryptAndVerifySymmetric(byte[] inputBytes, String inputUri,
// String encryptionPassphrase, IKeychainDecryptHandler handler)
// throws RemoteException {
//
// decryptAndVerifySafe(inputBytes, inputUri, encryptionPassphrase, true, handler);
// }
//
// @Override
// public void getDecryptionKeyId(byte[] inputBytes, String inputUri,
// IKeychainGetDecryptionKeyIdHandler handler) throws RemoteException {
//
// getDecryptionKeySafe(inputBytes, inputUri, handler);
// }
};
/**
* As we can not throw an exception through Android RPC, we assign identifiers to the exception
* types.
*
* @param e
* @return
*/
private int getExceptionId(Exception e) {
if (e instanceof NoSuchProviderException) {
return 0;
} else if (e instanceof NoSuchAlgorithmException) {
return 1;
} else if (e instanceof SignatureException) {
return 2;
} else if (e instanceof IOException) {
return 3;
} else if (e instanceof PgpGeneralException) {
return 4;
} else if (e instanceof PGPException) {
return 5;
} else {
return -1;
}
}
}

View File

@ -0,0 +1,74 @@
package org.sufficientlysecure.keychain.crypto_provider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class RegisterActivity extends Activity {
public static final String ACTION_REGISTER = "com.android.crypto.REGISTER";
public static final String EXTRA_PACKAGE_NAME = "packageName";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleActions(getIntent());
}
protected void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
final String callingPackageName = this.getCallingPackage();
/**
* com.android.crypto actions
*/
if (ACTION_REGISTER.equals(action)) {
setContentView(R.layout.register_crypto_consumer_activity);
Button allowButton = (Button) findViewById(R.id.register_crypto_consumer_allow);
Button disallowButton = (Button) findViewById(R.id.register_crypto_consumer_disallow);
allowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ProviderHelper.addCryptoConsumer(RegisterActivity.this, callingPackageName);
Intent data = new Intent();
data.putExtra(EXTRA_PACKAGE_NAME, "org.sufficientlysecure.keychain");
setResult(RESULT_OK, data);
finish();
}
});
disallowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
} else {
Log.e(Constants.TAG, "Please use com.android.crypto.REGISTER as intent action!");
finish();
}
}
}

View File

@ -53,6 +53,10 @@ public class KeychainContract {
String RANK = "rank";
}
interface CryptoConsumersColumns {
String PACKAGE_NAME = "package_name";
}
public static final class KeyTypes {
public static final int PUBLIC = 0;
public static final int SECRET = 1;
@ -78,6 +82,8 @@ public class KeychainContract {
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers";
public static class KeyRings implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
@ -207,6 +213,17 @@ public class KeychainContract {
}
}
public static class CryptoConsumers implements CryptoConsumersColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_CRYPTO_CONSUMERS).build();
/** Use if multiple items get returned */
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.crypto_consumers";
/** Use if a single item is returned */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.crypto_consumers";
}
public static class DataStream {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_DATA).build();

View File

@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
@ -28,15 +29,15 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "apg.db";
private static final int DATABASE_VERSION = 4;
private static final int DATABASE_VERSION = 5;
public interface Tables {
String KEY_RINGS = "key_rings";
String KEYS = "keys";
String USER_IDS = "user_ids";
String CRYPTO_CONSUMERS = "crypto_consumers";
}
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
@ -48,13 +49,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + KeysColumns.KEY_ID
+ " INT64, " + KeysColumns.TYPE + " INTEGER, " + KeysColumns.IS_MASTER_KEY
+ " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.KEY_SIZE
+ " INTEGER, " + KeysColumns.CAN_CERTIFY
+ " INTEGER, " + KeysColumns.CAN_SIGN + " INTEGER, " + KeysColumns.CAN_ENCRYPT
+ " INTEGER, " + KeysColumns.IS_REVOKED + " INTEGER, " + KeysColumns.CREATION
+ " INTEGER, " + KeysColumns.EXPIRY + " INTEGER, " + KeysColumns.KEY_DATA + " BLOB,"
+ KeysColumns.RANK + " INTEGER, " + KeysColumns.KEY_RING_ROW_ID
+ " INTEGER NOT NULL, FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES "
+ Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
+ " INTEGER, " + KeysColumns.CAN_CERTIFY + " INTEGER, " + KeysColumns.CAN_SIGN
+ " INTEGER, " + KeysColumns.CAN_ENCRYPT + " INTEGER, " + KeysColumns.IS_REVOKED
+ " INTEGER, " + KeysColumns.CREATION + " INTEGER, " + KeysColumns.EXPIRY
+ " INTEGER, " + KeysColumns.KEY_DATA + " BLOB," + KeysColumns.RANK + " INTEGER, "
+ KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@ -63,6 +64,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS "
+ Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME
+ " TEXT UNIQUE)";
KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@ -74,6 +80,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEY_RINGS);
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS);
db.execSQL(CREATE_CRYPTO_CONSUMERS);
}
@Override
@ -95,9 +102,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
switch (version) {
case 3:
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY + " INTEGER DEFAULT 0;");
db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
+ " INTEGER DEFAULT 0;");
db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
break;
case 4:
db.execSQL(CREATE_CRYPTO_CONSUMERS);
default:
break;

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.HashMap;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@ -80,7 +81,9 @@ public class KeychainProvider extends ContentProvider {
private static final int SECRET_KEY_RING_USER_ID = 221;
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
private static final int DATA_STREAM = 301;
private static final int CRYPTO_CONSUMERS = 301;
// private static final int DATA_STREAM = 401;
protected boolean mInternalProvider;
protected UriMatcher mUriMatcher;
@ -126,8 +129,7 @@ public class KeychainProvider extends ContentProvider {
PUBLIC_KEY_RING_BY_EMAILS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS,
PUBLIC_KEY_RING_BY_EMAILS); // without emails
// specified
PUBLIC_KEY_RING_BY_EMAILS); // without emails specified
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
PUBLIC_KEY_RING_BY_LIKE_EMAIL);
@ -189,8 +191,7 @@ public class KeychainProvider extends ContentProvider {
SECRET_KEY_RING_BY_EMAILS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS,
SECRET_KEY_RING_BY_EMAILS); // without emails
// specified
SECRET_KEY_RING_BY_EMAILS); // without emails specified
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
SECRET_KEY_RING_BY_LIKE_EMAIL);
@ -225,6 +226,11 @@ public class KeychainProvider extends ContentProvider {
+ KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
SECRET_KEY_RING_USER_ID_BY_ROW_ID);
/**
* Crypto Consumers
*/
matcher.addURI(authority, KeychainContract.BASE_CRYPTO_CONSUMERS, CRYPTO_CONSUMERS);
/**
* data stream
*
@ -232,7 +238,7 @@ public class KeychainProvider extends ContentProvider {
* data / _
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
// matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
return matcher;
}
@ -284,6 +290,9 @@ public class KeychainProvider extends ContentProvider {
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
return UserIds.CONTENT_ITEM_TYPE;
case CRYPTO_CONSUMERS:
return CryptoConsumers.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -591,6 +600,11 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case CRYPTO_CONSUMERS:
qb.setTables(Tables.CRYPTO_CONSUMERS);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
@ -869,16 +883,16 @@ public class KeychainProvider extends ContentProvider {
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
int match = mUriMatcher.match(uri);
if (match != DATA_STREAM) {
throw new FileNotFoundException();
}
String fileName = uri.getLastPathSegment();
File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
// @Override
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// int match = mUriMatcher.match(uri);
// if (match != DATA_STREAM) {
// throw new FileNotFoundException();
// }
// String fileName = uri.getLastPathSegment();
// File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
// return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
// }
/**
* This broadcast is send system wide to inform other application that a keyring was inserted,

View File

@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.helper.PgpMain;
import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
@ -516,10 +517,13 @@ public class ProviderHelper {
* @return
*/
private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) {
String[] projection = new String[] { KeyRings.MASTER_KEY_ID, "(SELECT COUNT(sign_keys." +
Keys._ID + ") FROM " + Tables.KEYS + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND sign_keys."
+ Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY + " = 1) AS sign", };
String[] projection = new String[] {
KeyRings.MASTER_KEY_ID,
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY
+ " = 1) AS sign", };
ContentResolver cr = context.getContentResolver();
Cursor cursor = cr.query(queryUri, projection, null, null, null);
@ -713,4 +717,31 @@ public class ProviderHelper {
return cursor;
}
public static ArrayList<String> getCryptoConsumers(Context context) {
Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null,
null, null);
ArrayList<String> packageNames = new ArrayList<String>();
if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME);
if (cursor.moveToFirst()) {
do {
packageNames.add(cursor.getString(packageNameCol));
} while (cursor.moveToNext());
}
}
if (cursor != null) {
cursor.close();
}
return packageNames;
}
public static void addCryptoConsumer(Context context, String packageName) {
ContentValues values = new ContentValues();
values.put(CryptoConsumers.PACKAGE_NAME, packageName);
context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values);
}
}

View File

@ -19,8 +19,12 @@ package org.sufficientlysecure.keychain.service;
import java.util.Date;
import java.util.HashMap;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.helper.PgpHelper;
@ -102,7 +106,14 @@ public class PassphraseCacheService extends Service {
// get cached passphrase
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
if (cachedPassphrase == null) {
return null;
// check if secret key has a passphrase
if (!hasPassphrase(context, masterKeyId)) {
// cache empty passphrase
addCachedPassphrase(context, masterKeyId, "");
return "";
} else {
return null;
}
}
// set it again to reset the cache life cycle
Log.d(TAG, "Cache passphrase again when getting it!");
@ -111,6 +122,37 @@ public class PassphraseCacheService extends Service {
return cachedPassphrase;
}
/**
* Checks if key has a passphrase.
*
* @param secretKeyId
* @return true if it has a passphrase
*/
public static boolean hasPassphrase(Context context, long secretKeyId) {
// check if the key has no passphrase
try {
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
Log.d(Constants.TAG, "Check if key has no passphrase...");
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
if (testKey != null) {
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
// cache empty passphrase
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
return false;
}
} catch (PGPException e) {
// silently catch
}
return true;
}
/**
* Register BroadcastReceiver that is unregistered when service is destroyed. This
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout

View File

@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.dialog;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
@ -44,7 +43,6 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -80,7 +78,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
long secretKeyId) throws PgpGeneralException {
// check if secret key has a passphrase
if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
if (!hasPassphrase(context, secretKeyId)) {
if (!PassphraseCacheService.hasPassphrase(context, secretKeyId)) {
throw new PgpMain.PgpGeneralException("No passphrase! No passphrase dialog needed!");
}
}
@ -95,39 +93,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
return frag;
}
/**
* Checks if key has a passphrase
*
* @param secretKeyId
* @return true if it has a passphrase
*/
private static boolean hasPassphrase(Context context, long secretKeyId) {
// check if the key has no passphrase
try {
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
// PGPSecretKey secretKey =
// PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
Log.d(Constants.TAG, "Check if key has no passphrase...");
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
if (testKey != null) {
Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
// cache empty passphrase
PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
return false;
}
} catch (PGPException e) {
// silently catch
}
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -153,7 +118,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
alert.setMessage(R.string.passPhraseForSymmetricEncryption);
} else {
// TODO: by master key id???
secretKey = PgpHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity, secretKeyId));
secretKey = PgpHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
secretKeyId));
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
if (secretKey == null) {
@ -181,7 +147,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
@ -189,38 +155,42 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
boolean keyOK = true;
String passPhrase = mPassphraseEditText.getText().toString();
long keyId;
PGPSecretKey clickSecretKey = secretKey;
PGPSecretKey clickSecretKey = secretKey;
if (clickSecretKey != null) {
while (keyOK == true) {
if (clickSecretKey != null) { //check again for loop
if (clickSecretKey != null) { // check again for loop
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(PgpMain.BOUNCY_CASTLE_PROVIDER_NAME).build(
passPhrase.toCharArray());
PGPPrivateKey testKey = clickSecretKey.extractPrivateKey(keyDecryptor);
PGPPrivateKey testKey = clickSecretKey
.extractPrivateKey(keyDecryptor);
if (testKey == null) {
if (!clickSecretKey.isMasterKey()) {
Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
Toast.makeText(activity,
R.string.error_couldNotExtractPrivateKey,
Toast.LENGTH_SHORT).show();
return;
} else {
clickSecretKey = PgpHelper.getKeyNum(ProviderHelper.getPGPSecretKeyRingByKeyId(activity, secretKeyId), curKeyIndex);
curKeyIndex++; //does post-increment work like C?
clickSecretKey = PgpHelper.getKeyNum(ProviderHelper
.getPGPSecretKeyRingByKeyId(activity, secretKeyId),
curKeyIndex);
curKeyIndex++; // does post-increment work like C?
continue;
}
} else {
keyOK = false;
}
} catch (PGPException e) {
Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT)
.show();
Toast.makeText(activity, R.string.wrongPassPhrase,
Toast.LENGTH_SHORT).show();
return;
}
} else {
Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
Toast.LENGTH_SHORT).show();
return; //ran out of keys to try
return; // ran out of keys to try
}
}
keyId = secretKey.getKeyID();
@ -232,7 +202,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
if (keyOK == false && clickSecretKey.getKeyID() != keyId) {
PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(), passPhrase);
PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
passPhrase);
}
sendMessageToHandler(MESSAGE_OKAY);
@ -240,7 +211,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
@ -255,7 +226,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);
if (canKB) {
// request focus and open soft keyboard
// request focus and open soft keyboard
mPassphraseEditText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@ -298,4 +269,3 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
}
}