OpenPGP API library
The OpenPGP API provides methods to execute OpenPGP operations, such as sign, encrypt, decrypt, verify, and more without user interaction from background threads. This is done by connecting your client application to a remote service provided by OpenKeychain or other OpenPGP providers.
News
Version 12
- OpenPgpDecryptionResult and OpenPgpSignatureResult are now immutable
- Added PROGRESS_MESSENGER and DATA_LENGTH extras for ACTION_DECRYPT_VERIFY. This allows to the client app to get periodic updates for displaying a progress bar on decryption.
- Added ACTION_BACKUP
- Added special API calls for better K-9 Mail integration:
Check for sender address matching with EXTRA_SENDER_ADDRESS and result in OpenPgpSignatureResult
Opportunistic encryption mode with EXTRA_OPPORTUNISTIC_ENCRYPTION
There is an external ContentProvider at org.sufficientlysecure.keychain.provider.exported for querying available keys (CAUTION: This API is not final!)
License
While OpenKeychain itself is GPLv3+, the API library is licensed under Apache License v2. Thus, you are allowed to also use it in closed source applications as long as you respect the Apache License v2.
Add the API library to your project
Add this to your build.gradle:
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
}
Full example
A full working example is available in the example project. The OpenPgpApiActivity.java
contains most relevant sourcecode.
API
OpenPgpApi contains all possible Intents and available extras.
Short tutorial
This tutorial only covers the basics, please consult the full example for a complete overview over all methods
The API is not designed around Intents
which are started via startActivityForResult
. These Intent actions typically start an activity for user interaction, so they are not suitable for background tasks. Most API design decisions are explained at the bottom of this wiki page.
We will go through the basic steps to understand how this API works, following this (greatly simplified) sequence diagram:
In this diagram the client app is depicted on the left side, the OpenPGP provider (in this case OpenKeychain) is depicted on the right. The remote service is defined via the AIDL file IOpenPgpService
. It contains only one exposed method which can be invoked remotely:
interface IOpenPgpService {
Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
}
The interaction between the apps is done by binding from your client app to the remote service of OpenKeychain. OpenPgpServiceConnection
is a helper class from the library to ease this step:
OpenPgpServiceConnection mServiceConnection;
public void onCreate(Bundle savedInstance) {
[...]
mServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain");
mServiceConnection.bindToService();
}
public void onDestroy() {
[...]
if (mServiceConnection != null) {
mServiceConnection.unbindFromService();
}
}
Following the sequence diagram, these steps are executed:
-
Define an
Intent
containing the actual PGP instructions which should be done, e.g.Intent data = new Intent(); data.setAction(OpenPgpApi.ACTION_ENCRYPT); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[]{"dominik@dominikschuermann.de"}); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
Define an
InputStream
currently holding the plaintext, and anOutputStream
where you want the ciphertext to be written by OpenKeychain's remote service:InputStream is = new ByteArrayInputStream("Hello world!".getBytes("UTF-8")); ByteArrayOutputStream os = new ByteArrayOutputStream();
Using a helper class from the library,
is
andos
are passed viaParcelFileDescriptors
asinput
andoutput
together withIntent data
, as depicted in the sequence diagram, from the client to the remote service. Programmatically, this can be done with:OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService()); Intent result = api.executeApi(data, is, os);
-
The PGP operation is executed by OpenKeychain and the produced ciphertext is written into
os
which can then be accessed by the client app. -
A result Intent is returned containing one of these result codes:
OpenPgpApi.RESULT_CODE_ERROR
OpenPgpApi.RESULT_CODE_SUCCESS
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED
If
RESULT_CODE_USER_INTERACTION_REQUIRED
is returned, an additionalPendingIntent
is returned to the client, which must be used to get user input required to process the request. APendingIntent
is executed withstartIntentSenderForResult
, which starts an activity, originally belonging to OpenKeychain, on the task stack of the client. Only ifRESULT_CODE_SUCCESS
is returned,os
actually contains data. A nearly complete example looks like this:switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: { try { Log.d(OpenPgpApi.TAG, "output: " + os.toString("UTF-8")); } catch (UnsupportedEncodingException e) { Log.e(Constants.TAG, "UnsupportedEncodingException", e); } if (result.hasExtra(OpenPgpApi.RESULT_SIGNATURE)) { OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); [...] } break; } case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: { PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); try { startIntentSenderForResult(pi.getIntentSender(), 42, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.e(Constants.TAG, "SendIntentException", e); } break; } case OpenPgpApi.RESULT_CODE_ERROR: { OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); [...] break; } }
-
Results from a
PendingIntent
are returned inonActivityResult
of the activity, which executedstartIntentSenderForResult
. The returnedIntent data
inonActivityResult
contains the original PGP operation definition and new values acquired from the user interaction. Thus, you can now execute theIntent
again, like done in step 1. This time it should return withRESULT_CODE_SUCCESS
because all required information has been obtained by the previous user interaction stored in thisIntent
.protected void onActivityResult(int requestCode, int resultCode, Intent data) { [...] // try again after user interaction if (resultCode == RESULT_OK) { switch (requestCode) { case 42: { encrypt(data); // defined like in step 1 break; } } } }
Tipps
-
api.executeApi(data, is, os);
is a blocking call. If you want a convenient asynchronous call, useapi.executeApiAsync(data, is, os, new MyCallback([... ]));
, whereMyCallback
is an private class implementingOpenPgpApi.IOpenPgpCallback
. SeeOpenPgpApiActivity.java
for an example. -
Using
mServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain");
connects to OpenKeychain directly. If you want to let the user choose between OpenPGP providers, you can implement the
OpenPgpAppPreference.java
like done in the example app. -
To enable installing a debug and release version at the same time, the
debug
build of OpenKeychain usesorg.sufficientlysecure.keychain.debug
as a package name. Make sure you connect to the right one during development!