master merge
16
CHANGELOG
@ -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
|
||||
* New design with navigation drawer
|
||||
* New public key list design
|
||||
@ -23,7 +37,7 @@
|
||||
* New AIDL API
|
||||
|
||||
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)
|
||||
* more choices for pass phrase cache: 1, 2, 4, 8, hours
|
||||
* translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)
|
||||
|
@ -17,7 +17,7 @@ License:
|
||||
GPLv3+
|
||||
|
||||
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
|
||||
• 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
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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";
|
||||
|
||||
}
|
@ -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;
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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);
|
||||
// }
|
||||
//
|
||||
//}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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>
|
@ -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 ',')"
|
||||
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>
|
@ -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>
|
3
OpenPGP-Keychain-API/build.gradle
Normal file
@ -0,0 +1,3 @@
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '1.10'
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
// please leave this here, so this library builds on its own
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.8.0'
|
||||
classpath 'com.android.tools.build:gradle:0.8.3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:19.0.1'
|
||||
compile project(':libraries:keychain-api-library')
|
||||
}
|
||||
|
||||
android {
|
||||
@ -23,7 +21,7 @@ android {
|
||||
buildToolsVersion "19.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 8
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 19
|
||||
}
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -2,19 +2,19 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.sufficientlysecure.keychain.demo"
|
||||
android:versionCode="2"
|
||||
android:versionName="1.1" >
|
||||
android:versionName="1.1">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="8"
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="OpenPGP Keychain API Demo" >
|
||||
android:label="OpenKeychain API Demo">
|
||||
<activity
|
||||
android:name=".BaseActivity"
|
||||
android:label="OpenPGP Keychain API Demo" >
|
||||
android:label="OpenKeychain API Demo">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@ -26,9 +26,8 @@
|
||||
android:label="OpenPGP Provider"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".AidlDemoActivity2"
|
||||
android:label="Aidl Demo (ACCESS_KEYS permission)"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:name=".IntentActivity"
|
||||
android:label="Intents" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,9 +16,6 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.demo;
|
||||
|
||||
import org.sufficientlysecure.keychain.demo.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
@ -27,13 +24,8 @@ import android.preference.PreferenceActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class BaseActivity extends PreferenceActivity {
|
||||
private Activity mActivity;
|
||||
|
||||
private Preference mIntentDemo;
|
||||
private Preference mContentProviderDemo;
|
||||
private Preference mCryptoProvider;
|
||||
private Preference mAidlDemo;
|
||||
private Preference mAidlDemo2;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
@ -42,23 +34,17 @@ public class BaseActivity extends PreferenceActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mActivity = this;
|
||||
|
||||
// load preferences from xml
|
||||
addPreferencesFromResource(R.xml.base_preference);
|
||||
|
||||
// find preferences
|
||||
mIntentDemo = (Preference) findPreference("intent_demo");
|
||||
mContentProviderDemo = (Preference) findPreference("content_provider_demo");
|
||||
mCryptoProvider = (Preference) findPreference("openpgp_provider_demo");
|
||||
mAidlDemo = (Preference) findPreference("aidl_demo");
|
||||
mAidlDemo2 = (Preference) findPreference("aidl_demo2");
|
||||
|
||||
mIntentDemo.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// startActivity(new Intent(mActivity, IntentDemoActivity.class));
|
||||
Toast.makeText(BaseActivity.this, "Not implemented!", Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(BaseActivity.this, IntentActivity.class));
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -67,21 +53,11 @@ public class BaseActivity extends PreferenceActivity {
|
||||
mCryptoProvider.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
startActivity(new Intent(mActivity, OpenPgpProviderActivity.class));
|
||||
startActivity(new Intent(BaseActivity.this, OpenPgpProviderActivity.class));
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// mAidlDemo2.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
// @Override
|
||||
// public boolean onPreferenceClick(Preference preference) {
|
||||
// startActivity(new Intent(mActivity, AidlDemoActivity2.class));
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,9 +17,5 @@
|
||||
package org.sufficientlysecure.keychain.demo;
|
||||
|
||||
public final class Constants {
|
||||
|
||||
public static final boolean DEBUG = BuildConfig.DEBUG;
|
||||
|
||||
public static final String TAG = "Keychain";
|
||||
|
||||
}
|
@ -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";
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@ -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 ',')"
|
||||
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>
|
@ -1,23 +1,18 @@
|
||||
<?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
|
||||
android:key="intent_demo"
|
||||
android:title="Intent Demo" />
|
||||
</PreferenceCategory>
|
||||
<!-- <PreferenceCategory android:title="AIDL" > -->
|
||||
<!-- <Preference -->
|
||||
<!-- android:key="aidl_demo2" -->
|
||||
<!-- android:title="AIDL Demo (ACCESS_KEYS permission)" /> -->
|
||||
<!-- </PreferenceCategory> -->
|
||||
<PreferenceCategory android:title="OpenPGP Provider" >
|
||||
<org.openintents.openpgp.OpenPgpListPreference
|
||||
<PreferenceCategory android:title="OpenPGP Provider API">
|
||||
<org.openintents.openpgp.util.OpenPgpListPreference
|
||||
android:key="openpgp_provider_list"
|
||||
android:title="Select OpenPGP Provider!" />
|
||||
<Preference
|
||||
android:key="openpgp_provider_demo"
|
||||
android:title="OpenPGP Provider" />
|
||||
android:title="OpenPGP Provider Demo" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
@ -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>
|
@ -1,6 +1,6 @@
|
||||
#Mon Jan 27 14:45:08 CET 2014
|
||||
#Sun Feb 09 19:19:25 CET 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
BIN
OpenPGP-Keychain-API/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
OpenPGP-Keychain-API/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
@ -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
@ -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
|
29
OpenPGP-Keychain-API/libraries/keychain-api-library/.gitignore
vendored
Normal 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
|
@ -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>
|
202
OpenPGP-Keychain-API/libraries/keychain-api-library/LICENSE
Normal 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.
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="openpgp_list_preference_none">None</string>
|
||||
|
||||
</resources>
|
@ -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);
|
||||
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,10 +20,13 @@ import android.os.Parcel;
|
||||
import android.os.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 NO_OR_WRONG_PASSPHRASE = 1;
|
||||
public static final int NO_USER_IDS = 2;
|
||||
public static final int USER_INTERACTION_REQUIRED = 3;
|
||||
public static final int INCOMPATIBLE_API_VERSIONS = 1;
|
||||
|
||||
public static final int NO_OR_WRONG_PASSPHRASE = 2;
|
||||
public static final int NO_USER_IDS = 3;
|
||||
|
||||
int errorId;
|
||||
String message;
|
@ -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");
|
||||
* 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 {
|
||||
// generic error on signature verification
|
||||
public static final int SIGNATURE_ERROR = 0;
|
||||
// successfully verified signature, with trusted public key
|
||||
public static final int SIGNATURE_SUCCESS_TRUSTED = 1;
|
||||
// successfully verified signature, with certified public key
|
||||
public static final int SIGNATURE_SUCCESS_CERTIFIED = 1;
|
||||
// no public key was found for this signature verification
|
||||
// you can retrieve the key with
|
||||
// getKeys(new String[] {String.valueOf(signatureResult.getKeyId)}, true, callback)
|
||||
public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2;
|
||||
// successfully verified signature, but with untrusted public key
|
||||
public static final int SIGNATURE_SUCCESS_UNTRUSTED = 3;
|
||||
// successfully verified signature, but with certified public key
|
||||
public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3;
|
||||
|
||||
int status;
|
||||
boolean signatureOnly;
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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";
|
||||
|
||||
}
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,18 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
@ -35,33 +31,21 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ListAdapter;
|
||||
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 {
|
||||
ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
|
||||
private ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
|
||||
private String mSelectedPackage;
|
||||
|
||||
public OpenPgpListPreference(Context context, AttributeSet 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) {
|
||||
@ -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 simpleName
|
||||
* @param icon
|
||||
*/
|
||||
public void addProvider(int position, String packageName, String simpleName, Drawable icon,
|
||||
int apiVersion) {
|
||||
mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon,
|
||||
apiVersion));
|
||||
public void addProvider(int position, String packageName, String simpleName, Drawable icon) {
|
||||
mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
|
||||
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);
|
||||
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;
|
||||
}
|
||||
};
|
||||
@ -169,6 +166,16 @@ public class OpenPgpListPreference extends DialogPreference {
|
||||
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) {
|
||||
for (OpenPgpProviderEntry app : mProviderList) {
|
||||
if (app.packageName.equals(packageName)) {
|
||||
@ -183,14 +190,11 @@ public class OpenPgpListPreference extends DialogPreference {
|
||||
private String packageName;
|
||||
private String simpleName;
|
||||
private Drawable icon;
|
||||
private int apiVersion;
|
||||
|
||||
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon,
|
||||
int apiVersion) {
|
||||
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
|
||||
this.packageName = packageName;
|
||||
this.simpleName = simpleName;
|
||||
this.icon = icon;
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
@Override
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openintents.openpgp;
|
||||
package org.openintents.openpgp.util;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
|
||||
@ -23,18 +23,17 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class OpenPgpServiceConnection {
|
||||
private Context mApplicationContext;
|
||||
|
||||
private IOpenPgpService mService;
|
||||
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.mCryptoProviderPackageName = cryptoProviderPackageName;
|
||||
this.mProviderPackageName = providerPackageName;
|
||||
}
|
||||
|
||||
public IOpenPgpService getService() {
|
||||
@ -45,49 +44,45 @@ public class OpenPgpServiceConnection {
|
||||
return mBound;
|
||||
}
|
||||
|
||||
private ServiceConnection mCryptoServiceConnection = new ServiceConnection() {
|
||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IOpenPgpService.Stub.asInterface(service);
|
||||
Log.d(OpenPgpConstants.TAG, "connected to service");
|
||||
mBound = true;
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
Log.d(OpenPgpConstants.TAG, "disconnected from service");
|
||||
mBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If not already bound, bind!
|
||||
*
|
||||
* If not already bound, bind to service!
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean bindToService() {
|
||||
if (mService == null && !mBound) { // if not already connected
|
||||
// if not already bound...
|
||||
if (mService == null && !mBound) {
|
||||
try {
|
||||
Log.d(OpenPgpConstants.TAG, "not bound yet");
|
||||
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setAction(IOpenPgpService.class.getName());
|
||||
serviceIntent.setPackage(mCryptoProviderPackageName);
|
||||
mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
|
||||
// NOTE: setPackage is very important to restrict the intent to this provider only!
|
||||
serviceIntent.setPackage(mProviderPackageName);
|
||||
mApplicationContext.bindService(serviceIntent, mServiceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.d(OpenPgpConstants.TAG, "Exception on binding", e);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log.d(OpenPgpConstants.TAG, "already bound");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void unbindFromService() {
|
||||
mApplicationContext.unbindService(mCryptoServiceConnection);
|
||||
mApplicationContext.unbindService(mServiceConnection);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
||||
}
|
2
OpenPGP-Keychain-API/settings.gradle
Normal file
@ -0,0 +1,2 @@
|
||||
include ':example-app'
|
||||
include ':libraries:keychain-api-library'
|
@ -3,6 +3,7 @@ apply plugin: 'android'
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4: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:StickyListHeaders:library')
|
||||
compile project(':libraries:AndroidBootstrap')
|
||||
@ -12,6 +13,7 @@ dependencies {
|
||||
compile project(':libraries:spongycastle:pg')
|
||||
compile project(':libraries:spongycastle:pkix')
|
||||
compile project(':libraries:spongycastle:prov')
|
||||
compile project(':libraries:Android-AppMsg:library')
|
||||
}
|
||||
|
||||
android {
|
||||
@ -19,7 +21,7 @@ android {
|
||||
buildToolsVersion "19.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 8
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 19
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.sufficientlysecure.keychain"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="22001"
|
||||
android:versionName="2.2">
|
||||
android:versionCode="23100"
|
||||
android:versionName="2.3.1">
|
||||
|
||||
<!--
|
||||
General remarks
|
||||
@ -30,7 +30,7 @@
|
||||
-->
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="8"
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<uses-feature
|
||||
@ -153,12 +153,19 @@
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- ENCRYPT with text as extra -->
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.action.ENCRYPT" />
|
||||
|
||||
<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>
|
||||
<!-- Android's Send Action -->
|
||||
<intent-filter android:label="@string/intent_send_encrypt">
|
||||
@ -175,13 +182,40 @@
|
||||
android:label="@string/title_decrypt"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<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-signature" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<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-encrypted" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- DECRYPT with text as extra -->
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.action.DECRYPT" />
|
||||
|
||||
<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>
|
||||
<!-- Android's Send Action -->
|
||||
<intent-filter android:label="@string/intent_send_decrypt">
|
||||
@ -249,17 +283,19 @@
|
||||
android:label="@string/title_key_server_preference"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".ui.SignKeyActivity"
|
||||
android:name=".ui.CertifyKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_sign_key" />
|
||||
android:label="@string/title_certify_key" />
|
||||
<activity
|
||||
android:name=".ui.ImportKeysActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_import_keys"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
|
||||
<!-- Handle URIs with fingerprints when scanning directly from Barcode Scanner -->
|
||||
<intent-filter>
|
||||
<!-- VIEW with fingerprint scheme:
|
||||
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" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@ -272,31 +308,25 @@
|
||||
<data android:scheme="OpenPGP4Fpr" />
|
||||
<data android:scheme="OpenPGP4fpr" />
|
||||
</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>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<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" />
|
||||
</intent-filter>
|
||||
<!-- Keychain's own Actions -->
|
||||
<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 -->
|
||||
<!-- VIEW with file endings: *.gpg (e.g. to import from OI File Manager) -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -317,6 +347,7 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
</intent-filter>
|
||||
<!-- VIEW with file endings: *.asc -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -338,6 +369,31 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
</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
|
||||
android:name=".ui.HelpActivity"
|
||||
@ -361,10 +417,9 @@
|
||||
<activity
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:process=":remote_api"
|
||||
android:taskAffinity=":remote_api" />
|
||||
android:label="@string/app_name" />
|
||||
<!--android:launchMode="singleTop"-->
|
||||
<!--android:process=":remote_api"-->
|
||||
<activity
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
@ -391,19 +446,19 @@
|
||||
</service>
|
||||
|
||||
<!-- Extended Remote API -->
|
||||
<service
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=":remote_api">
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />
|
||||
</intent-filter>
|
||||
<!--<service-->
|
||||
<!--android:name="org.sufficientlysecure.keychain.service.remote.ExtendedApiService"-->
|
||||
<!--android:enabled="true"-->
|
||||
<!--android:exported="true"-->
|
||||
<!--android:process=":remote_api">-->
|
||||
<!--<intent-filter>-->
|
||||
<!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />-->
|
||||
<!--</intent-filter>-->
|
||||
|
||||
<meta-data
|
||||
android:name="api_version"
|
||||
android:value="1" />
|
||||
</service>
|
||||
<!--<meta-data-->
|
||||
<!--android:name="api_version"-->
|
||||
<!--android:value="1" />-->
|
||||
<!--</service>-->
|
||||
|
||||
<!-- TODO: authority! Make this API with content provider uris -->
|
||||
<!-- <provider -->
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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";
|
||||
|
||||
}
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -78,7 +78,7 @@ public final class Id {
|
||||
public static final int filename = 0x00007003;
|
||||
// public static final int output_filename = 0x00007004;
|
||||
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 import_from_qr_code = 0x00007008;
|
||||
public static final int sign_key = 0x00007009;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -42,6 +42,8 @@ import android.content.Context;
|
||||
|
||||
public class PgpKeyHelper {
|
||||
|
||||
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
||||
|
||||
public static Date getCreationDate(PGPPublicKey key) {
|
||||
return key.getCreationTime();
|
||||
}
|
||||
@ -591,8 +593,7 @@ public class PgpKeyHelper {
|
||||
* "Max Mustermann (this is a comment)"
|
||||
* "Max Mustermann [this is nothing]"
|
||||
*/
|
||||
Pattern withComment = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
Matcher matcher = USER_ID_PATTERN.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
result[0] = matcher.group(1);
|
||||
result[1] = matcher.group(3);
|
||||
|
@ -504,7 +504,7 @@ public class PgpKeyOperation {
|
||||
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 {
|
||||
if (passphrase == null) {
|
||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||
@ -512,14 +512,14 @@ public class PgpKeyOperation {
|
||||
PGPPublicKeyRing pubring = ProviderHelper
|
||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||
|
||||
PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (signingKey == null) {
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
@ -527,7 +527,7 @@ public class PgpKeyOperation {
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -110,7 +110,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int 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) {
|
||||
Log.w(Constants.TAG, "Upgrading database to version " + version);
|
||||
|
||||
@ -123,14 +123,17 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
break;
|
||||
case 4:
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 5:
|
||||
// new column: package_signature
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 6:
|
||||
// new column: fingerprint
|
||||
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
|
||||
+ " BLOB;");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
|
@ -359,7 +359,9 @@ public class KeychainProvider extends ContentProvider {
|
||||
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID);
|
||||
// TODO: deprecated master key id
|
||||
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@ -210,6 +211,13 @@ public class ProviderHelper {
|
||||
++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 {
|
||||
context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
|
||||
} catch (RemoteException e) {
|
||||
@ -562,6 +570,26 @@ public class ProviderHelper {
|
||||
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,
|
||||
long[] masterKeyIds) {
|
||||
ArrayList<String> output = new ArrayList<String>();
|
||||
|
@ -43,10 +43,11 @@ import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
|
||||
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.provider.KeychainContract.DataStream;
|
||||
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_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 */
|
||||
|
||||
@ -119,11 +120,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
public static final String ENCRYPT_PROVIDER_URI = "provider_uri";
|
||||
|
||||
// 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_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
|
||||
public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey";
|
||||
|
||||
// save keyring
|
||||
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";
|
||||
|
||||
// sign key
|
||||
public static final String SIGN_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_MASTER_KEY_ID = "sign_key_master_key_id";
|
||||
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||
|
||||
/*
|
||||
* 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_KEY_ID = "signature_key_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_UNKNOWN = "signature_unknown";
|
||||
public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key";
|
||||
|
||||
// import
|
||||
public static final String RESULT_IMPORT_ADDED = "added";
|
||||
@ -241,7 +240,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
String action = intent.getAction();
|
||||
|
||||
// execute action from extra bundle
|
||||
// executeServiceMethod action from extra bundle
|
||||
if (ACTION_ENCRYPT_SIGN.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
@ -322,27 +321,41 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
PgpSignEncrypt.Builder builder =
|
||||
new PgpSignEncrypt.Builder(this, inputData, outStream);
|
||||
builder.progress(this);
|
||||
|
||||
if (generateSignature) {
|
||||
Log.d(Constants.TAG, "generating signature...");
|
||||
operation.generateSignature(useAsciiArmor, false, secretKeyId,
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures());
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().generateSignature();
|
||||
} else if (signOnly) {
|
||||
Log.d(Constants.TAG, "sign only...");
|
||||
operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase(
|
||||
this, secretKeyId), Preferences.getPreferences(this)
|
||||
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
|
||||
.getForceV3Signatures());
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "encrypt...");
|
||||
operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds,
|
||||
encryptionPassphrase, Preferences.getPreferences(this)
|
||||
.getDefaultEncryptionAlgorithm(), secretKeyId, Preferences
|
||||
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures(),
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.compressionId(compressionId)
|
||||
.symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.encryptionKeyIds(encryptionKeyIds)
|
||||
.encryptionPassphrase(encryptionPassphrase)
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
@ -395,12 +408,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
||||
boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY);
|
||||
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
|
||||
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
|
||||
|
||||
boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
@ -474,14 +484,13 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
if (signedOnly) {
|
||||
resultData = operation.verifyText(lookupUnknownKey);
|
||||
} else {
|
||||
resultData = operation.decryptAndVerify(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
assumeSymmetricEncryption);
|
||||
}
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
|
||||
builder.progress(this);
|
||||
|
||||
builder.assumeSymmetric(assumeSymmetricEncryption)
|
||||
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
resultData = builder.build().execute();
|
||||
|
||||
outStream.close();
|
||||
|
||||
@ -785,19 +794,19 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_SIGN_KEYRING.equals(action)) {
|
||||
} else if (ACTION_CERTIFY_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
|
||||
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
masterKeyId);
|
||||
|
||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId,
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
||||
signaturePassPhrase);
|
||||
|
||||
// store the signed key in our local cache
|
||||
|
@ -25,6 +25,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
@ -60,7 +61,14 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -109,6 +109,15 @@ public class AppSettingsFragment extends Fragment implements
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error String on key selection
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
public void setErrorOnSelectKeyFragment(String error) {
|
||||
mSelectKeyFragment.setError(error);
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_select_key_fragment);
|
||||
@ -182,7 +191,7 @@ public class AppSettingsFragment extends Fragment implements
|
||||
// TODO: Better: collapse/expand animation
|
||||
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);u
|
||||
// animation2.setDuration(150);
|
||||
|
||||
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
|
||||
|
@ -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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -17,108 +17,46 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
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 {
|
||||
|
||||
private String getCachedPassphrase(long keyId, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
|
||||
private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
|
||||
private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
|
||||
|
||||
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.
|
||||
*
|
||||
*
|
||||
* @param encryptionUserIds
|
||||
* @return
|
||||
*/
|
||||
private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
private Bundle getKeyIdsFromEmails(Bundle params, String[] encryptionUserIds) {
|
||||
// find key ids to given emails in database
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
|
||||
@ -152,96 +90,129 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
// allow the user to verify pub key selection
|
||||
if (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) {
|
||||
SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback();
|
||||
if (missingUserIdsCheck || dublicateUserIdsCheck) {
|
||||
// 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();
|
||||
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);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback,
|
||||
extras);
|
||||
// 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);
|
||||
|
||||
if (callback.isSuccess()) {
|
||||
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());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (keyIdsArray.length == 0) {
|
||||
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 {
|
||||
public static final String PUB_KEY_IDS = "pub_key_ids";
|
||||
private Bundle getPassphraseBundleIntent(Bundle params, long keyId) {
|
||||
// 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;
|
||||
private long[] pubKeyIds;
|
||||
// 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);
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long[] getPubKeyIds() {
|
||||
return pubKeyIds;
|
||||
}
|
||||
private Bundle signImpl(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output,
|
||||
AppSettings appSettings) {
|
||||
try {
|
||||
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
@Override
|
||||
public void handleUserInput(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
success = true;
|
||||
pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS);
|
||||
// get passphrase from cache, if key has "no" passphrase, this returns an empty String
|
||||
String passphrase;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
|
||||
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
|
||||
} else {
|
||||
success = false;
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction,
|
||||
IOpenPgpKeyIdsCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction);
|
||||
if (keyIds == null) {
|
||||
throw new NoUserIdsException("No user ids!");
|
||||
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;
|
||||
}
|
||||
|
||||
callback.onSuccess(keyIds);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (NoUserIdsException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage());
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
try {
|
||||
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) {
|
||||
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,
|
||||
final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings, boolean sign) {
|
||||
private Bundle encryptAndSignImpl(Bundle params, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings,
|
||||
boolean sign) {
|
||||
try {
|
||||
// TODO: other options of OpenPgpData!
|
||||
byte[] inputBytes = getInput(inputData);
|
||||
boolean asciiArmor = false;
|
||||
if (outputData.getType() == OpenPgpData.TYPE_STRING) {
|
||||
asciiArmor = true;
|
||||
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
long[] keyIds;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_KEY_IDS)) {
|
||||
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
|
||||
@ -249,351 +220,339 @@ public class OpenPgpService extends RemoteService {
|
||||
keyIds[keyIds.length - 1] = appSettings.getKeyId();
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputDt = new InputData(inputStream, inputLength);
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
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) {
|
||||
String passphrase = getCachedPassphrase(appSettings.getKeyId(),
|
||||
allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
if (sign) {
|
||||
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;
|
||||
}
|
||||
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
|
||||
appSettings.getHashAlgorithm(), true, passphrase);
|
||||
} else {
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
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();
|
||||
// sign and encrypt
|
||||
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
||||
.signatureForceV3(false)
|
||||
.signatureKeyId(appSettings.getKeyId())
|
||||
.signaturePassphrase(passphrase);
|
||||
} 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 (!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!");
|
||||
// encrypt only
|
||||
builder.signatureKeyId(Id.key.none);
|
||||
}
|
||||
// execute PGP operation!
|
||||
builder.build().execute();
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Bundle outputBundle;
|
||||
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());
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} 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
|
||||
*
|
||||
* @param callback
|
||||
* @param errorId
|
||||
* @param message
|
||||
* Check requirements:
|
||||
* - params != null
|
||||
* - has supported API version
|
||||
* - is allowed to call the service (access has been granted)
|
||||
*
|
||||
* @param params
|
||||
* @return null if everything is okay, or a Bundle with an error/PendingIntent
|
||||
*/
|
||||
private void callbackOpenPgpError(IOpenPgpCallback 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
private Bundle checkRequirements(Bundle params) {
|
||||
// params Bundle is required!
|
||||
if (params == null) {
|
||||
Bundle result = new Bundle();
|
||||
OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
|
||||
@Override
|
||||
public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle sign(Bundle params, final ParcelFileDescriptor input, final ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, false);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return signImpl(params, input, output, appSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output,
|
||||
final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle encrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, true);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return encryptAndSignImpl(params, input, output, appSettings, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sign(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle signAndEncrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
signSafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return encryptAndSignImpl(params, input, output, appSettings, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
public Bundle decryptAndVerify(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
decryptAndVerifySafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return decryptAndVerifyImpl(params, input, output, appSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getKeyIds(final String[] userIds, final boolean allowUserInteraction,
|
||||
final IOpenPgpKeyIdsCallback callback) throws RemoteException {
|
||||
public Bundle getKeyIds(Bundle params) {
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getKeyIdsSafe(userIds, allowUserInteraction, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return getKeyIdsImpl(params);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
|
@ -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
|
||||
* 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.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.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -50,66 +49,19 @@ import android.os.Messenger;
|
||||
public abstract class RemoteService extends Service {
|
||||
Context mContext;
|
||||
|
||||
private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
|
||||
// TODO: Are these parameters okay?
|
||||
private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
|
||||
TimeUnit.SECONDS, mPoolQueue);
|
||||
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
|
||||
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
|
||||
|
||||
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() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
|
||||
* execution
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
protected void checkAndEnqueue(Runnable r) {
|
||||
protected Bundle isAllowed(Bundle params) {
|
||||
try {
|
||||
if (isCallerAllowed(false)) {
|
||||
mThreadPool.execute(r);
|
||||
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
return null;
|
||||
} else {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(
|
||||
Binder.getCallingUid());
|
||||
@ -121,32 +73,46 @@ public abstract class RemoteService extends Service {
|
||||
packageSignature = getPackageSignature(packageName);
|
||||
} catch (NameNotFoundException 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,
|
||||
"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();
|
||||
Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!");
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback,
|
||||
extras);
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
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()) {
|
||||
mThreadPool.execute(r);
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
} else {
|
||||
Log.d(Constants.TAG, "User disallowed app!");
|
||||
}
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, 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;
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
Log.e(Constants.TAG, e.getMessage());
|
||||
Log.e(Constants.TAG, "wrong signature!", e);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras);
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature));
|
||||
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,41 +126,9 @@ public abstract class RemoteService extends Service {
|
||||
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
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected AppSettings getAppSettings() {
|
||||
@ -215,66 +149,11 @@ public abstract class RemoteService extends Service {
|
||||
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
|
||||
* process) is in the list of allowed package names.
|
||||
*
|
||||
* @param allowOnlySelf
|
||||
* allow only Keychain app itself
|
||||
*
|
||||
* @param allowOnlySelf allow only Keychain app itself
|
||||
* @return true if process is allowed to use this service
|
||||
* @throws WrongPackageSignatureException
|
||||
*/
|
||||
@ -308,7 +187,7 @@ public abstract class RemoteService extends Service {
|
||||
|
||||
/**
|
||||
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
||||
*
|
||||
*
|
||||
* @param packageName
|
||||
* @return
|
||||
* @throws WrongPackageSignatureException
|
||||
|
@ -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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -17,8 +17,15 @@
|
||||
|
||||
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.keychain.Constants;
|
||||
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.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
@ -64,17 +63,11 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
// error message
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
// register view
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
// select pub keys view
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -82,36 +75,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
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) {
|
||||
finishHandled = false;
|
||||
|
||||
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)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
@ -125,44 +94,27 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
// user needs to select a key!
|
||||
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
|
||||
Toast.makeText(RemoteServiceActivity.this,
|
||||
R.string.api_register_error_select_key, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
mSettingsFragment.setErrorOnSelectKeyFragment(
|
||||
getString(R.string.api_register_error_select_key));
|
||||
} else {
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mSettingsFragment.getAppSettings());
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.OKAY;
|
||||
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);
|
||||
}
|
||||
// give params through for new service call
|
||||
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, oldParams);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
}, R.string.api_register_disallow, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -176,8 +128,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
||||
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)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
@ -185,8 +138,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
|
||||
|
||||
String text = new String();
|
||||
text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
// TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids
|
||||
String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
text += "<br/><br/>";
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_missing_text);
|
||||
@ -213,40 +166,22 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY;
|
||||
Bundle data = new Bundle();
|
||||
data.putLongArray(
|
||||
OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS,
|
||||
// add key ids to params Bundle for new request
|
||||
Bundle params = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
|
||||
params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS,
|
||||
mSelectFragment.getSelectedMasterKeyIds());
|
||||
msg.setData(data);
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
Intent finishIntent = new Intent();
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL;
|
||||
;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -279,8 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
} else if (ACTION_ERROR_MESSAGE.equals(action)) {
|
||||
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
|
||||
|
||||
String text = new String();
|
||||
text += "<font color=\"red\">" + errorMessage + "</font>";
|
||||
String text = "<font color=\"red\">" + errorMessage + "</font>";
|
||||
|
||||
// Inflate a "Done" custom action bar view
|
||||
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay,
|
||||
@ -288,7 +222,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
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);
|
||||
textView.setHtmlFromString(text);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Wrong action!");
|
||||
Log.e(Constants.TAG, "Action does not exist!");
|
||||
setResult(RESULT_CANCELED);
|
||||
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
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog(long secretKeyId) {
|
||||
private void showPassphraseDialog(final Bundle params, long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
// return given params again, for calling the service method again
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
|
||||
} else {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
};
|
||||
|
||||
@ -345,9 +271,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, do pgp operation directly!");
|
||||
// return given params again, for calling the service method again
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
setResult(RESULT_OK, finishIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
public class WrongPackageSignatureException extends Exception {
|
||||
|
@ -56,7 +56,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
/**
|
||||
* 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 {
|
||||
private BootstrapButton mSignButton;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
@ -72,7 +72,7 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.sign_key_activity);
|
||||
setContentView(R.layout.certify_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
@ -164,8 +164,8 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key!");
|
||||
// send message to handler to start certification directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
@ -196,8 +196,8 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(mMasterKeyId);
|
||||
return; // bail out; need to wait until the user has entered the passphrase
|
||||
// before trying again
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
return;
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
@ -218,13 +218,13 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
// Send all information needed to service to sign key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING);
|
||||
intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@ -237,14 +237,12 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
|
||||
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();
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
// upload the newly signed key to the keyserver
|
||||
uploadKey();
|
||||
} else {
|
||||
setResult(RESULT_OK);
|
||||
@ -291,7 +289,7 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
super.handleMessage(message);
|
||||
|
||||
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();
|
||||
|
||||
setResult(RESULT_OK);
|
@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
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.PgpGeneralException;
|
||||
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.ui.dialog.DeleteFileDialogFragment;
|
||||
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.util.Log;
|
||||
|
||||
@ -61,7 +60,7 @@ import android.view.animation.AnimationUtils;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
@ -78,14 +77,20 @@ public class DecryptActivity extends DrawerActivity {
|
||||
/* EXTRA keys for input */
|
||||
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 boolean mReturnResult = false;
|
||||
|
||||
// TODO: replace signed only checks with something more intelligent
|
||||
// PgpDecryptVerify should handle all automatically!!!
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private LinearLayout mSignatureLayout = null;
|
||||
private RelativeLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
@ -100,6 +105,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private BootstrapButton mBrowse = null;
|
||||
private BootstrapButton mLookupKey = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
@ -107,14 +113,10 @@ public class DecryptActivity extends DrawerActivity {
|
||||
private Uri mContentUri = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
private boolean mLookupUnknownKey = true;
|
||||
|
||||
private boolean mDecryptImmediately = false;
|
||||
|
||||
private BootstrapButton mDecryptButton;
|
||||
@ -154,7 +156,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
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);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
@ -171,7 +173,15 @@ public class DecryptActivity extends DrawerActivity {
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
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);
|
||||
if (key != null) {
|
||||
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);
|
||||
startActivity(intent);
|
||||
}
|
||||
@ -263,14 +273,14 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
if (mDecryptImmediately
|
||||
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
decryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
@ -287,13 +297,13 @@ public class DecryptActivity extends DrawerActivity {
|
||||
* Android's Action
|
||||
*/
|
||||
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)) {
|
||||
// Plain text
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
// 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);
|
||||
action = ACTION_DECRYPT;
|
||||
}
|
||||
@ -375,21 +385,21 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +459,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
} else { // mDecryptTarget == Id.target.message
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
@ -527,7 +537,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
try {
|
||||
if (inStream.markSupported()) {
|
||||
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);
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
@ -539,7 +549,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
inStream.reset();
|
||||
}
|
||||
mSecretKeyId = Id.key.symmetric;
|
||||
if (!PgpOperation.hasSymmetricEncryption(this, inStream)) {
|
||||
if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
@ -559,7 +569,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -587,28 +597,10 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
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");
|
||||
Intent intent = new Intent(this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
@ -644,8 +636,6 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
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_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
|
||||
|
||||
@ -662,15 +652,6 @@ public class DecryptActivity extends DrawerActivity {
|
||||
// get returned data bundle
|
||||
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;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
|
||||
@ -685,26 +666,26 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
@ -727,19 +708,24 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
} else if (returnData
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
Toast.makeText(DecryptActivity.this,
|
||||
R.string.unknown_signature_key_touch_to_look_up,
|
||||
R.string.unknown_signature,
|
||||
Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
@ -756,36 +742,37 @@ public class DecryptActivity extends DrawerActivity {
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
case RESULT_CODE_FILE: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// ImportKeysActivity and user looked uo key
|
||||
case Id.request.look_up_key_id: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
// decrypt again without lookup
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// ImportKeysActivity and user looked uo key
|
||||
case RESULT_CODE_LOOKUP_KEY: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
if (resultCode == RESULT_OK) {
|
||||
// decrypt again
|
||||
decryptStart();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ public class EncryptActivity extends DrawerActivity {
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
// 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.putBoolean(EXTRA_ASCII_ARMOR, true);
|
||||
action = ACTION_ENCRYPT;
|
||||
|