master merge

This commit is contained in:
Ashley Hughes 2014-02-22 10:27:03 +00:00
commit 1b25ec5a0c
258 changed files with 10073 additions and 5926 deletions

View File

@ -1,3 +1,17 @@
2.3.1
* hotfix for crash when upgrading from old versions
2.3
* remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)
* fix setting expiry dates on keys (thanks to Ash Hughes)
* more internal fixes when editing keys (thanks to Ash Hughes)
* querying keyservers directly from the import screen
* fix layout and dialog style on Android 2.2-3.0
* fix crash on keys with empty user ids
* fix crash and empty lists when coming back from signing screen
* Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source
* fix upload of key from signing screen
2.2 2.2
* New design with navigation drawer * New design with navigation drawer
* New public key list design * New public key list design
@ -23,7 +37,7 @@
* New AIDL API * New AIDL API
1.0.8 1.0.8
* basic key server support (HKP, please report bugs :)) * basic keyserver support (HKP, please report bugs :))
* app2sd (untested, let me know if there are problems) * app2sd (untested, let me know if there are problems)
* more choices for pass phrase cache: 1, 2, 4, 8, hours * more choices for pass phrase cache: 1, 2, 4, 8, hours
* translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick) * translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)

View File

@ -17,7 +17,7 @@ License:
GPLv3+ GPLv3+
Permissions: Permissions:
• Full network access: Internet access is ONLY used for key server queries • Full network access: Internet access is ONLY used for keyserver queries
• Control Near Field Communication: To exchange keys via NFC • Control Near Field Communication: To exchange keys via NFC
• Modify or delete the contents of your USB storage: To export keys to the sd card • Modify or delete the contents of your USB storage: To export keys to the sd card
• Read the contents of your USB storage: To import keys from the sd card • Read the contents of your USB storage: To import keys from the sd card

View File

@ -1,45 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpCallback {
/**
* onSuccess returns on successful OpenPGP operations.
*
* @param output
* contains resulting output (decrypted content (when input was encrypted)
* or content without signature (when input was signed-only))
* @param signatureResult
* signatureResult is only non-null if decryptAndVerify() was called and the content
* was encrypted or signed-and-encrypted.
*/
oneway void onSuccess(in OpenPgpData output, in OpenPgpSignatureResult signatureResult);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,39 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpKeyIdsCallback {
/**
* onSuccess returns on successful getKeyIds operations.
*
* @param keyIds
* returned key ids
*/
oneway void onSuccess(in long[] keyIds);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,143 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.IOpenPgpCallback;
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
/**
* 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 IOpenPgpService {
/**
* Sign
*
* After successful signing, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Encrypt
*
* After successful encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void encrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Sign then encrypt
*
* After successful signing and encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void signAndEncrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* After successful decryption/verification, callback's onSuccess will contain the resulting output.
* The signatureResult in onSuccess is only non-null if signed-and-encrypted or signed-only inputBytes were given.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void decryptAndVerify(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Get available key ids based on given user ids
*
* @param ids
* User Ids (emails) of recipients OR key ids
* @param allowUserInteraction
* Enable user interaction to lookup and import unknown keys
* @param callback
* Callback where to return results (different type than callback in other functions!)
*/
oneway void getKeyIds(in String[] ids, in boolean allowUserInteraction, in IOpenPgpKeyIdsCallback callback);
}

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpData so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpData;

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpError;

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpSignatureResult;

View File

@ -1,45 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpCallback {
/**
* onSuccess returns on successful OpenPGP operations.
*
* @param output
* contains resulting output (decrypted content (when input was encrypted)
* or content without signature (when input was signed-only))
* @param signatureResult
* signatureResult is only non-null if decryptAndVerify() was called and the content
* was encrypted or signed-and-encrypted.
*/
oneway void onSuccess(in OpenPgpData output, in OpenPgpSignatureResult signatureResult);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,39 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpKeyIdsCallback {
/**
* onSuccess returns on successful getKeyIds operations.
*
* @param keyIds
* returned key ids
*/
oneway void onSuccess(in long[] keyIds);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,143 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.IOpenPgpCallback;
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
/**
* 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 IOpenPgpService {
/**
* Sign
*
* After successful signing, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Encrypt
*
* After successful encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void encrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Sign then encrypt
*
* After successful signing and encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void signAndEncrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* After successful decryption/verification, callback's onSuccess will contain the resulting output.
* The signatureResult in onSuccess is only non-null if signed-and-encrypted or signed-only inputBytes were given.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void decryptAndVerify(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Get available key ids based on given user ids
*
* @param ids
* User Ids (emails) of recipients OR key ids
* @param allowUserInteraction
* Enable user interaction to lookup and import unknown keys
* @param callback
* Callback where to return results (different type than callback in other functions!)
*/
oneway void getKeyIds(in String[] ids, in boolean allowUserInteraction, in IOpenPgpKeyIdsCallback callback);
}

View File

@ -1,10 +0,0 @@
package org.openintents.openpgp;
public class OpenPgpConstants {
public static final String TAG = "OpenPgp API";
public static final int REQUIRED_API_VERSION = 1;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
}

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpData so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpData;

View File

@ -1,127 +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.openintents.openpgp;
import android.net.Uri;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
public class OpenPgpData implements Parcelable {
public static final int TYPE_STRING = 0;
public static final int TYPE_BYTE_ARRAY = 1;
public static final int TYPE_FILE_DESCRIPTOR = 2;
public static final int TYPE_URI = 3;
int type;
String string;
byte[] bytes = new byte[0];
ParcelFileDescriptor fileDescriptor;
Uri uri;
public int getType() {
return type;
}
public String getString() {
return string;
}
public byte[] getBytes() {
return bytes;
}
public ParcelFileDescriptor getFileDescriptor() {
return fileDescriptor;
}
public Uri getUri() {
return uri;
}
public OpenPgpData() {
}
/**
* Not a real constructor. This can be used to define requested output type.
*
* @param type
*/
public OpenPgpData(int type) {
this.type = type;
}
public OpenPgpData(String string) {
this.string = string;
this.type = TYPE_STRING;
}
public OpenPgpData(byte[] bytes) {
this.bytes = bytes;
this.type = TYPE_BYTE_ARRAY;
}
public OpenPgpData(ParcelFileDescriptor fileDescriptor) {
this.fileDescriptor = fileDescriptor;
this.type = TYPE_FILE_DESCRIPTOR;
}
public OpenPgpData(Uri uri) {
this.uri = uri;
this.type = TYPE_URI;
}
public OpenPgpData(OpenPgpData b) {
this.string = b.string;
this.bytes = b.bytes;
this.fileDescriptor = b.fileDescriptor;
this.uri = b.uri;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(string);
dest.writeInt(bytes.length);
dest.writeByteArray(bytes);
dest.writeParcelable(fileDescriptor, 0);
dest.writeParcelable(uri, 0);
}
public static final Creator<OpenPgpData> CREATOR = new Creator<OpenPgpData>() {
public OpenPgpData createFromParcel(final Parcel source) {
OpenPgpData vr = new OpenPgpData();
vr.type = source.readInt();
vr.string = source.readString();
vr.bytes = new byte[source.readInt()];
source.readByteArray(vr.bytes);
vr.fileDescriptor = source.readParcelable(ParcelFileDescriptor.class.getClassLoader());
vr.fileDescriptor = source.readParcelable(Uri.class.getClassLoader());
return vr;
}
public OpenPgpData[] newArray(final int size) {
return new OpenPgpData[size];
}
};
}

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpError;

View File

@ -1,52 +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.openintents.openpgp;
import java.util.List;
import java.util.regex.Pattern;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
public class OpenPgpHelper {
private Context context;
public static Pattern PGP_MESSAGE = Pattern.compile(
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
public static Pattern PGP_SIGNED_MESSAGE = Pattern
.compile(
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
Pattern.DOTALL);
public OpenPgpHelper(Context context) {
super();
this.context = context;
}
public boolean isAvailable() {
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) {
return true;
} else {
return false;
}
}
}

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpSignatureResult;

View File

@ -1,168 +0,0 @@
///*
// * 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 java.util.ArrayList;
//import java.util.List;
//
//import org.sufficientlysecure.keychain.demo.R;
//import org.sufficientlysecure.keychain.integration.KeychainData;
//import org.sufficientlysecure.keychain.integration.KeychainIntentHelper;
//import org.sufficientlysecure.keychain.service.IKeychainKeyService;
//import org.sufficientlysecure.keychain.service.handler.IKeychainGetKeyringsHandler;
//
//import android.annotation.SuppressLint;
//import android.app.Activity;
//import android.app.AlertDialog;
//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.Base64;
//import android.view.View;
//import android.widget.TextView;
//
//public class AidlDemoActivity2 extends Activity {
// Activity mActivity;
//
// TextView mKeyringsTextView;
//
// KeychainIntentHelper mKeychainIntentHelper;
// KeychainData mKeychainData;
//
// byte[] keysBytes;
// ArrayList<String> keysStrings;
//
// private IKeychainKeyService service = null;
// private ServiceConnection svcConn = new ServiceConnection() {
// public void onServiceConnected(ComponentName className, IBinder binder) {
// service = IKeychainKeyService.Stub.asInterface(binder);
// }
//
// public void onServiceDisconnected(ComponentName className) {
// service = null;
// }
// };
//
// @Override
// public void onCreate(Bundle icicle) {
// super.onCreate(icicle);
// setContentView(R.layout.aidl_demo2);
//
// mActivity = this;
//
// mKeyringsTextView = (TextView) findViewById(R.id.aidl_demo_keyrings);
//
// mKeychainIntentHelper = new KeychainIntentHelper(mActivity);
// mKeychainData = new KeychainData();
//
// bindService(new Intent(IKeychainKeyService.class.getName()), svcConn,
// Context.BIND_AUTO_CREATE);
// }
//
// public void getKeyringsStringsOnClick(View view) {
// try {
// service.getPublicKeyRings(mKeychainData.getPublicKeys(), true, getKeyringsHandler);
// } catch (RemoteException e) {
// exceptionImplementation(-1, e.toString());
// }
// }
//
// public void getKeyringsBytesOnClick(View view) {
// try {
// service.getPublicKeyRings(mKeychainData.getPublicKeys(), false, getKeyringsHandler);
// } catch (RemoteException e) {
// exceptionImplementation(-1, e.toString());
// }
// }
//
// @SuppressLint("NewApi")
// private void updateView() {
// if (keysBytes != null) {
// mKeyringsTextView.setText(Base64.encodeToString(keysBytes, Base64.DEFAULT));
// } else if (keysStrings != null) {
// mKeyringsTextView.setText("");
// for (String output : keysStrings) {
// mKeyringsTextView.append(output);
// }
// }
// }
//
// @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 IKeychainGetKeyringsHandler.Stub getKeyringsHandler = new IKeychainGetKeyringsHandler.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, final List<String> outputStrings)
// throws RemoteException {
// runOnUiThread(new Runnable() {
// public void run() {
// if (outputBytes != null) {
// keysBytes = outputBytes;
// keysStrings = null;
// } else if (outputStrings != null) {
// keysBytes = null;
// keysStrings = (ArrayList<String>) outputStrings;
// }
// updateView();
// }
// });
//
// }
//
// };
//
// public void selectEncryptionKeysOnClick(View view) {
// mKeychainIntentHelper.selectPublicKeys("user@example.com");
// }
//
// @Override
// protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// // this updates the mKeychainData object to the result of the methods
// boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data,
// mKeychainData);
// if (result) {
// updateView();
// }
//
// // continue with other activity results
// super.onActivityResult(requestCode, resultCode, data);
// }
//
//}

View File

@ -1,352 +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.demo;
import java.util.ArrayList;
import java.util.List;
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpServiceConnection;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.IOpenPgpCallback;
import org.openintents.openpgp.IOpenPgpService;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.TextView;
import android.widget.Toast;
public class OpenPgpProviderActivity extends Activity {
Activity mActivity;
EditText mMessage;
EditText mCiphertext;
EditText mEncryptUserIds;
private OpenPgpServiceConnection mCryptoServiceConnection;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.crypto_provider_demo);
mActivity = this;
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext);
mEncryptUserIds = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id);
selectCryptoProvider();
}
/**
* Callback from remote openpgp service
*/
final IOpenPgpKeyIdsCallback.Stub getKeysEncryptCallback = new IOpenPgpKeyIdsCallback.Stub() {
@Override
public void onSuccess(final long[] keyIds) throws RemoteException {
Log.d(Constants.TAG, "getKeysEncryptCallback keyId " + keyIds[0]);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
// encrypt after getting key ids
String inputStr = mMessage.getText().toString();
OpenPgpData input = new OpenPgpData(inputStr);
Log.d(Constants.TAG, "getKeysEncryptCallback inputStr " + inputStr);
try {
mCryptoServiceConnection.getService().encrypt(input,
new OpenPgpData(OpenPgpData.TYPE_STRING), keyIds, encryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
});
}
@Override
public void onError(OpenPgpError error) throws RemoteException {
handleError(error);
}
};
final IOpenPgpKeyIdsCallback.Stub getKeysSignAndEncryptCallback = new IOpenPgpKeyIdsCallback.Stub() {
@Override
public void onSuccess(final long[] keyIds) throws RemoteException {
Log.d(Constants.TAG, "getKeysSignAndEncryptCallback keyId " + keyIds[0]);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
// encrypt after getting key ids
String inputStr = mMessage.getText().toString();
OpenPgpData input = new OpenPgpData(inputStr);
try {
mCryptoServiceConnection.getService().signAndEncrypt(input,
new OpenPgpData(OpenPgpData.TYPE_STRING), keyIds, encryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
});
}
@Override
public void onError(OpenPgpError error) throws RemoteException {
handleError(error);
}
};
final IOpenPgpCallback.Stub encryptCallback = new IOpenPgpCallback.Stub() {
@Override
public void onSuccess(final OpenPgpData output, OpenPgpSignatureResult signatureResult)
throws RemoteException {
Log.d(Constants.TAG, "encryptCallback");
runOnUiThread(new Runnable() {
@Override
public void run() {
mCiphertext.setText(output.getString());
}
});
}
@Override
public void onError(OpenPgpError error) throws RemoteException {
handleError(error);
}
};
final IOpenPgpCallback.Stub decryptAndVerifyCallback = new IOpenPgpCallback.Stub() {
@Override
public void onSuccess(final OpenPgpData output, final OpenPgpSignatureResult signatureResult)
throws RemoteException {
Log.d(Constants.TAG, "decryptAndVerifyCallback");
runOnUiThread(new Runnable() {
@Override
public void run() {
mMessage.setText(output.getString());
if (signatureResult != null) {
Toast.makeText(OpenPgpProviderActivity.this,
"signature result:\n" + signatureResult.toString(),
Toast.LENGTH_LONG).show();
}
}
});
}
@Override
public void onError(OpenPgpError error) throws RemoteException {
handleError(error);
}
};
private void handleError(final OpenPgpError error) {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mActivity,
"onError id:" + error.getErrorId() + "\n\n" + error.getMessage(),
Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
}
});
}
public void encryptOnClick(View view) {
try {
mCryptoServiceConnection.getService().getKeyIds(
mEncryptUserIds.getText().toString().split(","), true, getKeysEncryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
public void signOnClick(View view) {
String inputStr = mMessage.getText().toString();
OpenPgpData input = new OpenPgpData(inputStr);
try {
mCryptoServiceConnection.getService().sign(input,
new OpenPgpData(OpenPgpData.TYPE_STRING), encryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
public void signAndEncryptOnClick(View view) {
try {
mCryptoServiceConnection.getService().getKeyIds(
mEncryptUserIds.getText().toString().split(","), true,
getKeysSignAndEncryptCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
public void decryptAndVerifyOnClick(View view) {
String inputStr = mCiphertext.getText().toString();
OpenPgpData input = new OpenPgpData(inputStr);
try {
mCryptoServiceConnection.getService().decryptAndVerify(input,
new OpenPgpData(OpenPgpData.TYPE_STRING), decryptAndVerifyCallback);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoProviderDemo", e);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mCryptoServiceConnection != null) {
mCryptoServiceConnection.unbindFromService();
}
}
private static class OpenPgpProviderElement {
private String packageName;
private String simpleName;
private Drawable icon;
public OpenPgpProviderElement(String packageName, String simpleName, Drawable icon) {
this.packageName = packageName;
this.simpleName = simpleName;
this.icon = icon;
}
@Override
public String toString() {
return simpleName;
}
}
private void selectCryptoProvider() {
Intent intent = new Intent(IOpenPgpService.class.getName());
final ArrayList<OpenPgpProviderElement> providerList = new ArrayList<OpenPgpProviderElement>();
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 OpenPgpProviderElement(packageName, simpleName, icon));
}
}
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Select OpenPGP Provider!");
alert.setCancelable(false);
if (!providerList.isEmpty()) {
// add "disable OpenPGP provider"
providerList.add(0, new OpenPgpProviderElement(null, "Disable OpenPGP Provider",
getResources().getDrawable(android.R.drawable.ic_menu_close_clear_cancel)));
// Init ArrayAdapter with OpenPGP Providers
ListAdapter adapter = new ArrayAdapter<OpenPgpProviderElement>(this,
android.R.layout.select_dialog_item, android.R.id.text1, providerList) {
public View getView(int position, View convertView, ViewGroup parent) {
// User super class to create the View
View v = super.getView(position, convertView, parent);
TextView tv = (TextView) v.findViewById(android.R.id.text1);
// Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(providerList.get(position).icon,
null, null, null);
// Add margin between image and text (support various screen densities)
int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f);
tv.setCompoundDrawablePadding(dp5);
return v;
}
};
alert.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int position) {
String packageName = providerList.get(position).packageName;
if (packageName == null) {
dialog.cancel();
finish();
}
// bind to service
mCryptoServiceConnection = new OpenPgpServiceConnection(
OpenPgpProviderActivity.this, packageName);
mCryptoServiceConnection.bindToService();
dialog.dismiss();
}
});
} else {
alert.setMessage("No OpenPGP Provider installed!");
}
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
finish();
}
});
AlertDialog ad = alert.create();
ad.show();
}
}

View File

@ -1,40 +0,0 @@
<?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="wrap_content"
android:orientation="vertical" >
<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_keyrings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="keyrings output"
android:textAppearance="@android:style/TextAppearance.Small" />
<Button
android:id="@+id/aidl_demo_get_keyrings_strings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="getKeyringsStringsOnClick"
android:text="getKeyrings as Strings" />
<Button
android:id="@+id/aidl_demo_get_keyrings_bytes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="getKeyringsBytesOnClick"
android:text="getKeyringsBytes" />
</LinearLayout>
</ScrollView>

View File

@ -1,83 +0,0 @@
<?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"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Encrypt UserIds (split with &apos;,&apos;)"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
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="match_parent"
android:layout_height="wrap_content"
android:text="Message"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/crypto_provider_demo_message"
android:layout_width="match_parent"
android:layout_height="100dip"
android:scrollHorizontally="true"
android:scrollbars="vertical"
android:text="message"
android:textAppearance="@android:style/TextAppearance.Small" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ciphertext"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/crypto_provider_demo_ciphertext"
android:layout_width="match_parent"
android:layout_height="100dip"
android:text="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<Button
android:id="@+id/crypto_provider_demo_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="encryptOnClick"
android:text="Encrypt" />
<Button
android:id="@+id/crypto_provider_demo_sign"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="signOnClick"
android:text="Sign" />
<Button
android:id="@+id/crypto_provider_demo_encrypt_and_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="signAndEncryptOnClick"
android:text="Sign and Encrypt" />
</LinearLayout>
<Button
android:id="@+id/crypto_provider_demo_decrypt_and_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="decryptAndVerifyOnClick"
android:text="Decrypt and Verify" />
</LinearLayout>

View File

@ -1,93 +0,0 @@
<?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/Button02"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="encryptOnClick"
android:text="Encrypt" />
<Button
android:id="@+id/intent_demo_create_new_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="createNewKeyOnClick"
android:text="Create new key" />
<Button
android:id="@+id/intent_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/intent_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/intent_demo_message"
android:layout_width="match_parent"
android:layout_height="150dip"
android:text="message"
android:textAppearance="@android:style/TextAppearance.Small" />
<EditText
android:id="@+id/intent_demo_ciphertext"
android:layout_width="match_parent"
android:layout_height="150dip"
android:text="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" />
<Button
android:id="@+id/intent_demo_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="encryptOnClick"
android:text="Encrypt" />
<Button
android:id="@+id/intent_demo_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="encryptAndReturnOnClick"
android:text="Encrypt and return result" />
<Button
android:id="@+id/intent_demo_decrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="decryptOnClick"
android:text="Decrypt" />
<Button
android:id="@+id/intent_demo_decrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="decryptAndReturnOnClick"
android:text="Decrypt and return result" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="APG Data:" />
<TextView
android:id="@+id/intent_demo_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minLines="10" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,3 @@
task wrapper(type: Wrapper) {
gradleVersion = '1.10'
}

View File

@ -1,21 +1,19 @@
// please leave this here, so this library builds on its own
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:0.8.0' classpath 'com.android.tools.build:gradle:0.8.3'
} }
} }
apply plugin: 'android' apply plugin: 'android'
repositories {
mavenCentral()
}
dependencies { dependencies {
compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:support-v4:19.0.1'
compile project(':libraries:keychain-api-library')
} }
android { android {
@ -23,7 +21,7 @@ android {
buildToolsVersion "19.0.1" buildToolsVersion "19.0.1"
defaultConfig { defaultConfig {
minSdkVersion 8 minSdkVersion 9
targetSdkVersion 19 targetSdkVersion 19
} }

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -2,19 +2,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain.demo" package="org.sufficientlysecure.keychain.demo"
android:versionCode="2" android:versionCode="2"
android:versionName="1.1" > android:versionName="1.1">
<uses-sdk <uses-sdk
android:minSdkVersion="8" android:minSdkVersion="9"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="OpenPGP Keychain API Demo" > android:label="OpenKeychain API Demo">
<activity <activity
android:name=".BaseActivity" android:name=".BaseActivity"
android:label="OpenPGP Keychain API Demo" > android:label="OpenKeychain API Demo">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -26,9 +26,8 @@
android:label="OpenPGP Provider" android:label="OpenPGP Provider"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity <activity
android:name=".AidlDemoActivity2" android:name=".IntentActivity"
android:label="Aidl Demo (ACCESS_KEYS permission)" android:label="Intents" />
android:windowSoftInputMode="stateHidden" />
</application> </application>
</manifest> </manifest>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2014 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,9 +16,6 @@
package org.sufficientlysecure.keychain.demo; package org.sufficientlysecure.keychain.demo;
import org.sufficientlysecure.keychain.demo.R;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
@ -27,13 +24,8 @@ import android.preference.PreferenceActivity;
import android.widget.Toast; import android.widget.Toast;
public class BaseActivity extends PreferenceActivity { public class BaseActivity extends PreferenceActivity {
private Activity mActivity;
private Preference mIntentDemo; private Preference mIntentDemo;
private Preference mContentProviderDemo;
private Preference mCryptoProvider; private Preference mCryptoProvider;
private Preference mAidlDemo;
private Preference mAidlDemo2;
/** /**
* Called when the activity is first created. * Called when the activity is first created.
@ -42,23 +34,17 @@ public class BaseActivity extends PreferenceActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mActivity = this;
// load preferences from xml // load preferences from xml
addPreferencesFromResource(R.xml.base_preference); addPreferencesFromResource(R.xml.base_preference);
// find preferences // find preferences
mIntentDemo = (Preference) findPreference("intent_demo"); mIntentDemo = (Preference) findPreference("intent_demo");
mContentProviderDemo = (Preference) findPreference("content_provider_demo");
mCryptoProvider = (Preference) findPreference("openpgp_provider_demo"); mCryptoProvider = (Preference) findPreference("openpgp_provider_demo");
mAidlDemo = (Preference) findPreference("aidl_demo");
mAidlDemo2 = (Preference) findPreference("aidl_demo2");
mIntentDemo.setOnPreferenceClickListener(new OnPreferenceClickListener() { mIntentDemo.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
// startActivity(new Intent(mActivity, IntentDemoActivity.class)); startActivity(new Intent(BaseActivity.this, IntentActivity.class));
Toast.makeText(BaseActivity.this, "Not implemented!", Toast.LENGTH_LONG).show();
return false; return false;
} }
@ -67,21 +53,11 @@ public class BaseActivity extends PreferenceActivity {
mCryptoProvider.setOnPreferenceClickListener(new OnPreferenceClickListener() { mCryptoProvider.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(mActivity, OpenPgpProviderActivity.class)); startActivity(new Intent(BaseActivity.this, OpenPgpProviderActivity.class));
return false; return false;
} }
}); });
// mAidlDemo2.setOnPreferenceClickListener(new OnPreferenceClickListener() {
// @Override
// public boolean onPreferenceClick(Preference preference) {
// startActivity(new Intent(mActivity, AidlDemoActivity2.class));
//
// return false;
// }
// });
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2013-2014 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.
@ -17,9 +17,5 @@
package org.sufficientlysecure.keychain.demo; package org.sufficientlysecure.keychain.demo;
public final class Constants { public final class Constants {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final String TAG = "Keychain"; public static final String TAG = "Keychain";
} }

View File

@ -0,0 +1,583 @@
/*
* Copyright (C) 2014 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 android.content.ActivityNotFoundException;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import java.io.UnsupportedEncodingException;
public class IntentActivity extends PreferenceActivity {
private Preference mEncrypt;
private Preference mEncryptUri;
private Preference mDecrypt;
private Preference mImportKey;
private Preference mImportKeyFromKeyserver;
private Preference mImportKeyFromQrCode;
private Preference mOpenpgp4fpr;
private static final int REQUEST_CODE_SELECT_PHOTO = 100;
/**
* Called when the activity is first created.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// load preferences from xml
addPreferencesFromResource(R.xml.intent_preference);
// find preferences
mEncrypt = (Preference) findPreference("ENCRYPT");
mEncryptUri = (Preference) findPreference("ENCRYPT_URI");
mDecrypt = (Preference) findPreference("DECRYPT");
mImportKey = (Preference) findPreference("IMPORT_KEY");
mImportKeyFromKeyserver = (Preference) findPreference("IMPORT_KEY_FROM_KEYSERVER");
mImportKeyFromQrCode = (Preference) findPreference("IMPORT_KEY_FROM_QR_CODE");
mOpenpgp4fpr = (Preference) findPreference("openpgp4fpr");
mEncrypt.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(OpenKeychainIntents.ENCRYPT);
intent.putExtra(OpenKeychainIntents.ENCRYPT_EXTRA_TEXT, "Hello world!");
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
mEncryptUri.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
startActivityForResult(photoPickerIntent, REQUEST_CODE_SELECT_PHOTO);
return false;
}
});
mDecrypt.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(OpenKeychainIntents.DECRYPT);
intent.putExtra(OpenKeychainIntents.DECRYPT_EXTRA_TEXT, TEST_SIGNED_MESSAGE);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
mImportKey.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(OpenKeychainIntents.IMPORT_KEY);
byte[] pubkey = null;
try {
pubkey = TEST_PUBKEY.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
intent.putExtra(OpenKeychainIntents.IMPORT_KEY_EXTRA_KEY_BYTES, pubkey);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
mImportKeyFromKeyserver.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER);
intent.putExtra(OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER_QUERY, "Richard Stallman");
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
mImportKeyFromQrCode.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
mOpenpgp4fpr.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282"));
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
return false;
}
});
}
protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) {
super.onActivityResult(requestCode, resultCode, imageReturnedIntent);
switch (requestCode) {
case REQUEST_CODE_SELECT_PHOTO:
if (resultCode == RESULT_OK) {
Uri selectedImage = imageReturnedIntent.getData();
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(
selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
// TODO: after fixing DECRYPT, we could use Uri selectedImage directly
Log.d(Constants.TAG, "filePath: " + filePath);
try {
Intent intent = new Intent(OpenKeychainIntents.ENCRYPT);
Uri dataUri = Uri.parse("file://" + filePath);
Log.d(Constants.TAG, "Uri: " + dataUri);
intent.setData(dataUri);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}
}
}
}
private static final String TEST_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: SHA1\n" +
"\n" +
"Hello world!\n" +
"-----BEGIN PGP SIGNATURE-----\n" +
"Version: GnuPG v1.4.12 (GNU/Linux)\n" +
"Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/\n" +
"\n" +
"iQEcBAEBAgAGBQJS/7vTAAoJEHGMBwEAASKCkGYH/2jBLzamVyqd61jrjMQM0jUv\n" +
"MkDcPUxPrYH3wZOO0HcgdBQEo66GZEC2ATmo8izJUMk35Q5jas99k0ac9pXhPUPE\n" +
"5qDXdQS10S07R6J0SeDYFWDSyrSiDTCZpFkVu3JGP/3S0SkMYXPzfYlh8Ciuxu7i\n" +
"FR5dmIiz3VQaBgTBSCBFEomNFM5ypynBJqKIzIty8v0NbV72Rtg6Xg76YqWQ/6MC\n" +
"/MlT3y3++HhfpEmLf5WLEXljbuZ4SfCybgYXG9gBzhJu3+gmBoSicdYTZDHSxBBR\n" +
"BwI+ueLbhgRz+gU+WJFE7xNw35xKtBp1C4PR0iKI8rZCSHLjsRVzor7iwDaR51M=\n" +
"=3Ydc\n" +
"-----END PGP SIGNATURE-----";
private static final String TEST_PUBKEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: GnuPG v1.4.12 (GNU/Linux)\n" +
"\n" +
"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\n" +
"k3wUxv46o0y15asU\n" +
"=Bbew\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
}

View File

@ -0,0 +1,302 @@
/*
* Copyright (C) 2013-2014 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 android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class OpenPgpProviderActivity extends Activity {
private EditText mMessage;
private EditText mCiphertext;
private EditText mEncryptUserIds;
private Button mSign;
private Button mEncrypt;
private Button mSignAndEncrypt;
private Button mDecryptAndVerify;
private OpenPgpServiceConnection mServiceConnection;
public static final int REQUEST_CODE_SIGN = 9910;
public static final int REQUEST_CODE_ENCRYPT = 9911;
public static final int REQUEST_CODE_SIGN_AND_ENCRYPT = 9912;
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.openpgp_provider);
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext);
mEncryptUserIds = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id);
mSign = (Button) findViewById(R.id.crypto_provider_demo_sign);
mEncrypt = (Button) findViewById(R.id.crypto_provider_demo_encrypt);
mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt);
mDecryptAndVerify = (Button) findViewById(R.id.crypto_provider_demo_decrypt_and_verify);
mSign.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sign(new Bundle());
}
});
mEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(new Bundle());
}
});
mSignAndEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signAndEncrypt(new Bundle());
}
});
mDecryptAndVerify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
decryptAndVerify(new Bundle());
}
});
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
String providerPackageName = settings.getString("openpgp_provider_list", "");
if (TextUtils.isEmpty(providerPackageName)) {
Toast.makeText(this, "No OpenPGP Provider selected!", Toast.LENGTH_LONG).show();
finish();
} else {
// bind to service
mServiceConnection = new OpenPgpServiceConnection(
OpenPgpProviderActivity.this, providerPackageName);
mServiceConnection.bindToService();
}
}
private void handleError(final OpenPgpError error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OpenPgpProviderActivity.this,
"onError id:" + error.getErrorId() + "\n\n" + error.getMessage(),
Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
}
});
}
private void handleSignature(final OpenPgpSignatureResult sigResult) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(OpenPgpProviderActivity.this,
sigResult.toString(),
Toast.LENGTH_LONG).show();
}
});
}
/**
* Takes input from message or ciphertext EditText and turns it into a ByteArrayInputStream
*
* @param ciphertext
* @return
*/
private InputStream getInputstream(boolean ciphertext) {
InputStream is = null;
try {
String inputStr = null;
if (ciphertext) {
inputStr = mCiphertext.getText().toString();
} else {
inputStr = mMessage.getText().toString();
}
is = new ByteArrayInputStream(inputStr.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.e(Constants.TAG, "UnsupportedEncodingException", e);
}
return is;
}
private class MyCallback implements OpenPgpApi.IOpenPgpCallback {
boolean returnToCiphertextField;
ByteArrayOutputStream os;
int requestCode;
private MyCallback(boolean returnToCiphertextField, ByteArrayOutputStream os, int requestCode) {
this.returnToCiphertextField = returnToCiphertextField;
this.os = os;
this.requestCode = requestCode;
}
@Override
public void onReturn(Bundle result) {
switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
case OpenPgpConstants.RESULT_CODE_SUCCESS: {
try {
Log.d(OpenPgpConstants.TAG, "result: " + os.toByteArray().length
+ " str=" + os.toString("UTF-8"));
if (returnToCiphertextField) {
mCiphertext.setText(os.toString("UTF-8"));
} else {
mMessage.setText(os.toString("UTF-8"));
}
} catch (UnsupportedEncodingException e) {
Log.e(Constants.TAG, "UnsupportedEncodingException", e);
}
if (result.getBoolean(OpenPgpConstants.RESULT_SIGNATURE, false)) {
OpenPgpSignatureResult sigResult
= result.getParcelable(OpenPgpConstants.RESULT_SIGNATURE);
handleSignature(sigResult);
}
break;
}
case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED: {
PendingIntent pi = result.getParcelable(OpenPgpConstants.RESULT_INTENT);
try {
OpenPgpProviderActivity.this.startIntentSenderForResult(pi.getIntentSender(),
requestCode, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(Constants.TAG, "SendIntentException", e);
}
break;
}
case OpenPgpConstants.RESULT_CODE_ERROR: {
OpenPgpError error = result.getParcelable(OpenPgpConstants.RESULT_ERRORS);
handleError(error);
break;
}
}
}
}
public void sign(Bundle params) {
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.sign(params, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN));
}
public void encrypt(Bundle params) {
params.putStringArray(OpenPgpConstants.PARAMS_USER_IDS, mEncryptUserIds.getText().toString().split(","));
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.encrypt(params, is, os, new MyCallback(true, os, REQUEST_CODE_ENCRYPT));
}
public void signAndEncrypt(Bundle params) {
params.putStringArray(OpenPgpConstants.PARAMS_USER_IDS, mEncryptUserIds.getText().toString().split(","));
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.signAndEncrypt(params, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN_AND_ENCRYPT));
}
public void decryptAndVerify(Bundle params) {
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(true);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.decryptAndVerify(params, is, os, new MyCallback(false, os, REQUEST_CODE_DECRYPT_AND_VERIFY));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(Constants.TAG, "onActivityResult resultCode: " + resultCode);
// try again after user interaction
if (resultCode == RESULT_OK) {
/*
* The params originally given to the pgp method are are again
* returned here to be used when calling again after user interaction.
*
* They also contain results from the user interaction which happened,
* for example selected key ids.
*/
Bundle params = data.getBundleExtra(OpenPgpConstants.PI_RESULT_PARAMS);
switch (requestCode) {
case REQUEST_CODE_SIGN: {
sign(params);
break;
}
case REQUEST_CODE_ENCRYPT: {
encrypt(params);
break;
}
case REQUEST_CODE_SIGN_AND_ENCRYPT: {
signAndEncrypt(params);
break;
}
case REQUEST_CODE_DECRYPT_AND_VERIFY: {
decryptAndVerify(params);
break;
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mServiceConnection != null) {
mServiceConnection.unbindFromService();
}
}
}

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parent_scroll"
android:fillViewport="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Encrypt UserIds (split with &apos;,&apos;)"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
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="match_parent"
android:layout_height="wrap_content"
android:text="Message"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ScrollView
android:id="@+id/child_scroll1"
android:fillViewport="true"
android:layout_width="match_parent"
android:layout_height="120dp">
<EditText
android:id="@+id/crypto_provider_demo_message"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollHorizontally="true"
android:scrollbars="vertical"
android:text="message"
android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ciphertext"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ScrollView
android:id="@+id/child_scroll2"
android:fillViewport="true"
android:layout_width="match_parent"
android:layout_height="120dp">
<EditText
android:id="@+id/crypto_provider_demo_ciphertext"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/crypto_provider_demo_sign"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Sign"
android:layout_gravity="center_vertical" />
<Button
android:id="@+id/crypto_provider_demo_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Encrypt"
android:layout_gravity="center_vertical" />
<Button
android:id="@+id/crypto_provider_demo_sign_and_encrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Sign and Encrypt"
android:layout_gravity="center_vertical" />
</LinearLayout>
<Button
android:id="@+id/crypto_provider_demo_decrypt_and_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Decrypt and Verify" />
</LinearLayout>
</ScrollView>

View File

@ -1,23 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Intent" > <PreferenceCategory android:title="OpenKeychain Intents">
<Preference <Preference
android:key="intent_demo" android:key="intent_demo"
android:title="Intent Demo" /> android:title="Intent Demo" />
</PreferenceCategory> </PreferenceCategory>
<!-- <PreferenceCategory android:title="AIDL" > --> <PreferenceCategory android:title="OpenPGP Provider API">
<!-- <Preference --> <org.openintents.openpgp.util.OpenPgpListPreference
<!-- android:key="aidl_demo2" -->
<!-- android:title="AIDL Demo (ACCESS_KEYS permission)" /> -->
<!-- </PreferenceCategory> -->
<PreferenceCategory android:title="OpenPGP Provider" >
<org.openintents.openpgp.OpenPgpListPreference
android:key="openpgp_provider_list" android:key="openpgp_provider_list"
android:title="Select OpenPGP Provider!" /> android:title="Select OpenPGP Provider!" />
<Preference <Preference
android:key="openpgp_provider_demo" android:key="openpgp_provider_demo"
android:title="OpenPGP Provider" /> android:title="OpenPGP Provider Demo" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Intents (org.sufficientlysecure.keychain.action.)">
<Preference
android:key="ENCRYPT"
android:title="ENCRYPT" />
<Preference
android:key="ENCRYPT_URI"
android:title="ENCRYPT with Uri" />
<Preference
android:key="DECRYPT"
android:title="DECRYPT" />
<Preference
android:key="IMPORT_KEY"
android:title="IMPORT_KEY" />
<Preference
android:key="IMPORT_KEY_FROM_KEYSERVER"
android:title="IMPORT_KEY_FROM_KEYSERVER" />
<Preference
android:key="IMPORT_KEY_FROM_QR_CODE"
android:title="IMPORT_KEY_FROM_QR_CODE" />
</PreferenceCategory>
<PreferenceCategory android:title="Special Intents">
<Preference
android:key="openpgp4fpr"
android:title="VIEW openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -1,6 +1,6 @@
#Mon Jan 27 14:45:08 CET 2014 #Sun Feb 09 19:19:25 CET 2014
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Fri Feb 14 01:26:40 CET 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip

164
OpenPGP-Keychain-API/gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
OpenPGP-Keychain-API/gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,29 @@
#Android specific
bin
gen
obj
lint.xml
local.properties
release.properties
ant.properties
*.class
*.apk
#Gradle
.gradle
build
gradle.properties
#Maven
target
pom.xml.*
#Eclipse
.project
.classpath
.settings
.metadata
#IntelliJ IDEA
.idea
*.iml

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain.api"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<application/>
</manifest>

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,35 @@
// please leave this here, so this library builds on its own
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.3'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion '19.0.1'
// NOTE: We are using the old folder structure to also support Eclipse
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
// Do not abort build if lint finds errors
lintOptions {
abortOnError false
}
}

View File

@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="openpgp_list_preference_none">None</string>
</resources>

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2014 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.openpgp;
interface IOpenPgpService {
/**
* General extras
* --------------
*
* Bundle params:
* int api_version (required)
* boolean ascii_armor (request ascii armor for ouput)
*
* returned Bundle:
* int result_code (0, 1, or 2 (see OpenPgpConstants))
* OpenPgpError error (if result_code == 0)
* Intent intent (if result_code == 2)
*
*/
/**
* Sign only
*
* optional params:
* String passphrase (for key passphrase)
*/
Bundle sign(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Encrypt
*
* Bundle params:
* long[] key_ids
* or
* String[] user_ids (= emails of recipients) (if more than one key has this user_id, a PendingIntent is returned)
*
* optional params:
* String passphrase (for key passphrase)
*/
Bundle encrypt(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Sign and encrypt
*
* Bundle params:
* same as in encrypt()
*/
Bundle signAndEncrypt(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* returned Bundle:
* OpenPgpSignatureResult signature_result
*/
Bundle decryptAndVerify(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Retrieves key ids based on given user ids (=emails)
*
* Bundle params:
* String[] user_ids
*
* returned Bundle:
* long[] key_ids
*/
Bundle getKeyIds(in Bundle params);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 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.
@ -20,10 +20,13 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
public class OpenPgpError implements Parcelable { public class OpenPgpError implements Parcelable {
public static final int CLIENT_SIDE_ERROR = -1;
public static final int GENERIC_ERROR = 0; public static final int GENERIC_ERROR = 0;
public static final int NO_OR_WRONG_PASSPHRASE = 1; public static final int INCOMPATIBLE_API_VERSIONS = 1;
public static final int NO_USER_IDS = 2;
public static final int USER_INTERACTION_REQUIRED = 3; public static final int NO_OR_WRONG_PASSPHRASE = 2;
public static final int NO_USER_IDS = 3;
int errorId; int errorId;
String message; String message;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 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.
@ -22,14 +22,14 @@ import android.os.Parcelable;
public class OpenPgpSignatureResult implements Parcelable { public class OpenPgpSignatureResult implements Parcelable {
// generic error on signature verification // generic error on signature verification
public static final int SIGNATURE_ERROR = 0; public static final int SIGNATURE_ERROR = 0;
// successfully verified signature, with trusted public key // successfully verified signature, with certified public key
public static final int SIGNATURE_SUCCESS_TRUSTED = 1; public static final int SIGNATURE_SUCCESS_CERTIFIED = 1;
// no public key was found for this signature verification // no public key was found for this signature verification
// you can retrieve the key with // you can retrieve the key with
// getKeys(new String[] {String.valueOf(signatureResult.getKeyId)}, true, callback) // getKeys(new String[] {String.valueOf(signatureResult.getKeyId)}, true, callback)
public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2; public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2;
// successfully verified signature, but with untrusted public key // successfully verified signature, but with certified public key
public static final int SIGNATURE_SUCCESS_UNTRUSTED = 3; public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3;
int status; int status;
boolean signatureOnly; boolean signatureOnly;

View File

@ -0,0 +1,198 @@
/*
* Copyright (C) 2014 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.openpgp.util;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import java.io.InputStream;
import java.io.OutputStream;
public class OpenPgpApi {
IOpenPgpService mService;
Context mContext;
private static final int OPERATION_SIGN = 0;
private static final int OPERATION_ENCRYPT = 1;
private static final int OPERATION_SIGN_ENCRYPT = 2;
private static final int OPERATION_DECRYPT_VERIFY = 3;
private static final int OPERATION_GET_KEY_IDS = 4;
public OpenPgpApi(Context context, IOpenPgpService service) {
this.mContext = context;
this.mService = service;
}
public Bundle sign(InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN, new Bundle(), is, os);
}
public Bundle sign(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN, params, is, os);
}
public void sign(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_SIGN, params, is, os, callback);
}
public Bundle encrypt(InputStream is, final OutputStream os) {
return executeApi(OPERATION_ENCRYPT, new Bundle(), is, os);
}
public Bundle encrypt(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_ENCRYPT, params, is, os);
}
public void encrypt(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_ENCRYPT, params, is, os, callback);
}
public Bundle signAndEncrypt(InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN_ENCRYPT, new Bundle(), is, os);
}
public Bundle signAndEncrypt(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN_ENCRYPT, params, is, os);
}
public void signAndEncrypt(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_SIGN_ENCRYPT, params, is, os, callback);
}
public Bundle decryptAndVerify(InputStream is, final OutputStream os) {
return executeApi(OPERATION_DECRYPT_VERIFY, new Bundle(), is, os);
}
public Bundle decryptAndVerify(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_DECRYPT_VERIFY, params, is, os);
}
public void decryptAndVerify(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_DECRYPT_VERIFY, params, is, os, callback);
}
public Bundle getKeyIds(Bundle params) {
return executeApi(OPERATION_GET_KEY_IDS, params, null, null);
}
public interface IOpenPgpCallback {
void onReturn(final Bundle result);
}
private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Bundle> {
int operationId;
Bundle params;
InputStream is;
OutputStream os;
IOpenPgpCallback callback;
private OpenPgpAsyncTask(int operationId, Bundle params, InputStream is, OutputStream os, IOpenPgpCallback callback) {
this.operationId = operationId;
this.params = params;
this.is = is;
this.os = os;
this.callback = callback;
}
@Override
protected Bundle doInBackground(Void... unused) {
return executeApi(operationId, params, is, os);
}
protected void onPostExecute(Bundle result) {
callback.onReturn(result);
}
}
private void executeApiAsync(int operationId, Bundle params, InputStream is, OutputStream os, IOpenPgpCallback callback) {
new OpenPgpAsyncTask(operationId, params, is, os, callback).execute((Void[]) null);
}
private Bundle executeApi(int operationId, Bundle params, InputStream is, OutputStream os) {
try {
params.putInt(OpenPgpConstants.PARAMS_API_VERSION, OpenPgpConstants.API_VERSION);
Bundle result = null;
if (operationId == OPERATION_GET_KEY_IDS) {
result = mService.getKeyIds(params);
return result;
} else {
// send the input and output pfds
ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is,
new ParcelFileDescriptorUtil.IThreadListener() {
@Override
public void onThreadFinished(Thread thread) {
Log.d(OpenPgpConstants.TAG, "Copy to service finished");
}
});
ParcelFileDescriptor output = ParcelFileDescriptorUtil.pipeTo(os,
new ParcelFileDescriptorUtil.IThreadListener() {
@Override
public void onThreadFinished(Thread thread) {
Log.d(OpenPgpConstants.TAG, "Service finished writing!");
}
});
// blocks until result is ready
switch (operationId) {
case OPERATION_SIGN:
result = mService.sign(params, input, output);
break;
case OPERATION_ENCRYPT:
result = mService.encrypt(params, input, output);
break;
case OPERATION_SIGN_ENCRYPT:
result = mService.signAndEncrypt(params, input, output);
break;
case OPERATION_DECRYPT_VERIFY:
result = mService.decryptAndVerify(params, input, output);
break;
}
// close() is required to halt the TransferThread
output.close();
// set class loader to current context to allow unparcelling
// of OpenPgpError and OpenPgpSignatureResult
// http://stackoverflow.com/a/3806769
result.setClassLoader(mContext.getClassLoader());
return result;
}
} catch (Exception e) {
Log.e(OpenPgpConstants.TAG, "Exception", e);
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage()));
return result;
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2014 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.openpgp.util;
public class OpenPgpConstants {
public static final String TAG = "OpenPgp API";
public static final int API_VERSION = 1;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
/* Bundle params */
public static final String PARAMS_API_VERSION = "api_version";
// request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
public static final String PARAMS_REQUEST_ASCII_ARMOR = "ascii_armor";
// (for encrypt method)
public static final String PARAMS_USER_IDS = "user_ids";
public static final String PARAMS_KEY_IDS = "key_ids";
// optional parameter:
public static final String PARAMS_PASSPHRASE = "passphrase";
/* Service Bundle returns */
public static final String RESULT_CODE = "result_code";
public static final String RESULT_SIGNATURE = "signature";
public static final String RESULT_ERRORS = "error";
public static final String RESULT_INTENT = "intent";
// get actual error object from RESULT_ERRORS
public static final int RESULT_CODE_ERROR = 0;
// success!
public static final int RESULT_CODE_SUCCESS = 1;
// executeServiceMethod intent and do it again with params from intent
public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2;
/* PendingIntent returns */
public static final String PI_RESULT_PARAMS = "params";
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 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.
@ -14,18 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package org.openintents.openpgp; package org.openintents.openpgp.util;
import java.util.ArrayList;
import java.util.List;
import android.app.AlertDialog.Builder; import android.app.AlertDialog.Builder;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.preference.DialogPreference; import android.preference.DialogPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -35,33 +31,21 @@ import android.widget.ArrayAdapter;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import org.sufficientlysecure.keychain.api.R;
/**
* Does not extend ListPreference, but is very similar to it!
* http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source
*/
public class OpenPgpListPreference extends DialogPreference { public class OpenPgpListPreference extends DialogPreference {
ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>(); private ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
private String mSelectedPackage; private String mSelectedPackage;
public OpenPgpListPreference(Context context, AttributeSet attrs) { public OpenPgpListPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(
new Intent(OpenPgpConstants.SERVICE_INTENT), PackageManager.GET_META_DATA);
if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) {
if (resolveInfo.serviceInfo == null)
continue;
String packageName = resolveInfo.serviceInfo.packageName;
String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(context
.getPackageManager()));
Drawable icon = resolveInfo.serviceInfo.loadIcon(context.getPackageManager());
// get api version
ServiceInfo si = resolveInfo.serviceInfo;
int apiVersion = si.metaData.getInt("api_version");
mProviderList.add(new OpenPgpProviderEntry(packageName, simpleName, icon,
apiVersion));
}
}
} }
public OpenPgpListPreference(Context context) { public OpenPgpListPreference(Context context) {
@ -69,20 +53,42 @@ public class OpenPgpListPreference extends DialogPreference {
} }
/** /**
* Can be used to add "no selection" * Public method to add new entries for legacy applications
* *
* @param packageName * @param packageName
* @param simpleName * @param simpleName
* @param icon * @param icon
*/ */
public void addProvider(int position, String packageName, String simpleName, Drawable icon, public void addProvider(int position, String packageName, String simpleName, Drawable icon) {
int apiVersion) { mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon,
apiVersion));
} }
@Override @Override
protected void onPrepareDialogBuilder(Builder builder) { protected void onPrepareDialogBuilder(Builder builder) {
// get providers
mProviderList.clear();
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
List<ResolveInfo> resInfo = getContext().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(getContext()
.getPackageManager()));
Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager());
mProviderList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
}
}
// add "none"-entry
mProviderList.add(0, new OpenPgpProviderEntry("",
getContext().getString(R.string.openpgp_list_preference_none),
getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
// Init ArrayAdapter with OpenPGP Providers // Init ArrayAdapter with OpenPGP Providers
ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(), ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
android.R.layout.select_dialog_singlechoice, android.R.id.text1, mProviderList) { android.R.layout.select_dialog_singlechoice, android.R.id.text1, mProviderList) {
@ -99,15 +105,6 @@ public class OpenPgpListPreference extends DialogPreference {
int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f); int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
tv.setCompoundDrawablePadding(dp10); tv.setCompoundDrawablePadding(dp10);
// disable if it has the wrong api_version
if (mProviderList.get(position).apiVersion == OpenPgpConstants.REQUIRED_API_VERSION) {
tv.setEnabled(true);
} else {
tv.setEnabled(false);
tv.setText(tv.getText() + " (API v" + mProviderList.get(position).apiVersion
+ ", needs v" + OpenPgpConstants.REQUIRED_API_VERSION + ")");
}
return v; return v;
} }
}; };
@ -169,6 +166,16 @@ public class OpenPgpListPreference extends DialogPreference {
return getEntryByValue(mSelectedPackage); return getEntryByValue(mSelectedPackage);
} }
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setValue(restoreValue ? getPersistedString(mSelectedPackage) : (String) defaultValue);
}
public String getEntryByValue(String packageName) { public String getEntryByValue(String packageName) {
for (OpenPgpProviderEntry app : mProviderList) { for (OpenPgpProviderEntry app : mProviderList) {
if (app.packageName.equals(packageName)) { if (app.packageName.equals(packageName)) {
@ -183,14 +190,11 @@ public class OpenPgpListPreference extends DialogPreference {
private String packageName; private String packageName;
private String simpleName; private String simpleName;
private Drawable icon; private Drawable icon;
private int apiVersion;
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
int apiVersion) {
this.packageName = packageName; this.packageName = packageName;
this.simpleName = simpleName; this.simpleName = simpleName;
this.icon = icon; this.icon = icon;
this.apiVersion = apiVersion;
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2014 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.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.openintents.openpgp; package org.openintents.openpgp.util;
import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.IOpenPgpService;
@ -23,18 +23,17 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log;
public class OpenPgpServiceConnection { public class OpenPgpServiceConnection {
private Context mApplicationContext; private Context mApplicationContext;
private IOpenPgpService mService;
private boolean mBound; private boolean mBound;
private String mCryptoProviderPackageName; private IOpenPgpService mService;
private String mProviderPackageName;
public OpenPgpServiceConnection(Context context, String cryptoProviderPackageName) { public OpenPgpServiceConnection(Context context, String providerPackageName) {
this.mApplicationContext = context.getApplicationContext(); this.mApplicationContext = context.getApplicationContext();
this.mCryptoProviderPackageName = cryptoProviderPackageName; this.mProviderPackageName = providerPackageName;
} }
public IOpenPgpService getService() { public IOpenPgpService getService() {
@ -45,49 +44,45 @@ public class OpenPgpServiceConnection {
return mBound; return mBound;
} }
private ServiceConnection mCryptoServiceConnection = new ServiceConnection() { private ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
mService = IOpenPgpService.Stub.asInterface(service); mService = IOpenPgpService.Stub.asInterface(service);
Log.d(OpenPgpConstants.TAG, "connected to service");
mBound = true; mBound = true;
} }
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
mService = null; mService = null;
Log.d(OpenPgpConstants.TAG, "disconnected from service");
mBound = false; mBound = false;
} }
}; };
/** /**
* If not already bound, bind! * If not already bound, bind to service!
* *
* @return * @return
*/ */
public boolean bindToService() { public boolean bindToService() {
if (mService == null && !mBound) { // if not already connected // if not already bound...
if (mService == null && !mBound) {
try { try {
Log.d(OpenPgpConstants.TAG, "not bound yet");
Intent serviceIntent = new Intent(); Intent serviceIntent = new Intent();
serviceIntent.setAction(IOpenPgpService.class.getName()); serviceIntent.setAction(IOpenPgpService.class.getName());
serviceIntent.setPackage(mCryptoProviderPackageName); // NOTE: setPackage is very important to restrict the intent to this provider only!
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, serviceIntent.setPackage(mProviderPackageName);
mApplicationContext.bindService(serviceIntent, mServiceConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.d(OpenPgpConstants.TAG, "Exception on binding", e);
return false; return false;
} }
} else { } else {
Log.d(OpenPgpConstants.TAG, "already bound");
return true; return true;
} }
} }
public void unbindFromService() { public void unbindFromService() {
mApplicationContext.unbindService(mCryptoServiceConnection); mApplicationContext.unbindService(mServiceConnection);
} }
} }

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2014 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.openpgp.util;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
public class OpenPgpUtils {
public static final Pattern PGP_MESSAGE = Pattern.compile(
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
Pattern.DOTALL);
public static final Pattern PGP_SIGNED_MESSAGE = Pattern.compile(
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
Pattern.DOTALL);
public static final int PARSE_RESULT_NO_PGP = -1;
public static final int PARSE_RESULT_MESSAGE = 0;
public static final int PARSE_RESULT_SIGNED_MESSAGE = 1;
public static int parseMessage(String message) {
Matcher matcherSigned = PGP_SIGNED_MESSAGE.matcher(message);
Matcher matcherMessage = PGP_MESSAGE.matcher(message);
if (matcherMessage.matches()) {
return PARSE_RESULT_MESSAGE;
} else if (matcherSigned.matches()) {
return PARSE_RESULT_SIGNED_MESSAGE;
} else {
return PARSE_RESULT_NO_PGP;
}
}
public static boolean isAvailable(Context context) {
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) {
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* 2013 Flow (http://stackoverflow.com/questions/18212152/transfer-inputstream-to-another-service-across-process-boundaries-with-parcelf)
*
* 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.openpgp.util;
import android.os.ParcelFileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ParcelFileDescriptorUtil {
public interface IThreadListener {
void onThreadFinished(final Thread thread);
}
public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener)
throws IOException {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];
// start the transfer thread
new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
listener)
.start();
return readSide;
}
public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener)
throws IOException {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];
// start the transfer thread
new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream,
listener)
.start();
return writeSide;
}
static class TransferThread extends Thread {
final InputStream mIn;
final OutputStream mOut;
final IThreadListener mListener;
TransferThread(InputStream in, OutputStream out, IThreadListener listener) {
super("ParcelFileDescriptor Transfer Thread");
mIn = in;
mOut = out;
mListener = listener;
setDaemon(true);
}
@Override
public void run() {
byte[] buf = new byte[1024];
int len;
try {
while ((len = mIn.read(buf)) > 0) {
mOut.write(buf, 0, len);
}
mOut.flush(); // just to be safe
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId() + ": writing failed", e);
} finally {
try {
mIn.close();
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId(), e);
}
try {
mOut.close();
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId(), e);
}
}
if (mListener != null) {
//Log.d(OpenPgpConstants.TAG, "TransferThread " + getId() + " finished!");
mListener.onThreadFinished(this);
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2014 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.api;
public class OpenKeychainIntents {
public static final String ENCRYPT = "org.sufficientlysecure.keychain.action.ENCRYPT";
public static final String ENCRYPT_EXTRA_TEXT = "text"; // String
public static final String ENCRYPT_ASCII_ARMOR = "ascii_armor"; // boolean
public static final String DECRYPT = "org.sufficientlysecure.keychain.action.DECRYPT";
public static final String DECRYPT_EXTRA_TEXT = "text"; // String
public static final String IMPORT_KEY = "org.sufficientlysecure.keychain.action.IMPORT_KEY";
public static final String IMPORT_KEY_EXTRA_KEY_BYTES = "key_bytes"; // byte[]
public static final String IMPORT_KEY_FROM_KEYSERVER = "org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_KEYSERVER";
public static final String IMPORT_KEY_FROM_KEYSERVER_QUERY = "query"; // String
public static final String IMPORT_KEY_FROM_KEYSERVER_FINGERPRINT = "fingerprint"; // String
public static final String IMPORT_KEY_FROM_QR_CODE = "org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE";
}

View File

@ -0,0 +1,2 @@
include ':example-app'
include ':libraries:keychain-api-library'

View File

@ -3,6 +3,7 @@ apply plugin: 'android'
dependencies { dependencies {
compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:support-v4:19.0.1'
compile 'com.android.support:appcompat-v7:19.0.1' compile 'com.android.support:appcompat-v7:19.0.1'
compile project(':libraries:keychain-api-library')
compile project(':libraries:HtmlTextView') compile project(':libraries:HtmlTextView')
compile project(':libraries:StickyListHeaders:library') compile project(':libraries:StickyListHeaders:library')
compile project(':libraries:AndroidBootstrap') compile project(':libraries:AndroidBootstrap')
@ -12,6 +13,7 @@ dependencies {
compile project(':libraries:spongycastle:pg') compile project(':libraries:spongycastle:pg')
compile project(':libraries:spongycastle:pkix') compile project(':libraries:spongycastle:pkix')
compile project(':libraries:spongycastle:prov') compile project(':libraries:spongycastle:prov')
compile project(':libraries:Android-AppMsg:library')
} }
android { android {
@ -19,7 +21,7 @@ android {
buildToolsVersion "19.0.1" buildToolsVersion "19.0.1"
defaultConfig { defaultConfig {
minSdkVersion 8 minSdkVersion 9
targetSdkVersion 19 targetSdkVersion 19
} }

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain" package="org.sufficientlysecure.keychain"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="22001" android:versionCode="23100"
android:versionName="2.2"> android:versionName="2.3.1">
<!-- <!--
General remarks General remarks
@ -30,7 +30,7 @@
--> -->
<uses-sdk <uses-sdk
android:minSdkVersion="8" android:minSdkVersion="9"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<uses-feature <uses-feature
@ -153,12 +153,19 @@
android:windowSoftInputMode="stateHidden"> android:windowSoftInputMode="stateHidden">
<!-- Keychain's own Actions --> <!-- Keychain's own Actions -->
<!-- ENCRYPT with text as extra -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" /> <action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- ENCRYPT with data Uri -->
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
<data android:mimeType="*/*" /> <category android:name="android.intent.category.DEFAULT" />
<!-- TODO: accept other schemes! -->
<data android:scheme="file" />
</intent-filter> </intent-filter>
<!-- Android's Send Action --> <!-- Android's Send Action -->
<intent-filter android:label="@string/intent_send_encrypt"> <intent-filter android:label="@string/intent_send_encrypt">
@ -175,13 +182,40 @@
android:label="@string/title_decrypt" android:label="@string/title_decrypt"
android:windowSoftInputMode="stateHidden"> android:windowSoftInputMode="stateHidden">
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />-->
<!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-signature" />-->
<!--</intent-filter>-->
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />-->
<!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-encrypted" />-->
<!--</intent-filter>-->
<!-- Keychain's own Actions --> <!-- Keychain's own Actions -->
<!-- DECRYPT with text as extra -->
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" /> <action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- DECRYPT with data Uri -->
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
<data android:mimeType="*/*" /> <category android:name="android.intent.category.DEFAULT" />
<!-- TODO: accept other schemes! -->
<data android:scheme="file" />
</intent-filter> </intent-filter>
<!-- Android's Send Action --> <!-- Android's Send Action -->
<intent-filter android:label="@string/intent_send_decrypt"> <intent-filter android:label="@string/intent_send_decrypt">
@ -249,17 +283,19 @@
android:label="@string/title_key_server_preference" android:label="@string/title_key_server_preference"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity <activity
android:name=".ui.SignKeyActivity" android:name=".ui.CertifyKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_sign_key" /> android:label="@string/title_certify_key" />
<activity <activity
android:name=".ui.ImportKeysActivity" android:name=".ui.ImportKeysActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_import_keys" android:label="@string/title_import_keys"
android:launchMode="singleTop" android:launchMode="singleTop"
android:windowSoftInputMode="stateHidden"> android:windowSoftInputMode="stateHidden">
<!-- Handle URIs with fingerprints when scanning directly from Barcode Scanner --> <!-- VIEW with fingerprint scheme:
<intent-filter> Handle URIs with fingerprints when scanning directly from Barcode Scanner -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@ -272,31 +308,25 @@
<data android:scheme="OpenPGP4Fpr" /> <data android:scheme="OpenPGP4Fpr" />
<data android:scheme="OpenPGP4fpr" /> <data android:scheme="OpenPGP4fpr" />
</intent-filter> </intent-filter>
<!-- Handle NFC tags detected from outside our application --> <!-- VIEW with mimeType: Allows to import keys (attached to emails) from email apps -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
<data android:mimeType="application/pgp-keys" />
</intent-filter>
<!-- NFC: Handle NFC tags detected from outside our application -->
<intent-filter> <intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" /> <action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156, section 7 --> <!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
<data android:mimeType="application/pgp-keys" /> <data android:mimeType="application/pgp-keys" />
</intent-filter> </intent-filter>
<!-- Keychain's own Actions --> <!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<!-- IMPORT again without mimeType to also allow data only without filename -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE" />
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_KEY_SERVER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- Linking "Import key" to file types -->
<intent-filter android:label="@string/intent_import_key"> <intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -317,6 +347,7 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
</intent-filter> </intent-filter>
<!-- VIEW with file endings: *.asc -->
<intent-filter android:label="@string/intent_import_key"> <intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -338,6 +369,31 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
</intent-filter> </intent-filter>
<!-- Keychain's own Actions -->
<!-- IMPORT_KEY with files TODO: does this work? -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<!-- IMPORT_KEY with mimeType 'application/pgp-keys' -->
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
<category android:name="android.intent.category.DEFAULT" />
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156, section 7 -->
<data android:mimeType="application/pgp-keys" />
</intent-filter>
<!-- IMPORT_KEY without mimeType to allow import with extras Bundle -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY" />
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE" />
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_KEYSERVER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".ui.HelpActivity" android:name=".ui.HelpActivity"
@ -361,10 +417,9 @@
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity" android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
android:exported="false" android:exported="false"
android:label="@string/app_name" android:label="@string/app_name" />
android:launchMode="singleTop" <!--android:launchMode="singleTop"-->
android:process=":remote_api" <!--android:process=":remote_api"-->
android:taskAffinity=":remote_api" />
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity" android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -391,19 +446,19 @@
</service> </service>
<!-- Extended Remote API --> <!-- Extended Remote API -->
<service <!--<service-->
android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService" <!--android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService"-->
android:enabled="true" <!--android:enabled="true"-->
android:exported="true" <!--android:exported="true"-->
android:process=":remote_api"> <!--android:process=":remote_api">-->
<intent-filter> <!--<intent-filter>-->
<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" /> <!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />-->
</intent-filter> <!--</intent-filter>-->
<meta-data <!--<meta-data-->
android:name="api_version" <!--android:name="api_version"-->
android:value="1" /> <!--android:value="1" />-->
</service> <!--</service>-->
<!-- TODO: authority! Make this API with content provider uris --> <!-- TODO: authority! Make this API with content provider uris -->
<!-- <provider --> <!-- <provider -->

View File

@ -1,45 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpCallback {
/**
* onSuccess returns on successful OpenPGP operations.
*
* @param output
* contains resulting output (decrypted content (when input was encrypted)
* or content without signature (when input was signed-only))
* @param signatureResult
* signatureResult is only non-null if decryptAndVerify() was called and the content
* was encrypted or signed-and-encrypted.
*/
oneway void onSuccess(in OpenPgpData output, in OpenPgpSignatureResult signatureResult);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,39 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpError;
interface IOpenPgpKeyIdsCallback {
/**
* onSuccess returns on successful getKeyIds operations.
*
* @param keyIds
* returned key ids
*/
oneway void onSuccess(in long[] keyIds);
/**
* onError returns on errors or when allowUserInteraction was set to false, but user interaction
* was required execute an OpenPGP operation.
*
* @param error
* See OpenPgpError class for more information.
*/
oneway void onError(in OpenPgpError error);
}

View File

@ -1,143 +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.openintents.openpgp;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.IOpenPgpCallback;
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
/**
* 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 IOpenPgpService {
/**
* Sign
*
* After successful signing, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void sign(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Encrypt
*
* After successful encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void encrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Sign then encrypt
*
* After successful signing and encryption, callback's onSuccess will contain the resulting output.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param keyIds
* Key Ids of recipients. Can be retrieved with getKeyIds()
* @param callback
* Callback where to return results
*/
oneway void signAndEncrypt(in OpenPgpData input, in OpenPgpData output, in long[] keyIds, in IOpenPgpCallback callback);
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* After successful decryption/verification, callback's onSuccess will contain the resulting output.
* The signatureResult in onSuccess is only non-null if signed-and-encrypted or signed-only inputBytes were given.
*
* @param input
* OpenPgpData object containing String, byte[], ParcelFileDescriptor, or Uri
* @param output
* Request output format by defining OpenPgpData object
*
* new OpenPgpData(OpenPgpData.TYPE_STRING)
* Returns as String
* (OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
* new OpenPgpData(OpenPgpData.TYPE_BYTE_ARRAY)
* Returns as byte[]
* new OpenPgpData(uri)
* Writes output to given Uri
* new OpenPgpData(fileDescriptor)
* Writes output to given ParcelFileDescriptor
* @param callback
* Callback where to return results
*/
oneway void decryptAndVerify(in OpenPgpData input, in OpenPgpData output, in IOpenPgpCallback callback);
/**
* Get available key ids based on given user ids
*
* @param ids
* User Ids (emails) of recipients OR key ids
* @param allowUserInteraction
* Enable user interaction to lookup and import unknown keys
* @param callback
* Callback where to return results (different type than callback in other functions!)
*/
oneway void getKeyIds(in String[] ids, in boolean allowUserInteraction, in IOpenPgpKeyIdsCallback callback);
}

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpData so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpData;

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpError so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpError;

View File

@ -1,20 +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.openintents.openpgp;
// Declare OpenPgpSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
parcelable OpenPgpSignatureResult;

View File

@ -1,10 +0,0 @@
package org.openintents.openpgp;
public class OpenPgpConstants {
public static final String TAG = "OpenPgp API";
public static final int REQUIRED_API_VERSION = 1;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
}

View File

@ -1,127 +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.openintents.openpgp;
import android.net.Uri;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
public class OpenPgpData implements Parcelable {
public static final int TYPE_STRING = 0;
public static final int TYPE_BYTE_ARRAY = 1;
public static final int TYPE_FILE_DESCRIPTOR = 2;
public static final int TYPE_URI = 3;
int type;
String string;
byte[] bytes = new byte[0];
ParcelFileDescriptor fileDescriptor;
Uri uri;
public int getType() {
return type;
}
public String getString() {
return string;
}
public byte[] getBytes() {
return bytes;
}
public ParcelFileDescriptor getFileDescriptor() {
return fileDescriptor;
}
public Uri getUri() {
return uri;
}
public OpenPgpData() {
}
/**
* Not a real constructor. This can be used to define requested output type.
*
* @param type
*/
public OpenPgpData(int type) {
this.type = type;
}
public OpenPgpData(String string) {
this.string = string;
this.type = TYPE_STRING;
}
public OpenPgpData(byte[] bytes) {
this.bytes = bytes;
this.type = TYPE_BYTE_ARRAY;
}
public OpenPgpData(ParcelFileDescriptor fileDescriptor) {
this.fileDescriptor = fileDescriptor;
this.type = TYPE_FILE_DESCRIPTOR;
}
public OpenPgpData(Uri uri) {
this.uri = uri;
this.type = TYPE_URI;
}
public OpenPgpData(OpenPgpData b) {
this.string = b.string;
this.bytes = b.bytes;
this.fileDescriptor = b.fileDescriptor;
this.uri = b.uri;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(string);
dest.writeInt(bytes.length);
dest.writeByteArray(bytes);
dest.writeParcelable(fileDescriptor, 0);
dest.writeParcelable(uri, 0);
}
public static final Creator<OpenPgpData> CREATOR = new Creator<OpenPgpData>() {
public OpenPgpData createFromParcel(final Parcel source) {
OpenPgpData vr = new OpenPgpData();
vr.type = source.readInt();
vr.string = source.readString();
vr.bytes = new byte[source.readInt()];
source.readByteArray(vr.bytes);
vr.fileDescriptor = source.readParcelable(ParcelFileDescriptor.class.getClassLoader());
vr.fileDescriptor = source.readParcelable(Uri.class.getClassLoader());
return vr;
}
public OpenPgpData[] newArray(final int size) {
return new OpenPgpData[size];
}
};
}

View File

@ -1,52 +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.openintents.openpgp;
import java.util.List;
import java.util.regex.Pattern;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
public class OpenPgpHelper {
private Context context;
public static Pattern PGP_MESSAGE = Pattern.compile(
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
public static Pattern PGP_SIGNED_MESSAGE = Pattern
.compile(
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
Pattern.DOTALL);
public OpenPgpHelper(Context context) {
super();
this.context = context;
}
public boolean isAvailable() {
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) {
return true;
} else {
return false;
}
}
}

View File

@ -78,7 +78,7 @@ public final class Id {
public static final int filename = 0x00007003; public static final int filename = 0x00007003;
// public static final int output_filename = 0x00007004; // public static final int output_filename = 0x00007004;
public static final int key_server_preference = 0x00007005; public static final int key_server_preference = 0x00007005;
public static final int look_up_key_id = 0x00007006; // public static final int look_up_key_id = 0x00007006;
public static final int export_to_server = 0x00007007; public static final int export_to_server = 0x00007007;
public static final int import_from_qr_code = 0x00007008; public static final int import_from_qr_code = 0x00007008;
public static final int sign_key = 0x00007009; public static final int sign_key = 0x00007009;

View File

@ -0,0 +1,782 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* 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.pgp;
import android.content.Context;
import android.os.Bundle;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
import org.spongycastle.openpgp.PGPPBEEncryptedData;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SignatureException;
import java.util.Iterator;
/**
* This class uses a Builder pattern!
*/
public class PgpDecryptVerify {
private Context context;
private InputData data;
private OutputStream outStream;
private ProgressDialogUpdater progress;
boolean assumeSymmetric;
String passphrase;
private PgpDecryptVerify(Builder builder) {
// private Constructor can only be called from Builder
this.context = builder.context;
this.data = builder.data;
this.outStream = builder.outStream;
this.progress = builder.progress;
this.assumeSymmetric = builder.assumeSymmetric;
this.passphrase = builder.passphrase;
}
public static class Builder {
// mandatory parameter
private Context context;
private InputData data;
private OutputStream outStream;
// optional
private ProgressDialogUpdater progress = null;
private boolean assumeSymmetric = false;
private String passphrase = "";
public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context;
this.data = data;
this.outStream = outStream;
}
public Builder progress(ProgressDialogUpdater progress) {
this.progress = progress;
return this;
}
public Builder assumeSymmetric(boolean assumeSymmetric) {
this.assumeSymmetric = assumeSymmetric;
return this;
}
public Builder passphrase(String passphrase) {
this.passphrase = passphrase;
return this;
}
public PgpDecryptVerify build() {
return new PgpDecryptVerify(this);
}
}
public void updateProgress(int message, int current, int total) {
if (progress != null) {
progress.setProgress(message, current, total);
}
}
public void updateProgress(int current, int total) {
if (progress != null) {
progress.setProgress(current, total);
}
}
public static boolean hasSymmetricEncryption(Context context, InputStream inputStream)
throws PgpGeneralException, IOException {
InputStream in = PGPUtil.getDecoderStream(inputStream);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
// the first object might be a PGP marker packet.
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
if (enc == null) {
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
}
Iterator<?> it = enc.getEncryptedDataObjects();
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof PGPPBEEncryptedData) {
return true;
}
}
return false;
}
/**
* Decrypts and/or verifies data based on parameters of class
*
* @return
* @throws IOException
* @throws PgpGeneralException
* @throws PGPException
* @throws SignatureException
*/
public Bundle execute()
throws IOException, PgpGeneralException, PGPException, SignatureException {
// automatically works with ascii armor input and binary
InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
return verifyCleartextSignature(aIn);
}
// else: ascii armored encryption! go on...
}
return decryptVerify(in);
}
/**
* Decrypt and/or verifies binary or ascii armored pgp
*
* @param in
* @return
* @throws IOException
* @throws PgpGeneralException
* @throws PGPException
* @throws SignatureException
*/
private Bundle decryptVerify(InputStream in)
throws IOException, PgpGeneralException, PGPException, SignatureException {
Bundle returnData = new Bundle();
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
int currentProgress = 0;
updateProgress(R.string.progress_reading_data, currentProgress, 100);
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
if (enc == null) {
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
}
InputStream clear;
PGPEncryptedData encryptedData;
currentProgress += 5;
// TODO: currently we always only look at the first known key or symmetric encryption,
// there might be more...
if (assumeSymmetric) {
PGPPBEEncryptedData pbe = null;
Iterator<?> it = enc.getEncryptedDataObjects();
// find secret key
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof PGPPBEEncryptedData) {
pbe = (PGPPBEEncryptedData) obj;
break;
}
}
if (pbe == null) {
throw new PgpGeneralException(
context.getString(R.string.error_no_symmetric_encryption_packet));
}
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray());
clear = pbe.getDataStream(decryptorFactory);
encryptedData = pbe;
currentProgress += 5;
} else {
updateProgress(R.string.progress_finding_key, currentProgress, 100);
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKey secretKey = null;
Iterator<?> it = enc.getEncryptedDataObjects();
// find secret key
while (it.hasNext()) {
Object obj = it.next();
if (obj instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID());
if (secretKey != null) {
pbe = encData;
break;
}
}
}
if (secretKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found));
}
currentProgress += 5;
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
PGPPrivateKey privateKey = null;
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray());
privateKey = secretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) {
throw new PGPException(context.getString(R.string.error_wrong_passphrase));
}
if (privateKey == null) {
throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key));
}
currentProgress += 5;
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
clear = pbe.getDataStream(decryptorFactory);
encryptedData = pbe;
currentProgress += 5;
}
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
Object dataChunk = plainFact.nextObject();
PGPOnePassSignature signature = null;
PGPPublicKey signatureKey = null;
int signatureIndex = -1;
if (dataChunk instanceof PGPCompressedData) {
updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
PGPObjectFactory fact = new PGPObjectFactory(
((PGPCompressedData) dataChunk).getDataStream());
dataChunk = fact.nextObject();
plainFact = fact;
currentProgress += 10;
}
long signatureKeyId = 0;
if (dataChunk instanceof PGPOnePassSignatureList) {
updateProgress(R.string.progress_processing_signature, currentProgress, 100);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
for (int i = 0; i < sigList.size(); ++i) {
signature = sigList.get(i);
signatureKey = ProviderHelper
.getPGPPublicKeyByKeyId(context, signature.getKeyID());
if (signatureKeyId == 0) {
signatureKeyId = signature.getKeyID();
}
if (signatureKey == null) {
signature = null;
} else {
signatureIndex = i;
signatureKeyId = signature.getKeyID();
String userId = null;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
context, signatureKeyId);
if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
}
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
break;
}
}
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
if (signature != null) {
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signature.init(contentVerifierBuilderProvider, signatureKey);
} else {
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
}
dataChunk = plainFact.nextObject();
currentProgress += 10;
}
if (dataChunk instanceof PGPSignatureList) {
dataChunk = plainFact.nextObject();
}
if (dataChunk instanceof PGPLiteralData) {
updateProgress(R.string.progress_decrypting, currentProgress, 100);
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
byte[] buffer = new byte[1 << 16];
InputStream dataIn = literalData.getInputStream();
int startProgress = currentProgress;
int endProgress = 100;
if (signature != null) {
endProgress = 90;
} else if (encryptedData.isIntegrityProtected()) {
endProgress = 95;
}
int n;
// TODO: progress calculation is broken here! Try to rework it based on commented code!
// int progress = 0;
long startPos = data.getStreamPosition();
while ((n = dataIn.read(buffer)) > 0) {
outStream.write(buffer, 0, n);
// progress += n;
if (signature != null) {
try {
signature.update(buffer, 0, n);
} catch (SignatureException e) {
returnData
.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
signature = null;
}
}
// TODO: dead code?!
// unknown size, but try to at least have a moving, slowing down progress bar
// currentProgress = startProgress + (endProgress - startProgress) * progress
// / (progress + 100000);
if (data.getSize() - startPos == 0) {
currentProgress = endProgress;
} else {
currentProgress = (int) (startProgress + (endProgress - startProgress)
* (data.getStreamPosition() - startPos) / (data.getSize() - startPos));
}
updateProgress(currentProgress, 100);
}
if (signature != null) {
updateProgress(R.string.progress_verifying_signature, 90, 100);
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
// these are not cleartext signatures!
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
//Now check binding signatures
boolean keyBinding_isok = verifyKeyBinding(context, messageSignature, signatureKey);
boolean sig_isok = signature.verify(messageSignature);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok);
}
}
// TODO: test if this integrity really check works!
if (encryptedData.isIntegrityProtected()) {
updateProgress(R.string.progress_verifying_integrity, 95, 100);
if (encryptedData.verify()) {
// passed
Log.d(Constants.TAG, "Integrity verification: success!");
} else {
// failed
Log.d(Constants.TAG, "Integrity verification: failed!");
throw new PgpGeneralException(context.getString(R.string.error_integrity_check_failed));
}
} else {
// no integrity check
Log.e(Constants.TAG, "Encrypted data was not integrity protected!");
}
updateProgress(R.string.progress_done, 100, 100);
return returnData;
}
/**
* This method verifies cleartext signatures
* as defined in http://tools.ietf.org/html/rfc4880#section-7
* <p/>
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*
* @return
* @throws IOException
* @throws PgpGeneralException
* @throws PGPException
* @throws SignatureException
*/
private Bundle verifyCleartextSignature(ArmoredInputStream aIn)
throws IOException, PgpGeneralException, PGPException, SignatureException {
Bundle returnData = new Bundle();
// cleartext signatures are never encrypted ;)
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
updateProgress(R.string.progress_done, 0, 100);
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, aIn);
byte[] lineSep = getLineSeparator();
byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparator(line));
out.write(lineSep);
while (lookAhead != -1 && aIn.isClearText()) {
lookAhead = readInputLine(lineOut, lookAhead, aIn);
line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparator(line));
out.write(lineSep);
}
out.close();
byte[] clearText = out.toByteArray();
outStream.write(clearText);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
updateProgress(R.string.progress_processing_signature, 60, 100);
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
if (sigList == null) {
throw new PgpGeneralException(context.getString(R.string.error_corrupt_data));
}
PGPSignature signature = null;
long signatureKeyId = 0;
PGPPublicKey signatureKey = null;
for (int i = 0; i < sigList.size(); ++i) {
signature = sigList.get(i);
signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID());
if (signatureKeyId == 0) {
signatureKeyId = signature.getKeyID();
}
if (signatureKey == null) {
signature = null;
} else {
signatureKeyId = signature.getKeyID();
String userId = null;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
signatureKeyId);
if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
}
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
break;
}
}
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
if (signature == null) {
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
updateProgress(R.string.progress_done, 100, 100);
return returnData;
}
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signature.init(contentVerifierBuilderProvider, signatureKey);
InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
lookAhead = readInputLine(lineOut, sigIn);
processLine(signature, lineOut.toByteArray());
if (lookAhead != -1) {
do {
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
signature.update((byte) '\r');
signature.update((byte) '\n');
processLine(signature, lineOut.toByteArray());
} while (lookAhead != -1);
}
boolean sig_isok = signature.verify();
//Now check binding signatures
boolean keyBinding_isok = verifyKeyBinding(context, signature, signatureKey);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok);
updateProgress(R.string.progress_done, 100, 100);
return returnData;
}
private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) {
long signatureKeyId = signature.getKeyID();
boolean keyBinding_isok = false;
String userId = null;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
signatureKeyId);
PGPPublicKey mKey = null;
if (signKeyRing != null) {
mKey = PgpKeyHelper.getMasterKey(signKeyRing);
}
if (signature.getKeyID() != mKey.getKeyID()) {
keyBinding_isok = verifyKeyBinding(mKey, signatureKey);
} else { //if the key used to make the signature was the master key, no need to check binding sigs
keyBinding_isok = true;
}
return keyBinding_isok;
}
private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean subkeyBinding_isok = false;
boolean tmp_subkeyBinding_isok = false;
boolean primkeyBinding_isok = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
Iterator<PGPSignature> itr = signingPublicKey.getSignatures();
subkeyBinding_isok = false;
tmp_subkeyBinding_isok = false;
primkeyBinding_isok = false;
while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong?
//gpg has an invalid subkey binding error on key import I think, but doesn't shout
//about keys without subkey signing. Can't get it to import a slightly broken one
//either, so we will err on bad subkey binding here.
PGPSignature sig = itr.next();
if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
//check and if ok, check primary key binding.
try {
sig.init(contentVerifierBuilderProvider, masterPublicKey);
tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey);
} catch (PGPException e) {
continue;
} catch (SignatureException e) {
continue;
}
if (tmp_subkeyBinding_isok)
subkeyBinding_isok = true;
if (tmp_subkeyBinding_isok) {
primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
break;
primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
break;
}
}
}
return (subkeyBinding_isok & primkeyBinding_isok);
}
private static boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean primkeyBinding_isok = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureList eSigList;
if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) {
try {
eSigList = Pkts.getEmbeddedSignatures();
} catch (IOException e) {
return false;
} catch (PGPException e) {
return false;
}
for (int j = 0; j < eSigList.size(); ++j) {
PGPSignature emSig = eSigList.get(j);
if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
try {
emSig.init(contentVerifierBuilderProvider, signingPublicKey);
primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
break;
} catch (PGPException e) {
continue;
} catch (SignatureException e) {
continue;
}
}
}
}
return primkeyBinding_isok;
}
/**
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
*
* @param sig
* @param line
* @throws SignatureException
* @throws IOException
*/
private static void processLine(PGPSignature sig, byte[] line)
throws SignatureException, IOException {
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sig.update(line, 0, length);
}
}
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException {
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0) {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
return lookAhead;
}
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
throws IOException {
bOut.reset();
int ch = lookAhead;
do {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
} while ((ch = fIn.read()) >= 0);
if (ch < 0) {
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
throws IOException {
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n') {
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
private static int getLengthWithoutSeparator(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isLineEnding(line[end])) {
end--;
}
return end + 1;
}
private static boolean isLineEnding(byte b) {
return b == '\r' || b == '\n';
}
private static int getLengthWithoutWhiteSpace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isWhiteSpace(byte b) {
return b == '\r' || b == '\n' || b == '\t' || b == ' ';
}
private static byte[] getLineSeparator() {
String nl = System.getProperty("line.separator");
byte[] nlBytes = new byte[nl.length()];
for (int i = 0; i != nlBytes.length; i++) {
nlBytes[i] = (byte) nl.charAt(i);
}
return nlBytes;
}
}

View File

@ -42,6 +42,8 @@ import android.content.Context;
public class PgpKeyHelper { public class PgpKeyHelper {
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
public static Date getCreationDate(PGPPublicKey key) { public static Date getCreationDate(PGPPublicKey key) {
return key.getCreationTime(); return key.getCreationTime();
} }
@ -591,8 +593,7 @@ public class PgpKeyHelper {
* "Max Mustermann (this is a comment)" * "Max Mustermann (this is a comment)"
* "Max Mustermann [this is nothing]" * "Max Mustermann [this is nothing]"
*/ */
Pattern withComment = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); Matcher matcher = USER_ID_PATTERN.matcher(userId);
Matcher matcher = withComment.matcher(userId);
if (matcher.matches()) { if (matcher.matches()) {
result[0] = matcher.group(1); result[0] = matcher.group(1);
result[1] = matcher.group(3); result[1] = matcher.group(3);

View File

@ -504,7 +504,7 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }
public PGPPublicKeyRing signKey(long masterKeyId, long pubKeyId, String passphrase) public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase)
throws PgpGeneralException, PGPException, SignatureException { throws PgpGeneralException, PGPException, SignatureException {
if (passphrase == null) { if (passphrase == null) {
throw new PgpGeneralException("Unable to obtain passphrase"); throw new PgpGeneralException("Unable to obtain passphrase");
@ -512,14 +512,14 @@ public class PgpKeyOperation {
PGPPublicKeyRing pubring = ProviderHelper PGPPublicKeyRing pubring = ProviderHelper
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId); .getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
if (signingKey == null) { if (certificationKey == null) {
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
} }
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) { if (signaturePrivateKey == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
mContext.getString(R.string.error_could_not_extract_private_key)); mContext.getString(R.string.error_could_not_extract_private_key));
@ -527,7 +527,7 @@ public class PgpKeyOperation {
// TODO: SHA256 fixed? // TODO: SHA256 fixed?
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(

View File

@ -0,0 +1,605 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* 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.pgp;
import android.content.Context;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.util.Date;
/**
* This class uses a Builder pattern!
*/
public class PgpSignEncrypt {
private Context context;
private InputData data;
private OutputStream outStream;
private ProgressDialogUpdater progress;
private boolean enableAsciiArmorOutput;
private int compressionId;
private long[] encryptionKeyIds;
private String encryptionPassphrase;
private int symmetricEncryptionAlgorithm;
private long signatureKeyId;
private int signatureHashAlgorithm;
private boolean signatureForceV3;
private String signaturePassphrase;
private PgpSignEncrypt(Builder builder) {
// private Constructor can only be called from Builder
this.context = builder.context;
this.data = builder.data;
this.outStream = builder.outStream;
this.progress = builder.progress;
this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput;
this.compressionId = builder.compressionId;
this.encryptionKeyIds = builder.encryptionKeyIds;
this.encryptionPassphrase = builder.encryptionPassphrase;
this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm;
this.signatureKeyId = builder.signatureKeyId;
this.signatureHashAlgorithm = builder.signatureHashAlgorithm;
this.signatureForceV3 = builder.signatureForceV3;
this.signaturePassphrase = builder.signaturePassphrase;
}
public static class Builder {
// mandatory parameter
private Context context;
private InputData data;
private OutputStream outStream;
// optional
private ProgressDialogUpdater progress = null;
private boolean enableAsciiArmorOutput = false;
private int compressionId = Id.choice.compression.none;
private long[] encryptionKeyIds = new long[0];
private String encryptionPassphrase = null;
private int symmetricEncryptionAlgorithm = 0;
private long signatureKeyId = Id.key.none;
private int signatureHashAlgorithm = 0;
private boolean signatureForceV3 = false;
private String signaturePassphrase = null;
public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context;
this.data = data;
this.outStream = outStream;
}
public Builder progress(ProgressDialogUpdater progress) {
this.progress = progress;
return this;
}
public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
this.enableAsciiArmorOutput = enableAsciiArmorOutput;
return this;
}
public Builder compressionId(int compressionId) {
this.compressionId = compressionId;
return this;
}
public Builder encryptionKeyIds(long[] encryptionKeyIds) {
this.encryptionKeyIds = encryptionKeyIds;
return this;
}
public Builder encryptionPassphrase(String encryptionPassphrase) {
this.encryptionPassphrase = encryptionPassphrase;
return this;
}
public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this;
}
public Builder signatureKeyId(long signatureKeyId) {
this.signatureKeyId = signatureKeyId;
return this;
}
public Builder signatureHashAlgorithm(int signatureHashAlgorithm) {
this.signatureHashAlgorithm = signatureHashAlgorithm;
return this;
}
public Builder signatureForceV3(boolean signatureForceV3) {
this.signatureForceV3 = signatureForceV3;
return this;
}
public Builder signaturePassphrase(String signaturePassphrase) {
this.signaturePassphrase = signaturePassphrase;
return this;
}
public PgpSignEncrypt build() {
return new PgpSignEncrypt(this);
}
}
public void updateProgress(int message, int current, int total) {
if (progress != null) {
progress.setProgress(message, current, total);
}
}
public void updateProgress(int current, int total) {
if (progress != null) {
progress.setProgress(current, total);
}
}
/**
* Signs and/or encrypts data based on parameters of class
*
* @throws IOException
* @throws PgpGeneralException
* @throws PGPException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws SignatureException
*/
public void execute()
throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
NoSuchAlgorithmException, SignatureException {
boolean enableSignature = signatureKeyId != Id.key.none;
boolean enableEncryption = (encryptionKeyIds.length != 0 || encryptionPassphrase != null);
boolean enableCompression = (enableEncryption && compressionId != Id.choice.compression.none);
Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption
+ "\nenableCompression:" + enableCompression
+ "\nenableAsciiArmorOutput:" + enableAsciiArmorOutput);
int signatureType;
if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
// for sign-only ascii text
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
} else {
signatureType = PGPSignature.BINARY_DOCUMENT;
}
ArmoredOutputStream armorOut = null;
OutputStream out;
if (enableAsciiArmorOutput) {
armorOut = new ArmoredOutputStream(outStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
out = armorOut;
} else {
out = outStream;
}
/* Get keys for signature generation for later usage */
PGPSecretKey signingKey = null;
PGPSecretKeyRing signingKeyRing = null;
PGPPrivateKey signaturePrivateKey = null;
if (enableSignature) {
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
if (signingKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_signature_failed));
}
if (signaturePassphrase == null) {
throw new PgpGeneralException(
context.getString(R.string.error_no_signature_passphrase));
}
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key));
}
}
updateProgress(R.string.progress_preparing_streams, 5, 100);
/* Initialize PGPEncryptedDataGenerator for later usage */
PGPEncryptedDataGenerator cPk = null;
if (enableEncryption) {
// has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
.setWithIntegrityPacket(true);
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
if (encryptionKeyIds.length == 0) {
// Symmetric encryption
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray());
cPk.addMethod(symmetricEncryptionGenerator);
} else {
// Asymmetric encryption
for (long id : encryptionKeyIds) {
PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id);
if (key != null) {
JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator =
new JcePublicKeyKeyEncryptionMethodGenerator(key);
cPk.addMethod(pubKeyEncryptionGenerator);
}
}
}
}
/* Initialize signature generator object for later usage */
PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null;
if (enableSignature) {
updateProgress(R.string.progress_preparing_signature, 10, 100);
// content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
if (signatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(signatureType, signaturePrivateKey);
} else {
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(signatureType, signaturePrivateKey);
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, userId);
signatureGenerator.setHashedSubpackets(spGen.generate());
}
}
PGPCompressedDataGenerator compressGen = null;
OutputStream pOut;
OutputStream encryptionOut = null;
BCPGOutputStream bcpgOut;
if (enableEncryption) {
/* actual encryption */
encryptionOut = cPk.open(out, new byte[1 << 16]);
if (enableCompression) {
compressGen = new PGPCompressedDataGenerator(compressionId);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
} else {
bcpgOut = new BCPGOutputStream(encryptionOut);
}
if (enableSignature) {
if (signatureForceV3) {
signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
} else {
signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
}
}
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
// file name not needed, so empty string
pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(),
new byte[1 << 16]);
updateProgress(R.string.progress_encrypting, 20, 100);
long progress = 0;
int n;
byte[] buffer = new byte[1 << 16];
InputStream in = data.getInputStream();
while ((n = in.read(buffer)) > 0) {
pOut.write(buffer, 0, n);
// update signature buffer if signature is requested
if (enableSignature) {
if (signatureForceV3) {
signatureV3Generator.update(buffer, 0, n);
} else {
signatureGenerator.update(buffer, 0, n);
}
}
progress += n;
if (data.getSize() != 0) {
updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100);
}
}
literalGen.close();
} else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
/* sign-only of ascii text */
updateProgress(R.string.progress_signing, 40, 100);
// write directly on armor output stream
armorOut.beginClearText(signatureHashAlgorithm);
InputStream in = data.getInputStream();
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
final byte[] newline = "\r\n".getBytes("UTF-8");
if (signatureForceV3) {
processLine(reader.readLine(), armorOut, signatureV3Generator);
} else {
processLine(reader.readLine(), armorOut, signatureGenerator);
}
while (true) {
String line = reader.readLine();
if (line == null) {
armorOut.write(newline);
break;
}
armorOut.write(newline);
// update signature buffer with input line
if (signatureForceV3) {
signatureV3Generator.update(newline);
processLine(line, armorOut, signatureV3Generator);
} else {
signatureGenerator.update(newline);
processLine(line, armorOut, signatureGenerator);
}
}
armorOut.endClearText();
pOut = new BCPGOutputStream(armorOut);
} else {
// TODO: implement sign-only for files!
pOut = null;
Log.e(Constants.TAG, "not supported!");
}
if (enableSignature) {
updateProgress(R.string.progress_generating_signature, 95, 100);
if (signatureForceV3) {
signatureV3Generator.generate().encode(pOut);
} else {
signatureGenerator.generate().encode(pOut);
}
}
// closing outputs
// NOTE: closing needs to be done in the correct order!
// TODO: closing bcpgOut and pOut???
if (enableEncryption) {
if (enableCompression) {
compressGen.close();
}
encryptionOut.close();
}
if (enableAsciiArmorOutput) {
armorOut.close();
}
out.close();
outStream.close();
updateProgress(R.string.progress_done, 100, 100);
}
// TODO: merge this into execute method!
// TODO: allow binary input for this class
public void generateSignature()
throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException,
SignatureException {
OutputStream out;
if (enableAsciiArmorOutput) {
// Ascii Armor (Radix-64)
ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
out = armorOut;
} else {
out = outStream;
}
if (signatureKeyId == 0) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_key));
}
PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
if (signingKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_signature_failed));
}
if (signaturePassphrase == null) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase));
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key));
}
updateProgress(R.string.progress_preparing_streams, 0, 100);
updateProgress(R.string.progress_preparing_signature, 30, 100);
int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
// if (binary) {
// type = PGPSignature.BINARY_DOCUMENT;
// }
// content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null;
if (signatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(type, signaturePrivateKey);
} else {
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(type, signaturePrivateKey);
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
spGen.setSignerUserID(false, userId);
signatureGenerator.setHashedSubpackets(spGen.generate());
}
updateProgress(R.string.progress_signing, 40, 100);
InputStream inStream = data.getInputStream();
// if (binary) {
// byte[] buffer = new byte[1 << 16];
// int n = 0;
// while ((n = inStream.read(buffer)) > 0) {
// if (signatureForceV3) {
// signatureV3Generator.update(buffer, 0, n);
// } else {
// signatureGenerator.update(buffer, 0, n);
// }
// }
// } else {
final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
final byte[] newline = "\r\n".getBytes("UTF-8");
String line;
while ((line = reader.readLine()) != null) {
if (signatureForceV3) {
processLine(line, null, signatureV3Generator);
signatureV3Generator.update(newline);
} else {
processLine(line, null, signatureGenerator);
signatureGenerator.update(newline);
}
}
// }
BCPGOutputStream bOut = new BCPGOutputStream(out);
if (signatureForceV3) {
signatureV3Generator.generate().encode(bOut);
} else {
signatureGenerator.generate().encode(bOut);
}
out.close();
outStream.close();
updateProgress(R.string.progress_done, 100, 100);
}
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
final PGPSignatureGenerator pSignatureGenerator)
throws IOException, SignatureException {
if (pLine == null) {
return;
}
final char[] chars = pLine.toCharArray();
int len = chars.length;
while (len > 0) {
if (!Character.isWhitespace(chars[len - 1])) {
break;
}
len--;
}
final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
if (pArmoredOutput != null) {
pArmoredOutput.write(data);
}
pSignatureGenerator.update(data);
}
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
final PGPV3SignatureGenerator pSignatureGenerator)
throws IOException, SignatureException {
if (pLine == null) {
return;
}
final char[] chars = pLine.toCharArray();
int len = chars.length;
while (len > 0) {
if (!Character.isWhitespace(chars[len - 1])) {
break;
}
len--;
}
final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
if (pArmoredOutput != null) {
pArmoredOutput.write(data);
}
pSignatureGenerator.update(data);
}
}

View File

@ -110,7 +110,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
// Upgrade from oldVersion through all methods to newest one // Upgrade from oldVersion through all cases to newest one
for (int version = oldVersion; version < newVersion; ++version) { for (int version = oldVersion; version < newVersion; ++version) {
Log.w(Constants.TAG, "Upgrading database to version " + version); Log.w(Constants.TAG, "Upgrading database to version " + version);
@ -123,14 +123,17 @@ public class KeychainDatabase extends SQLiteOpenHelper {
break; break;
case 4: case 4:
db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS);
break;
case 5: case 5:
// new column: package_signature // new column: package_signature
db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS); db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS);
break;
case 6: case 6:
// new column: fingerprint // new column: fingerprint
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
+ " BLOB;"); + " BLOB;");
break;
default: default:
break; break;

View File

@ -359,7 +359,9 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID); projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID);
// TODO: deprecated master key id // TODO: deprecated master key id
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT); projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);
projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);

View File

@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
@ -210,6 +211,13 @@ public class ProviderHelper {
++userIdRank; ++userIdRank;
} }
for (PGPSignature certification : new IterableIterator<PGPSignature>(masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) {
//TODO: how to do this?? we need to verify the signatures again and again when they are displayed...
// if (certification.verify
// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
}
try { try {
context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
} catch (RemoteException e) { } catch (RemoteException e) {
@ -562,6 +570,26 @@ public class ProviderHelper {
return fingerprint; return fingerprint;
} }
public static String getUserId(Context context, Uri queryUri) {
String[] projection = new String[]{UserIds.USER_ID};
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
String userId = null;
try {
if (cursor != null && cursor.moveToFirst()) {
int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
userId = cursor.getString(col);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return userId;
}
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri, public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
long[] masterKeyIds) { long[] masterKeyIds) {
ArrayList<String> output = new ArrayList<String>(); ArrayList<String> output = new ArrayList<String>();

View File

@ -43,10 +43,11 @@ import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpImportExport; import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
@ -95,7 +96,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING"; public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING"; public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
public static final String ACTION_SIGN_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
/* keys for data bundle */ /* keys for data bundle */
@ -119,11 +120,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String ENCRYPT_PROVIDER_URI = "provider_uri"; public static final String ENCRYPT_PROVIDER_URI = "provider_uri";
// decrypt/verify // decrypt/verify
public static final String DECRYPT_SIGNED_ONLY = "signed_only";
public static final String DECRYPT_RETURN_BYTES = "return_binary"; public static final String DECRYPT_RETURN_BYTES = "return_binary";
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric"; public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey";
// save keyring // save keyring
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase"; public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
@ -166,8 +165,8 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String DOWNLOAD_KEY_LIST = "query_key_id"; public static final String DOWNLOAD_KEY_LIST = "query_key_id";
// sign key // sign key
public static final String SIGN_KEY_MASTER_KEY_ID = "sign_key_master_key_id"; public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
public static final String SIGN_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
/* /*
* possible data keys as result send over messenger * possible data keys as result send over messenger
@ -189,10 +188,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String RESULT_SIGNATURE = "signature"; public static final String RESULT_SIGNATURE = "signature";
public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id"; public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id";
public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id"; public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id";
public static final String RESULT_CLEARTEXT_SIGNATURE_ONLY = "signature_only";
public static final String RESULT_SIGNATURE_SUCCESS = "signature_success"; public static final String RESULT_SIGNATURE_SUCCESS = "signature_success";
public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown"; public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown";
public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key";
// import // import
public static final String RESULT_IMPORT_ADDED = "added"; public static final String RESULT_IMPORT_ADDED = "added";
@ -241,7 +240,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String action = intent.getAction(); String action = intent.getAction();
// execute action from extra bundle // executeServiceMethod action from extra bundle
if (ACTION_ENCRYPT_SIGN.equals(action)) { if (ACTION_ENCRYPT_SIGN.equals(action)) {
try { try {
/* Input */ /* Input */
@ -322,27 +321,41 @@ public class KeychainIntentService extends IntentService implements ProgressDial
} }
/* Operation */ /* Operation */
PgpOperation operation = new PgpOperation(this, this, inputData, outStream); PgpSignEncrypt.Builder builder =
new PgpSignEncrypt.Builder(this, inputData, outStream);
builder.progress(this);
if (generateSignature) { if (generateSignature) {
Log.d(Constants.TAG, "generating signature..."); Log.d(Constants.TAG, "generating signature...");
operation.generateSignature(useAsciiArmor, false, secretKeyId, builder.enableAsciiArmorOutput(useAsciiArmor)
PassphraseCacheService.getCachedPassphrase(this, secretKeyId), .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences .signatureKeyId(secretKeyId)
.getPreferences(this).getForceV3Signatures()); .signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().generateSignature();
} else if (signOnly) { } else if (signOnly) {
Log.d(Constants.TAG, "sign only..."); Log.d(Constants.TAG, "sign only...");
operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase( builder.enableAsciiArmorOutput(useAsciiArmor)
this, secretKeyId), Preferences.getPreferences(this) .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.getDefaultHashAlgorithm(), Preferences.getPreferences(this) .signatureKeyId(secretKeyId)
.getForceV3Signatures()); .signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute();
} else { } else {
Log.d(Constants.TAG, "encrypt..."); Log.d(Constants.TAG, "encrypt...");
operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds, builder.enableAsciiArmorOutput(useAsciiArmor)
encryptionPassphrase, Preferences.getPreferences(this) .compressionId(compressionId)
.getDefaultEncryptionAlgorithm(), secretKeyId, Preferences .symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
.getPreferences(this).getDefaultHashAlgorithm(), Preferences .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.getPreferences(this).getForceV3Signatures(), .encryptionKeyIds(encryptionKeyIds)
PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); .encryptionPassphrase(encryptionPassphrase)
.signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute();
} }
outStream.close(); outStream.close();
@ -395,12 +408,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID); long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES); byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY);
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES); boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC); boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY);
InputStream inStream = null; InputStream inStream = null;
long inLength = -1; long inLength = -1;
InputData inputData = null; InputData inputData = null;
@ -474,14 +484,13 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// verifyText and decrypt returning additional resultData values for the // verifyText and decrypt returning additional resultData values for the
// verification of signatures // verification of signatures
PgpOperation operation = new PgpOperation(this, this, inputData, outStream); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
if (signedOnly) { builder.progress(this);
resultData = operation.verifyText(lookupUnknownKey);
} else { builder.assumeSymmetric(assumeSymmetricEncryption)
resultData = operation.decryptAndVerify( .passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
assumeSymmetricEncryption); resultData = builder.build().execute();
}
outStream.close(); outStream.close();
@ -785,19 +794,19 @@ public class KeychainIntentService extends IntentService implements ProgressDial
} catch (Exception e) { } catch (Exception e) {
sendErrorToHandler(e); sendErrorToHandler(e);
} }
} else if (ACTION_SIGN_KEYRING.equals(action)) { } else if (ACTION_CERTIFY_KEYRING.equals(action)) {
try { try {
/* Input */ /* Input */
long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID); long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID); long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
/* Operation */ /* Operation */
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
masterKeyId); masterKeyId);
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this); PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId, PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
signaturePassPhrase); signaturePassPhrase);
// store the signed key in our local cache // store the signed key in our local cache

View File

@ -25,6 +25,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.widget.Toast; import android.widget.Toast;
public class KeychainIntentServiceHandler extends Handler { public class KeychainIntentServiceHandler extends Handler {
@ -60,7 +61,14 @@ public class KeychainIntentServiceHandler extends Handler {
} }
public void showProgressDialog(FragmentActivity activity) { public void showProgressDialog(FragmentActivity activity) {
mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog"); // TODO: This is a hack!, see http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
final FragmentManager manager = activity.getSupportFragmentManager();
Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
mProgressDialogFragment.show(manager, "progressDialog");
}
});
} }
@Override @Override

View File

@ -1,10 +0,0 @@
package org.sufficientlysecure.keychain.service.exception;
public class NoUserIdsException extends Exception {
private static final long serialVersionUID = 7009311527126696207L;
public NoUserIdsException(String message) {
super(message);
}
}

View File

@ -1,10 +0,0 @@
package org.sufficientlysecure.keychain.service.exception;
public class UserInteractionRequiredException extends Exception {
private static final long serialVersionUID = -60128148603511936L;
public UserInteractionRequiredException(String message) {
super(message);
}
}

View File

@ -1,10 +0,0 @@
package org.sufficientlysecure.keychain.service.exception;
public class WrongPassphraseException extends Exception {
private static final long serialVersionUID = -5309689232853485740L;
public WrongPassphraseException(String message) {
super(message);
}
}

View File

@ -109,6 +109,15 @@ public class AppSettingsFragment extends Fragment implements
return view; return view;
} }
/**
* Set error String on key selection
*
* @param error
*/
public void setErrorOnSelectKeyFragment(String error) {
mSelectKeyFragment.setError(error);
}
private void initView(View view) { private void initView(View view) {
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById( mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
R.id.api_app_settings_select_key_fragment); R.id.api_app_settings_select_key_fragment);
@ -182,7 +191,7 @@ public class AppSettingsFragment extends Fragment implements
// TODO: Better: collapse/expand animation // TODO: Better: collapse/expand animation
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, // 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, Animation.RELATIVE_TO_SELF, -1.0f,
// Animation.RELATIVE_TO_SELF, 0.0f); // Animation.RELATIVE_TO_SELF, 0.0f);u
// animation2.setDuration(150); // animation2.setDuration(150);
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() { mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -17,99 +17,38 @@
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.service.remote;
import java.io.ByteArrayInputStream; import android.app.PendingIntent;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.regex.Matcher;
import org.openintents.openpgp.IOpenPgpCallback;
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpData;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpOperation;
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.exception.NoUserIdsException;
import org.sufficientlysecure.keychain.service.exception.UserInteractionRequiredException;
import org.sufficientlysecure.keychain.service.exception.WrongPassphraseException;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.Message; import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
public class OpenPgpService extends RemoteService { public class OpenPgpService extends RemoteService {
private String getCachedPassphrase(long keyId, boolean allowUserInteraction) private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
throws UserInteractionRequiredException { private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
if (passphrase == null) {
if (!allowUserInteraction) {
throw new UserInteractionRequiredException(
"Passphrase not found in cache, please enter your passphrase!");
}
Log.d(Constants.TAG, "No passphrase! Activity required!");
// start passphrase dialog
PassphraseActivityCallback callback = new PassphraseActivityCallback();
Bundle extras = new Bundle();
extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, callback,
extras);
if (callback.isSuccess()) {
Log.d(Constants.TAG, "New passphrase entered!");
// get again after it was entered
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
} else {
Log.d(Constants.TAG, "Passphrase dialog canceled!");
return null;
}
}
return passphrase;
}
public class PassphraseActivityCallback extends UserInputCallback {
private boolean success = false;
public boolean isSuccess() {
return success;
}
@Override
public void handleUserInput(Message msg) {
if (msg.arg1 == OKAY) {
success = true;
} else {
success = false;
}
}
};
/** /**
* Search database for key ids based on emails. * Search database for key ids based on emails.
@ -117,8 +56,7 @@ public class OpenPgpService extends RemoteService {
* @param encryptionUserIds * @param encryptionUserIds
* @return * @return
*/ */
private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction) private Bundle getKeyIdsFromEmails(Bundle params, String[] encryptionUserIds) {
throws UserInteractionRequiredException {
// find key ids to given emails in database // find key ids to given emails in database
ArrayList<Long> keyIds = new ArrayList<Long>(); ArrayList<Long> keyIds = new ArrayList<Long>();
@ -152,96 +90,129 @@ public class OpenPgpService extends RemoteService {
} }
// allow the user to verify pub key selection // allow the user to verify pub key selection
if (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) { if (missingUserIdsCheck || dublicateUserIdsCheck) {
SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback(); // build PendingIntent for passphrase input
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
Bundle extras = new Bundle(); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
extras.putLongArray(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
extras.putStringArrayList(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
extras.putStringArrayList(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS,
dublicateUserIds);
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback, // return PendingIntent to be executed by client
extras); Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
if (callback.isSuccess()) { return result;
Log.d(Constants.TAG, "New selection of pub keys!");
keyIdsArray = callback.getPubKeyIds();
} else {
Log.d(Constants.TAG, "Pub key selection canceled!");
return null;
}
}
// if no user interaction is allow throw exceptions on duplicate or missing pub keys
if (!allowUserInteraction) {
if (missingUserIdsCheck)
throw new UserInteractionRequiredException(
"Pub keys for these user ids are missing:" + missingUserIds.toString());
if (dublicateUserIdsCheck)
throw new UserInteractionRequiredException(
"More than one pub key with these user ids exist:"
+ dublicateUserIds.toString());
} }
if (keyIdsArray.length == 0) { if (keyIdsArray.length == 0) {
return null; return null;
} }
return keyIdsArray;
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
result.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIdsArray);
return result;
} }
public class SelectPubKeysActivityCallback extends UserInputCallback { private Bundle getPassphraseBundleIntent(Bundle params, long keyId) {
public static final String PUB_KEY_IDS = "pub_key_ids"; // build PendingIntent for passphrase input
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE);
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0);
private boolean success = false; // return PendingIntent to be executed by client
private long[] pubKeyIds; Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
public boolean isSuccess() { return result;
return success; }
}
public long[] getPubKeyIds() { private Bundle signImpl(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output,
return pubKeyIds; AppSettings appSettings) {
} try {
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
@Override // get passphrase from cache, if key has "no" passphrase, this returns an empty String
public void handleUserInput(Message msg) { String passphrase;
if (msg.arg1 == OKAY) { if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
success = true; passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS);
} else { } else {
success = false; passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
} }
} if (passphrase == null) {
}; // get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction, return passphraseBundle;
IOpenPgpKeyIdsCallback callback, AppSettings appSettings) {
try {
long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction);
if (keyIds == null) {
throw new NoUserIdsException("No user ids!");
} }
callback.onSuccess(keyIds); // Get Input- and OutputStream from ParcelFileDescriptor
} catch (UserInteractionRequiredException e) { InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage()); OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
} catch (NoUserIdsException e) { try {
callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage()); long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
// sign-only
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
.signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId())
.signaturePassphrase(passphrase);
builder.build().execute();
} finally {
is.close();
os.close();
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) { } catch (Exception e) {
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result;
} }
} }
private synchronized void encryptAndSignSafe(OpenPgpData inputData, private Bundle encryptAndSignImpl(Bundle params, ParcelFileDescriptor input,
final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction, ParcelFileDescriptor output, AppSettings appSettings,
IOpenPgpCallback callback, AppSettings appSettings, boolean sign) { boolean sign) {
try { try {
// TODO: other options of OpenPgpData! boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
byte[] inputBytes = getInput(inputData);
boolean asciiArmor = false; long[] keyIds;
if (outputData.getType() == OpenPgpData.TYPE_STRING) { if (params.containsKey(OpenPgpConstants.PARAMS_KEY_IDS)) {
asciiArmor = true; keyIds = params.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
} else if (params.containsKey(OpenPgpConstants.PARAMS_USER_IDS)) {
// get key ids based on given user ids
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
// give params through to activity...
Bundle result = getKeyIdsFromEmails(params, userIds);
if (result.getInt(OpenPgpConstants.RESULT_CODE, 0) == OpenPgpConstants.RESULT_CODE_SUCCESS) {
keyIds = result.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
} else {
// if not success -> result contains a PendingIntent for user interaction
return result;
}
} else {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!"));
return result;
} }
// add own key for encryption // add own key for encryption
@ -249,351 +220,339 @@ public class OpenPgpService extends RemoteService {
keyIds[keyIds.length - 1] = appSettings.getKeyId(); keyIds[keyIds.length - 1] = appSettings.getKeyId();
// build InputData and write into OutputStream // build InputData and write into OutputStream
InputStream inputStream = new ByteArrayInputStream(inputBytes); // Get Input- and OutputStream from ParcelFileDescriptor
long inputLength = inputBytes.length; InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
InputData inputDt = new InputData(inputStream, inputLength); OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
try {
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
OutputStream outputStream = new ByteArrayOutputStream(); PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.compressionId(appSettings.getCompression())
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
.encryptionKeyIds(keyIds);
PgpOperation operation = new PgpOperation(getContext(), null, inputDt, outputStream); if (sign) {
if (sign) { String passphrase;
String passphrase = getCachedPassphrase(appSettings.getKeyId(), if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
allowUserInteraction); passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
if (passphrase == null) { } else {
throw new WrongPassphraseException("No or wrong passphrase!"); passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
} appSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
return passphraseBundle;
}
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null, // sign and encrypt
appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(), builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
appSettings.getHashAlgorithm(), true, passphrase); .signatureForceV3(false)
} else { .signatureKeyId(appSettings.getKeyId())
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null, .signaturePassphrase(passphrase);
appSettings.getEncryptionAlgorithm(), Id.key.none,
appSettings.getHashAlgorithm(), true, null);
}
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
OpenPgpData output = null;
if (asciiArmor) {
output = new OpenPgpData(new String(outputBytes));
} else {
output = new OpenPgpData(outputBytes);
}
// return over handler on client side
callback.onSuccess(output, null);
} catch (UserInteractionRequiredException e) {
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
} catch (WrongPassphraseException e) {
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
} catch (Exception e) {
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
}
}
// TODO: asciiArmor?!
private void signSafe(byte[] inputBytes, boolean allowUserInteraction,
IOpenPgpCallback callback, AppSettings appSettings) {
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(), allowUserInteraction);
if (passphrase == null) {
throw new WrongPassphraseException("No or wrong passphrase!");
}
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
operation.signText(appSettings.getKeyId(), passphrase, appSettings.getHashAlgorithm(),
Preferences.getPreferences(this).getForceV3Signatures());
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
OpenPgpData output = new OpenPgpData(new String(outputBytes));
// return over handler on client side
callback.onSuccess(output, null);
} catch (UserInteractionRequiredException e) {
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
} catch (WrongPassphraseException e) {
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
} catch (Exception e) {
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
}
}
private synchronized void decryptAndVerifySafe(byte[] inputBytes, boolean allowUserInteraction,
IOpenPgpCallback callback, AppSettings appSettings) {
try {
// TODO: this is not really needed
// checked if it is text with BEGIN and END tags
String message = new String(inputBytes);
Log.d(Constants.TAG, "in: " + message);
boolean signedOnly = false;
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
if (matcher.matches()) {
Log.d(Constants.TAG, "PGP_MESSAGE matched");
message = matcher.group(1);
// replace non breakable spaces
message = message.replaceAll("\\xa0", " ");
// overwrite inputBytes
inputBytes = message.getBytes();
} else {
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
if (matcher.matches()) {
signedOnly = true;
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
message = matcher.group(1);
// replace non breakable spaces
message = message.replaceAll("\\xa0", " ");
// overwrite inputBytes
inputBytes = message.getBytes();
} else { } else {
Log.d(Constants.TAG, "Nothing matched! Binary?"); // encrypt only
} builder.signatureKeyId(Id.key.none);
}
// END TODO
Log.d(Constants.TAG, "in: " + new String(inputBytes));
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
// app, Fix this?
String passphrase = null;
if (!signedOnly) {
// BEGIN Get key
// TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
// better!
InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
// TODO: duplicates functions from DecryptActivity!
long secretKeyId;
try {
if (inputStream2.markSupported()) {
// should probably set this to the max size of two
// pgpF objects, if it even needs to be anything other
// than 0.
inputStream2.mark(200);
}
secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
if (secretKeyId == Id.key.none) {
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
}
} catch (NoAsymmetricEncryptionException e) {
if (inputStream2.markSupported()) {
inputStream2.reset();
}
secretKeyId = Id.key.symmetric;
if (!PgpOperation.hasSymmetricEncryption(this, inputStream2)) {
throw new PgpGeneralException(
getString(R.string.error_no_known_encryption_found));
}
// we do not support symmetric decryption from the API!
throw new Exception("Symmetric decryption is not supported!");
}
Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
passphrase = getCachedPassphrase(secretKeyId, allowUserInteraction);
if (passphrase == null) {
throw new WrongPassphraseException("No or wrong passphrase!");
} }
// execute PGP operation!
builder.build().execute();
} finally {
is.close();
os.close();
} }
// build InputData and write into OutputStream Bundle result = new Bundle();
InputStream inputStream = new ByteArrayInputStream(inputBytes); result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
long inputLength = inputBytes.length; return result;
InputData inputData = new InputData(inputStream, inputLength);
OutputStream outputStream = new ByteArrayOutputStream();
Bundle outputBundle;
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
if (signedOnly) {
// TODO: download missing keys from keyserver?
outputBundle = operation.verifyText(false);
} else {
outputBundle = operation.decryptAndVerify(passphrase, false);
}
outputStream.close();
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
OpenPgpSignatureResult sigResult = null;
if (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);
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
if (signatureSuccess) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_TRUSTED;
} else if (signatureUnknown) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
}
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
signedOnly, signatureKeyId);
}
OpenPgpData output = new OpenPgpData(new String(outputBytes));
// return over handler on client side
callback.onSuccess(output, sigResult);
} catch (UserInteractionRequiredException e) {
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
} catch (WrongPassphraseException e) {
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
} catch (Exception e) { } catch (Exception e) {
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result;
} }
} }
private Bundle decryptAndVerifyImpl(Bundle params, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
try {
// Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
OpenPgpSignatureResult sigResult = null;
try {
// PGPUtil.getDecoderStream(is)
// TODOs API 2.0:
// implement verify-only!
// fix the mess: http://stackoverflow.com/questions/148130/how-do-i-peek-at-the-first-two-bytes-in-an-inputstream
// should we allow to decrypt everything under every key id or only the one set?
// TODO: instead of trying to get the passphrase before
// pause stream when passphrase is missing and then resume
// TODO: this is not really needed
// checked if it is text with BEGIN and END tags
// String message = new String(inputBytes);
// Log.d(Constants.TAG, "in: " + message);
// boolean signedOnly = false;
// Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
// if (matcher.matches()) {
// Log.d(Constants.TAG, "PGP_MESSAGE matched");
// message = matcher.group(1);
// // replace non breakable spaces
// message = message.replaceAll("\\xa0", " ");
//
// // overwrite inputBytes
// inputBytes = message.getBytes();
// } else {
// matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
// if (matcher.matches()) {
// signedOnly = true;
// Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
// message = matcher.group(1);
// // replace non breakable spaces
// message = message.replaceAll("\\xa0", " ");
//
// // overwrite inputBytes
// inputBytes = message.getBytes();
// } else {
// Log.d(Constants.TAG, "Nothing matched! Binary?");
// }
// }
// END TODO
// Log.d(Constants.TAG, "in: " + new String(inputBytes));
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
// app, Fix this?
// String passphrase = null;
// if (!signedOnly) {
// // BEGIN Get key
// // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
// // better!
// InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
//
// // TODO: duplicates functions from DecryptActivity!
// long secretKeyId;
// try {
// if (inputStream2.markSupported()) {
// // should probably set this to the max size of two
// // pgpF objects, if it even needs to be anything other
// // than 0.
// inputStream2.mark(200);
// }
// secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
// if (secretKeyId == Id.key.none) {
// throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
// }
// } catch (NoAsymmetricEncryptionException e) {
// if (inputStream2.markSupported()) {
// inputStream2.reset();
// }
// secretKeyId = Id.key.symmetric;
// if (!PgpDecryptVerify.hasSymmetricEncryption(this, inputStream2)) {
// throw new PgpGeneralException(
// getString(R.string.error_no_known_encryption_found));
// }
// // we do not support symmetric decryption from the API!
// throw new Exception("Symmetric decryption is not supported!");
// }
//
// Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
// NOTE: currently this only gets the passphrase for the key set for this client
String passphrase;
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
return passphraseBundle;
}
// }
// build InputData and write into OutputStream
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
Bundle outputBundle;
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
// if (signedOnly) {
// outputBundle = builder.build().verifyText();
// } else {
builder.assumeSymmetric(false)
.passphrase(passphrase);
// Do we want to do this: instead of trying to get the passphrase before
// pause stream when passphrase is missing and then resume???
// TODO: this also decrypts with other secret keys without passphrase!!!
outputBundle = builder.build().execute();
// }
// outputStream.close();
// byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false);
if (signature) {
long signatureKeyId = outputBundle
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0);
String signatureUserId = outputBundle
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
boolean signatureSuccess = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
boolean signatureUnknown = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, false);
boolean signatureOnly = outputBundle
.getBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
if (signatureSuccess) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED;
} else if (signatureUnknown) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
}
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
signatureOnly, signatureKeyId);
}
} finally {
is.close();
os.close();
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
result.putParcelable(OpenPgpConstants.RESULT_SIGNATURE, sigResult);
return result;
} catch (Exception e) {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result;
}
}
private Bundle getKeyIdsImpl(Bundle params) {
// get key ids based on given user ids
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
Bundle result = getKeyIdsFromEmails(params, userIds);
return result;
}
/** /**
* Returns error to IOpenPgpCallback * Check requirements:
* - params != null
* - has supported API version
* - is allowed to call the service (access has been granted)
* *
* @param callback * @param params
* @param errorId * @return null if everything is okay, or a Bundle with an error/PendingIntent
* @param message
*/ */
private void callbackOpenPgpError(IOpenPgpCallback callback, int errorId, String message) { private Bundle checkRequirements(Bundle params) {
try { // params Bundle is required!
callback.onError(new OpenPgpError(0, message)); if (params == null) {
} catch (Exception t) { Bundle result = new Bundle();
Log.e(Constants.TAG, OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
"Exception while returning OpenPgpError to client via callback.onError()", t); result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
} result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
} return result;
private void callbackOpenPgpError(IOpenPgpKeyIdsCallback callback, int errorId, String message) {
try {
callback.onError(new OpenPgpError(0, message));
} catch (Exception t) {
Log.e(Constants.TAG,
"Exception while returning OpenPgpError to client via callback.onError()", t);
} }
// version code is required and needs to correspond to version code of service!
if (params.getInt(OpenPgpConstants.PARAMS_API_VERSION) != OpenPgpConstants.API_VERSION) {
Bundle result = new Bundle();
OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
return result;
}
// check if caller is allowed to access openpgp keychain
Bundle result = isAllowed(params);
if (result != null) {
return result;
}
return null;
} }
// TODO: multi-threading
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
@Override @Override
public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds, public Bundle sign(Bundle params, final ParcelFileDescriptor input, final ParcelFileDescriptor output) {
final IOpenPgpCallback callback) throws RemoteException { final AppSettings appSettings = getAppSettings();
final AppSettings settings = getAppSettings();
Runnable r = new Runnable() { Bundle errorResult = checkRequirements(params);
@Override if (errorResult != null) {
public void run() { return errorResult;
encryptAndSignSafe(input, output, keyIds, true, callback, settings, false); }
}
};
checkAndEnqueue(r); return signImpl(params, input, output, appSettings);
} }
@Override @Override
public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output, public Bundle encrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException { final AppSettings appSettings = getAppSettings();
final AppSettings settings = getAppSettings();
Runnable r = new Runnable() { Bundle errorResult = checkRequirements(params);
@Override if (errorResult != null) {
public void run() { return errorResult;
encryptAndSignSafe(input, output, keyIds, true, callback, settings, true); }
}
};
checkAndEnqueue(r); return encryptAndSignImpl(params, input, output, appSettings, false);
} }
@Override @Override
public void sign(final OpenPgpData input, final OpenPgpData output, public Bundle signAndEncrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final IOpenPgpCallback callback) throws RemoteException { final AppSettings appSettings = getAppSettings();
final AppSettings settings = getAppSettings();
Runnable r = new Runnable() { Bundle errorResult = checkRequirements(params);
@Override if (errorResult != null) {
public void run() { return errorResult;
signSafe(getInput(input), true, callback, settings); }
}
};
checkAndEnqueue(r); return encryptAndSignImpl(params, input, output, appSettings, true);
} }
@Override @Override
public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output, public Bundle decryptAndVerify(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final IOpenPgpCallback callback) throws RemoteException { final AppSettings appSettings = getAppSettings();
final AppSettings settings = getAppSettings(); Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
Runnable r = new Runnable() { return decryptAndVerifyImpl(params, input, output, appSettings);
@Override
public void run() {
decryptAndVerifySafe(getInput(input), true, callback, settings);
}
};
checkAndEnqueue(r);
} }
@Override @Override
public void getKeyIds(final String[] userIds, final boolean allowUserInteraction, public Bundle getKeyIds(Bundle params) {
final IOpenPgpKeyIdsCallback callback) throws RemoteException { Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
final AppSettings settings = getAppSettings(); return getKeyIdsImpl(params);
Runnable r = new Runnable() {
@Override
public void run() {
getKeyIdsSafe(userIds, allowUserInteraction, callback, settings);
}
};
checkAndEnqueue(r);
} }
}; };
private static byte[] getInput(OpenPgpData data) {
// TODO: support Uri and ParcelFileDescriptor
byte[] inBytes = null;
switch (data.getType()) {
case OpenPgpData.TYPE_STRING:
inBytes = data.getString().getBytes();
break;
case OpenPgpData.TYPE_BYTE_ARRAY:
inBytes = data.getBytes();
break;
default:
Log.e(Constants.TAG, "Uri and ParcelFileDescriptor not supported right now!");
break;
}
return inBytes;
}
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return mBinder; return mBinder;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -19,17 +19,16 @@ package org.sufficientlysecure.keychain.service.remote;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -50,66 +49,19 @@ import android.os.Messenger;
public abstract class RemoteService extends Service { public abstract class RemoteService extends Service {
Context mContext; Context mContext;
private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100); private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
// TODO: Are these parameters okay? private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
TimeUnit.SECONDS, mPoolQueue);
private final Object userInputLock = new Object();
/**
* Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting
* threads will be notified and the queue resumed
*/
protected class UserInputCallback extends BaseCallback {
public void handleUserInput(Message msg) {
}
@Override
public boolean handleMessage(Message msg) {
handleUserInput(msg);
// resume
synchronized (userInputLock) {
userInputLock.notifyAll();
}
mThreadPool.resume();
return true;
}
}
/**
* Extends Handler.Callback with OKAY (1), CANCEL (0) variables
*/
private class BaseCallback implements Handler.Callback {
public static final int OKAY = 1;
public static final int CANCEL = 0;
@Override
public boolean handleMessage(Message msg) {
return false;
}
}
public Context getContext() { public Context getContext() {
return mContext; return mContext;
} }
/** protected Bundle isAllowed(Bundle params) {
* Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
* execution
*
* @param r
*/
protected void checkAndEnqueue(Runnable r) {
try { try {
if (isCallerAllowed(false)) { if (isCallerAllowed(false)) {
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…"); return null;
} else { } else {
String[] callingPackages = getPackageManager().getPackagesForUid( String[] callingPackages = getPackageManager().getPackagesForUid(
Binder.getCallingUid()); Binder.getCallingUid());
@ -121,32 +73,46 @@ public abstract class RemoteService extends Service {
packageSignature = getPackageSignature(packageName); packageSignature = getPackageSignature(packageName);
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.e(Constants.TAG, "Should not happen, returning!", e); Log.e(Constants.TAG, "Should not happen, returning!", e);
return; // return error
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result;
} }
Log.e(Constants.TAG, Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!");
"Not allowed to use service! Starting activity for registration!");
Bundle extras = new Bundle();
extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
RegisterActivityCallback callback = new RegisterActivityCallback();
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback, Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
extras); intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
if (callback.isAllowed()) { PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, intent, 0);
mThreadPool.execute(r);
Log.d(Constants.TAG, "Enqueued runnable…"); // return PendingIntent to be executed by client
} else { Bundle result = new Bundle();
Log.d(Constants.TAG, "User disallowed app!"); result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
} result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
return result;
} }
} catch (WrongPackageSignatureException e) { } catch (WrongPackageSignatureException e) {
Log.e(Constants.TAG, e.getMessage()); Log.e(Constants.TAG, "wrong signature!", e);
Bundle extras = new Bundle(); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
getString(R.string.api_error_wrong_signature)); intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature));
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras); intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_ERROR, intent, 0);
// return PendingIntent to be executed by client
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
return result;
} }
} }
@ -160,38 +126,6 @@ public abstract class RemoteService extends Service {
return packageSignature; return packageSignature;
} }
/**
* Locks current thread and pauses execution of runnables and starts activity for user input
*
* @param action
* @param messenger
* @param extras
*/
protected void pauseAndStartUserInteraction(String action, BaseCallback callback, Bundle extras) {
synchronized (userInputLock) {
mThreadPool.pause();
Log.d(Constants.TAG, "starting activity...");
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(action);
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger);
intent.putExtras(extras);
startActivity(intent);
// lock current thread for user input
try {
userInputLock.wait();
} catch (InterruptedException e) {
Log.e(Constants.TAG, "CryptoService", e);
}
}
}
/** /**
* Retrieves AppSettings from database for the application calling this remote service * Retrieves AppSettings from database for the application calling this remote service
* *
@ -215,66 +149,11 @@ public abstract class RemoteService extends Service {
return null; return null;
} }
class RegisterActivityCallback extends BaseCallback {
public static final String PACKAGE_NAME = "package_name";
private boolean allowed = false;
private String packageName;
public boolean isAllowed() {
return allowed;
}
public String getPackageName() {
return packageName;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.arg1 == OKAY) {
allowed = true;
packageName = msg.getData().getString(PACKAGE_NAME);
// resume threads
try {
if (isPackageAllowed(packageName)) {
synchronized (userInputLock) {
userInputLock.notifyAll();
}
mThreadPool.resume();
} else {
// Should not happen!
Log.e(Constants.TAG, "Should not happen! Emergency shutdown!");
mThreadPool.shutdownNow();
}
} catch (WrongPackageSignatureException e) {
Log.e(Constants.TAG, e.getMessage());
Bundle extras = new Bundle();
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
getString(R.string.api_error_wrong_signature));
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null,
extras);
}
} else {
allowed = false;
synchronized (userInputLock) {
userInputLock.notifyAll();
}
mThreadPool.resume();
}
return true;
}
}
/** /**
* Checks if process that binds to this service (i.e. the package name corresponding to the * 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. * process) is in the list of allowed package names.
* *
* @param allowOnlySelf * @param allowOnlySelf allow only Keychain app itself
* allow only Keychain app itself
* @return true if process is allowed to use this service * @return true if process is allowed to use this service
* @throws WrongPackageSignatureException * @throws WrongPackageSignatureException
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -17,8 +17,15 @@
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.service.remote;
import java.util.ArrayList; import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.htmltextview.HtmlTextView;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -30,15 +37,7 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.content.Intent; import java.util.ArrayList;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Toast;
public class RemoteServiceActivity extends ActionBarActivity { public class RemoteServiceActivity extends ActionBarActivity {
@ -64,17 +63,11 @@ public class RemoteServiceActivity extends ActionBarActivity {
// error message // error message
public static final String EXTRA_ERROR_MESSAGE = "error_message"; public static final String EXTRA_ERROR_MESSAGE = "error_message";
private Messenger mMessenger;
// register view // register view
private AppSettingsFragment mSettingsFragment; private AppSettingsFragment mSettingsFragment;
// select pub keys view // select pub keys view
private SelectPublicKeyFragment mSelectFragment; private SelectPublicKeyFragment mSelectFragment;
// has the user clicked one of the buttons
// or do we need to handle the callback in onStop()
private boolean finishHandled;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -82,36 +75,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
handleActions(getIntent(), savedInstanceState); handleActions(getIntent(), savedInstanceState);
} }
@Override
protected void onStop() {
super.onStop();
if (!finishHandled) {
Message msg = Message.obtain();
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
}
}
protected void handleActions(Intent intent, Bundle savedInstanceState) { protected void handleActions(Intent intent, Bundle savedInstanceState) {
finishHandled = false;
String action = intent.getAction(); String action = intent.getAction();
Bundle extras = intent.getExtras(); final Bundle extras = intent.getExtras();
if (extras == null) {
extras = new Bundle();
}
mMessenger = extras.getParcelable(EXTRA_MESSENGER);
/**
* com.android.crypto actions
*/
if (ACTION_REGISTER.equals(action)) { if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
@ -125,44 +94,27 @@ public class RemoteServiceActivity extends ActionBarActivity {
// user needs to select a key! // user needs to select a key!
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
Toast.makeText(RemoteServiceActivity.this, mSettingsFragment.setErrorOnSelectKeyFragment(
R.string.api_register_error_select_key, Toast.LENGTH_LONG) getString(R.string.api_register_error_select_key));
.show();
} else { } else {
ProviderHelper.insertApiApp(RemoteServiceActivity.this, ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings()); mSettingsFragment.getAppSettings());
Message msg = Message.obtain(); // give params through for new service call
msg.arg1 = RemoteService.RegisterActivityCallback.OKAY; Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
Bundle data = new Bundle();
data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME,
packageName);
msg.setData(data);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
finishHandled = true; Intent finishIntent = new Intent();
finish(); finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, oldParams);
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
RemoteServiceActivity.this.finish();
} }
} }
}, R.string.api_register_disallow, new View.OnClickListener() { }, R.string.api_register_disallow, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// Disallow // Disallow
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
Message msg = Message.obtain(); RemoteServiceActivity.this.finish();
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
finishHandled = true;
finish();
} }
} }
); );
@ -176,8 +128,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
mSettingsFragment.setAppSettings(settings); mSettingsFragment.setAppSettings(settings);
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) { } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
showPassphraseDialog(secretKeyId); showPassphraseDialog(oldParams, secretKeyId);
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) { } else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
ArrayList<String> missingUserIds = intent ArrayList<String> missingUserIds = intent
@ -185,8 +138,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
ArrayList<String> dublicateUserIds = intent ArrayList<String> dublicateUserIds = intent
.getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
String text = new String(); // TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids
text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>"; String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
text += "<br/><br/>"; text += "<br/><br/>";
if (missingUserIds != null && missingUserIds.size() > 0) { if (missingUserIds != null && missingUserIds.size() > 0) {
text += getString(R.string.api_select_pub_keys_missing_text); text += getString(R.string.api_select_pub_keys_missing_text);
@ -213,40 +166,22 @@ public class RemoteServiceActivity extends ActionBarActivity {
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// ok // add key ids to params Bundle for new request
Bundle params = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
Message msg = Message.obtain(); params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS,
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY;
Bundle data = new Bundle();
data.putLongArray(
OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS,
mSelectFragment.getSelectedMasterKeyIds()); mSelectFragment.getSelectedMasterKeyIds());
msg.setData(data);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
finishHandled = true; Intent finishIntent = new Intent();
finish(); finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
RemoteServiceActivity.this.finish();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
Message msg = Message.obtain(); RemoteServiceActivity.this.finish();
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL;
;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
finishHandled = true;
finish();
} }
} }
); );
@ -279,8 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} else if (ACTION_ERROR_MESSAGE.equals(action)) { } else if (ACTION_ERROR_MESSAGE.equals(action)) {
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE); String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
String text = new String(); String text = "<font color=\"red\">" + errorMessage + "</font>";
text += "<font color=\"red\">" + errorMessage + "</font>";
// Inflate a "Done" custom action bar view // Inflate a "Done" custom action bar view
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay,
@ -288,7 +222,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
finish(); RemoteServiceActivity.this.setResult(RESULT_OK);
RemoteServiceActivity.this.finish();
} }
}); });
@ -298,7 +233,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
textView.setHtmlFromString(text); textView.setHtmlFromString(text);
} else { } else {
Log.e(Constants.TAG, "Wrong action!"); Log.e(Constants.TAG, "Action does not exist!");
setResult(RESULT_CANCELED);
finish(); finish();
} }
} }
@ -308,31 +244,21 @@ public class RemoteServiceActivity extends ActionBarActivity {
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
* for a symmetric passphrase * for a symmetric passphrase
*/ */
private void showPassphraseDialog(long secretKeyId) { private void showPassphraseDialog(final Bundle params, long secretKeyId) {
// Message is received after passphrase is cached // Message is received after passphrase is cached
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
Message msg = Message.obtain(); // return given params again, for calling the service method again
msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY; Intent finishIntent = new Intent();
try { finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
mMessenger.send(msg); RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
} else { } else {
Message msg = Message.obtain(); RemoteServiceActivity.this.setResult(RESULT_CANCELED);
msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.e(Constants.TAG, "CryptoServiceActivity", e);
}
} }
finishHandled = true; RemoteServiceActivity.this.finish();
finish();
} }
}; };
@ -345,9 +271,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); Log.d(Constants.TAG, "No passphrase for this secret key, do pgp operation directly!");
// send message to handler to start encryption directly // return given params again, for calling the service method again
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); Intent finishIntent = new Intent();
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
setResult(RESULT_OK, finishIntent);
finish();
} }
} }
} }

View File

@ -1,4 +1,4 @@
package org.sufficientlysecure.keychain.service.exception; package org.sufficientlysecure.keychain.service.remote;
public class WrongPackageSignatureException extends Exception { public class WrongPackageSignatureException extends Exception {

View File

@ -56,7 +56,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
/** /**
* Signs the specified public key with the specified secret master key * Signs the specified public key with the specified secret master key
*/ */
public class SignKeyActivity extends ActionBarActivity implements public class CertifyKeyActivity extends ActionBarActivity implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
private BootstrapButton mSignButton; private BootstrapButton mSignButton;
private CheckBox mUploadKeyCheckbox; private CheckBox mUploadKeyCheckbox;
@ -72,7 +72,7 @@ public class SignKeyActivity extends ActionBarActivity implements
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.sign_key_activity); setContentView(R.layout.certify_key_activity);
final ActionBar actionBar = getSupportActionBar(); final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayShowTitleEnabled(true);
@ -164,8 +164,8 @@ public class SignKeyActivity extends ActionBarActivity implements
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); Log.d(Constants.TAG, "No passphrase for this secret key!");
// send message to handler to start encryption directly // send message to handler to start certification directly
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
} }
} }
@ -196,8 +196,8 @@ public class SignKeyActivity extends ActionBarActivity implements
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
if (passphrase == null) { if (passphrase == null) {
showPassphraseDialog(mMasterKeyId); showPassphraseDialog(mMasterKeyId);
return; // bail out; need to wait until the user has entered the passphrase // bail out; need to wait until the user has entered the passphrase before trying again
// before trying again return;
} else { } else {
startSigning(); startSigning();
} }
@ -218,13 +218,13 @@ public class SignKeyActivity extends ActionBarActivity implements
// Send all information needed to service to sign key in other thread // Send all information needed to service to sign key in other thread
Intent intent = new Intent(this, KeychainIntentService.class); Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING); intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
// fill values for this action // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId); data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId); data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@ -237,14 +237,12 @@ public class SignKeyActivity extends ActionBarActivity implements
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Toast.makeText(SignKeyActivity.this, R.string.key_sign_success, Toast.makeText(CertifyKeyActivity.this, R.string.key_sign_success,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
// check if we need to send the key to the server or not // check if we need to send the key to the server or not
if (mUploadKeyCheckbox.isChecked()) { if (mUploadKeyCheckbox.isChecked()) {
/* // upload the newly signed key to the keyserver
* upload the newly signed key to the key server
*/
uploadKey(); uploadKey();
} else { } else {
setResult(RESULT_OK); setResult(RESULT_OK);
@ -291,7 +289,7 @@ public class SignKeyActivity extends ActionBarActivity implements
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
Toast.makeText(SignKeyActivity.this, R.string.key_send_success, Toast.makeText(CertifyKeyActivity.this, R.string.key_send_success,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
setResult(RESULT_OK); setResult(RESULT_OK);

View File

@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.PgpOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException; import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
@ -43,7 +43,6 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.LookupUnknownKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -61,7 +60,7 @@ import android.view.animation.AnimationUtils;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.ViewFlipper; import android.widget.ViewFlipper;
@ -78,14 +77,20 @@ public class DecryptActivity extends DrawerActivity {
/* EXTRA keys for input */ /* EXTRA keys for input */
public static final String EXTRA_TEXT = "text"; public static final String EXTRA_TEXT = "text";
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
private static final int RESULT_CODE_FILE = 0x00007003;
private long mSignatureKeyId = 0; private long mSignatureKeyId = 0;
private boolean mReturnResult = false; private boolean mReturnResult = false;
// TODO: replace signed only checks with something more intelligent
// PgpDecryptVerify should handle all automatically!!!
private boolean mSignedOnly = false; private boolean mSignedOnly = false;
private boolean mAssumeSymmetricEncryption = false; private boolean mAssumeSymmetricEncryption = false;
private EditText mMessage = null; private EditText mMessage = null;
private LinearLayout mSignatureLayout = null; private RelativeLayout mSignatureLayout = null;
private ImageView mSignatureStatusImage = null; private ImageView mSignatureStatusImage = null;
private TextView mUserId = null; private TextView mUserId = null;
private TextView mUserIdRest = null; private TextView mUserIdRest = null;
@ -100,6 +105,7 @@ public class DecryptActivity extends DrawerActivity {
private EditText mFilename = null; private EditText mFilename = null;
private CheckBox mDeleteAfter = null; private CheckBox mDeleteAfter = null;
private BootstrapButton mBrowse = null; private BootstrapButton mBrowse = null;
private BootstrapButton mLookupKey = null;
private String mInputFilename = null; private String mInputFilename = null;
private String mOutputFilename = null; private String mOutputFilename = null;
@ -107,14 +113,10 @@ public class DecryptActivity extends DrawerActivity {
private Uri mContentUri = null; private Uri mContentUri = null;
private boolean mReturnBinary = false; private boolean mReturnBinary = false;
private long mUnknownSignatureKeyId = 0;
private long mSecretKeyId = Id.key.none; private long mSecretKeyId = Id.key.none;
private FileDialogFragment mFileDialog; private FileDialogFragment mFileDialog;
private boolean mLookupUnknownKey = true;
private boolean mDecryptImmediately = false; private boolean mDecryptImmediately = false;
private BootstrapButton mDecryptButton; private BootstrapButton mDecryptButton;
@ -154,7 +156,7 @@ public class DecryptActivity extends DrawerActivity {
mSourceLabel.setOnClickListener(nextSourceClickListener); mSourceLabel.setOnClickListener(nextSourceClickListener);
mMessage = (EditText) findViewById(R.id.message); mMessage = (EditText) findViewById(R.id.message);
mSignatureLayout = (LinearLayout) findViewById(R.id.signature); mSignatureLayout = (RelativeLayout) findViewById(R.id.signature);
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status); mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
mUserId = (TextView) findViewById(R.id.mainUserId); mUserId = (TextView) findViewById(R.id.mainUserId);
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
@ -171,7 +173,15 @@ public class DecryptActivity extends DrawerActivity {
mBrowse.setOnClickListener(new View.OnClickListener() { mBrowse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*", FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
Id.request.filename); RESULT_CODE_FILE);
}
});
mLookupKey = (BootstrapButton) findViewById(R.id.lookup_key);
mLookupKey.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
lookupUnknownKey(mSignatureKeyId);
} }
}); });
@ -239,7 +249,7 @@ public class DecryptActivity extends DrawerActivity {
DecryptActivity.this, mSignatureKeyId); DecryptActivity.this, mSignatureKeyId);
if (key != null) { if (key != null) {
Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class); Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class);
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEY_SERVER); intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId); intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId);
startActivity(intent); startActivity(intent);
} }
@ -263,7 +273,7 @@ public class DecryptActivity extends DrawerActivity {
if (mDecryptImmediately if (mDecryptImmediately
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText() || (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
.length() > 0 || mContentUri != null))) { .length() > 0 || mContentUri != null))) {
decryptClicked(); decryptClicked();
} }
} }
@ -287,13 +297,13 @@ public class DecryptActivity extends DrawerActivity {
* Android's Action * Android's Action
*/ */
if (Intent.ACTION_SEND.equals(action) && type != null) { if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Encrypt via share menu // When sending to Keychain Decrypt via share menu
if ("text/plain".equals(type)) { if ("text/plain".equals(type)) {
// Plain text // Plain text
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) { if (sharedText != null) {
// handle like normal text decryption, override action and extras to later // handle like normal text decryption, override action and extras to later
// execute ACTION_DECRYPT in main actions // executeServiceMethod ACTION_DECRYPT in main actions
extras.putString(EXTRA_TEXT, sharedText); extras.putString(EXTRA_TEXT, sharedText);
action = ACTION_DECRYPT; action = ACTION_DECRYPT;
} }
@ -375,21 +385,21 @@ public class DecryptActivity extends DrawerActivity {
private void updateSource() { private void updateSource() {
switch (mSource.getCurrentView().getId()) { switch (mSource.getCurrentView().getId()) {
case R.id.sourceFile: { case R.id.sourceFile: {
mSourceLabel.setText(R.string.label_file); mSourceLabel.setText(R.string.label_file);
mDecryptButton.setText(getString(R.string.btn_decrypt)); mDecryptButton.setText(getString(R.string.btn_decrypt));
break; break;
} }
case R.id.sourceMessage: { case R.id.sourceMessage: {
mSourceLabel.setText(R.string.label_message); mSourceLabel.setText(R.string.label_message);
mDecryptButton.setText(getString(R.string.btn_decrypt)); mDecryptButton.setText(getString(R.string.btn_decrypt));
break; break;
} }
default: { default: {
break; break;
} }
} }
} }
@ -449,7 +459,7 @@ public class DecryptActivity extends DrawerActivity {
} else { } else {
if (mDecryptTarget == Id.target.file) { if (mDecryptTarget == Id.target.file) {
askForOutputFilename(); askForOutputFilename();
} else { } else { // mDecryptTarget == Id.target.message
decryptStart(); decryptStart();
} }
} }
@ -527,7 +537,7 @@ public class DecryptActivity extends DrawerActivity {
try { try {
if (inStream.markSupported()) { if (inStream.markSupported()) {
inStream.mark(200); // should probably set this to the max size of two pgpF inStream.mark(200); // should probably set this to the max size of two pgpF
// objects, if it even needs to be anything other than 0. // objects, if it even needs to be anything other than 0.
} }
mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream); mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream);
if (mSecretKeyId == Id.key.none) { if (mSecretKeyId == Id.key.none) {
@ -539,7 +549,7 @@ public class DecryptActivity extends DrawerActivity {
inStream.reset(); inStream.reset();
} }
mSecretKeyId = Id.key.symmetric; mSecretKeyId = Id.key.symmetric;
if (!PgpOperation.hasSymmetricEncryption(this, inStream)) { if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) {
throw new PgpGeneralException( throw new PgpGeneralException(
getString(R.string.error_no_known_encryption_found)); getString(R.string.error_no_known_encryption_found));
} }
@ -559,7 +569,7 @@ public class DecryptActivity extends DrawerActivity {
data = "\n\n" + data; data = "\n\n" + data;
intent.putExtra(EncryptActivity.EXTRA_TEXT, data); intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId); intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId }); intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[]{mSignatureKeyId});
startActivity(intent); startActivity(intent);
} }
@ -587,28 +597,10 @@ public class DecryptActivity extends DrawerActivity {
} }
private void lookupUnknownKey(long unknownKeyId) { private void lookupUnknownKey(long unknownKeyId) {
// Message is received after passphrase is cached Intent intent = new Intent(this, ImportKeysActivity.class);
Handler returnHandler = new Handler() { intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
@Override intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
public void handleMessage(Message message) { startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) {
// the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment
// starts a new Intent which then returns data
} else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) {
// decrypt again, but don't lookup unknown keys!
mLookupUnknownKey = false;
decryptStart();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment
.newInstance(messenger, unknownKeyId);
lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog");
} }
private void decryptStart() { private void decryptStart() {
@ -644,8 +636,6 @@ public class DecryptActivity extends DrawerActivity {
data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId); data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId);
data.putBoolean(KeychainIntentService.DECRYPT_SIGNED_ONLY, mSignedOnly);
data.putBoolean(KeychainIntentService.DECRYPT_LOOKUP_UNKNOWN_KEY, mLookupUnknownKey);
data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary); data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary);
data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption); data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
@ -662,15 +652,6 @@ public class DecryptActivity extends DrawerActivity {
// get returned data bundle // get returned data bundle
Bundle returnData = message.getData(); Bundle returnData = message.getData();
// if key is unknown show lookup dialog
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY)
&& mLookupUnknownKey) {
mUnknownSignatureKeyId = returnData
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
lookupUnknownKey(mUnknownSignatureKeyId);
return;
}
mSignatureKeyId = 0; mSignatureKeyId = 0;
mSignatureLayout.setVisibility(View.GONE); mSignatureLayout.setVisibility(View.GONE);
@ -685,26 +666,26 @@ public class DecryptActivity extends DrawerActivity {
} }
switch (mDecryptTarget) { switch (mDecryptTarget) {
case Id.target.message: case Id.target.message:
String decryptedMessage = returnData String decryptedMessage = returnData
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING); .getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
mMessage.setText(decryptedMessage); mMessage.setText(decryptedMessage);
mMessage.setHorizontallyScrolling(false); mMessage.setHorizontallyScrolling(false);
break; break;
case Id.target.file: case Id.target.file:
if (mDeleteAfter.isChecked()) { if (mDeleteAfter.isChecked()) {
// Create and show dialog to delete original file // Create and show dialog to delete original file
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
.newInstance(mInputFilename); .newInstance(mInputFilename);
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
} }
break; break;
default: default:
// shouldn't happen // shouldn't happen
break; break;
} }
@ -727,19 +708,24 @@ public class DecryptActivity extends DrawerActivity {
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) { if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
mLookupKey.setVisibility(View.GONE);
} else if (returnData } else if (returnData
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) { .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error); mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.VISIBLE);
Toast.makeText(DecryptActivity.this, Toast.makeText(DecryptActivity.this,
R.string.unknown_signature_key_touch_to_look_up, R.string.unknown_signature,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
} else { } else {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error); mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.GONE);
} }
mSignatureLayout.setVisibility(View.VISIBLE); mSignatureLayout.setVisibility(View.VISIBLE);
} }
} }
}; }
;
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -756,36 +742,37 @@ public class DecryptActivity extends DrawerActivity {
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) { switch (requestCode) {
case Id.request.filename: { case RESULT_CODE_FILE: {
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
try { try {
String path = FileHelper.getPath(this, data.getData()); String path = FileHelper.getPath(this, data.getData());
Log.d(Constants.TAG, "path=" + path); Log.d(Constants.TAG, "path=" + path);
mFilename.setText(path); mFilename.setText(path);
} catch (NullPointerException e) { } catch (NullPointerException e) {
Log.e(Constants.TAG, "Nullpointer while retrieving path!"); Log.e(Constants.TAG, "Nullpointer while retrieving path!");
}
} }
return;
} }
return;
}
// this request is returned after LookupUnknownKeyDialogFragment started // this request is returned after LookupUnknownKeyDialogFragment started
// ImportKeysActivity and user looked uo key // ImportKeysActivity and user looked uo key
case Id.request.look_up_key_id: { case RESULT_CODE_LOOKUP_KEY: {
Log.d(Constants.TAG, "Returning from Lookup Key..."); Log.d(Constants.TAG, "Returning from Lookup Key...");
// decrypt again without lookup if (resultCode == RESULT_OK) {
mLookupUnknownKey = false; // decrypt again
decryptStart(); decryptStart();
return; }
} return;
}
default: { default: {
break; super.onActivityResult(requestCode, resultCode, data);
}
}
super.onActivityResult(requestCode, resultCode, data); break;
}
}
} }
} }

View File

@ -173,7 +173,7 @@ public class EncryptActivity extends DrawerActivity {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) { if (sharedText != null) {
// handle like normal text encryption, override action and extras to later // handle like normal text encryption, override action and extras to later
// execute ACTION_ENCRYPT in main actions // executeServiceMethod ACTION_ENCRYPT in main actions
extras.putString(EXTRA_TEXT, sharedText); extras.putString(EXTRA_TEXT, sharedText);
extras.putBoolean(EXTRA_ASCII_ARMOR, true); extras.putBoolean(EXTRA_ASCII_ARMOR, true);
action = ACTION_ENCRYPT; action = ACTION_ENCRYPT;

Some files were not shown because too many files have changed in this diff Show More