From 312b735fbd0303a49e3d2efbefd2076c432f276f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 15 Sep 2013 15:20:15 +0200 Subject: Extended api --- .../keychain/helper/PgpToX509.java | 307 +++++++++++++++++++ .../keychain/service/IKeychainKeyService.aidl | 32 -- .../keychain/service/KeychainKeyService.java | 139 --------- .../handler/IKeychainGetKeyringsHandler.aidl | 28 -- .../service/remote/ExtendedApiService.java | 125 ++++++++ .../service/remote/IExtendedApiCallback.aidl | 24 ++ .../service/remote/IExtendedApiService.aidl | 48 +++ .../keychain/service/remote/OpenPgpService.java | 45 ++- .../service/remote/OpenPgpServiceActivity.java | 325 --------------------- .../keychain/service/remote/RemoteApiService.java | 281 ------------------ .../keychain/service/remote/RemoteService.java | 285 ++++++++++++++++++ .../service/remote/RemoteServiceActivity.java | 325 +++++++++++++++++++++ 12 files changed, 1136 insertions(+), 828 deletions(-) create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/PgpToX509.java delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/IKeychainKeyService.aidl delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainKeyService.java delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java delete mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java create mode 100644 OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java (limited to 'OpenPGP-Keychain/src') diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/PgpToX509.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/PgpToX509.java new file mode 100644 index 000000000..34815a53d --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/PgpToX509.java @@ -0,0 +1,307 @@ +package org.sufficientlysecure.keychain.helper; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.DateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.Vector; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.spongycastle.asn1.DERObjectIdentifier; +import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; +import org.spongycastle.asn1.x509.BasicConstraints; +import org.spongycastle.asn1.x509.GeneralName; +import org.spongycastle.asn1.x509.GeneralNames; +import org.spongycastle.asn1.x509.SubjectKeyIdentifier; +import org.spongycastle.asn1.x509.X509Extensions; +import org.spongycastle.asn1.x509.X509Name; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.x509.X509V3CertificateGenerator; +import org.spongycastle.x509.extension.AuthorityKeyIdentifierStructure; +import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class PgpToX509 { + public final static String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge"; + public final static String DN_COMMON_PART_OU = "OpenPGP Keychain cert"; + + /** + * Creates a self-signed certificate from a public and private key. The (critical) key-usage + * extension is set up with: digital signature, non-repudiation, key-encipherment, key-agreement + * and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and + * S/MIME. A URI subjectAltName may also be set up. + * + * @param pubKey + * public key + * @param privKey + * private key + * @param subject + * subject (and issuer) DN for this certificate, RFC 2253 format preferred. + * @param startDate + * date from which the certificate will be valid (defaults to current date and time + * if null) + * @param endDate + * date until which the certificate will be valid (defaults to current date and time + * if null) * + * @param subjAltNameURI + * URI to be placed in subjectAltName + * @return self-signed certificate + * @throws InvalidKeyException + * @throws SignatureException + * @throws NoSuchAlgorithmException + * @throws IllegalStateException + * @throws NoSuchProviderException + * @throws CertificateException + * @throws Exception + * + * @author Bruno Harbulot + */ + public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey, + X509Name subject, Date startDate, Date endDate, String subjAltNameURI) + throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, + SignatureException, CertificateException, NoSuchProviderException { + + X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator(); + + certGenerator.reset(); + /* + * Sets up the subject distinguished name. Since it's a self-signed certificate, issuer and + * subject are the same. + */ + certGenerator.setIssuerDN(subject); + certGenerator.setSubjectDN(subject); + + /* + * Sets up the validity dates. + */ + if (startDate == null) { + startDate = new Date(System.currentTimeMillis()); + } + certGenerator.setNotBefore(startDate); + if (endDate == null) { + endDate = new Date(startDate.getTime() + (365L * 24L * 60L * 60L * 1000L)); + Log.d(Constants.TAG, "end date is=" + DateFormat.getDateInstance().format(endDate)); + } + + certGenerator.setNotAfter(endDate); + + /* + * The serial-number of this certificate is 1. It makes sense because it's self-signed. + */ + certGenerator.setSerialNumber(BigInteger.ONE); + /* + * Sets the public-key to embed in this certificate. + */ + certGenerator.setPublicKey(pubKey); + /* + * Sets the signature algorithm. + */ + String pubKeyAlgorithm = pubKey.getAlgorithm(); + if (pubKeyAlgorithm.equals("DSA")) { + certGenerator.setSignatureAlgorithm("SHA1WithDSA"); + } else if (pubKeyAlgorithm.equals("RSA")) { + certGenerator.setSignatureAlgorithm("SHA1WithRSAEncryption"); + } else { + RuntimeException re = new RuntimeException("Algorithm not recognised: " + + pubKeyAlgorithm); + Log.e(Constants.TAG, re.getMessage(), re); + throw re; + } + + /* + * Adds the Basic Constraint (CA: true) extension. + */ + certGenerator.addExtension(X509Extensions.BasicConstraints, true, + new BasicConstraints(true)); + + /* + * Adds the subject key identifier extension. + */ + SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pubKey); + certGenerator + .addExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier); + + /* + * Adds the authority key identifier extension. + */ + AuthorityKeyIdentifier authorityKeyIdentifier = new AuthorityKeyIdentifierStructure(pubKey); + certGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, + authorityKeyIdentifier); + + /* + * Adds the subject alternative-name extension. + */ + if (subjAltNameURI != null) { + GeneralNames subjectAltNames = new GeneralNames(new GeneralName( + GeneralName.uniformResourceIdentifier, subjAltNameURI)); + certGenerator.addExtension(X509Extensions.SubjectAlternativeName, false, + subjectAltNames); + } + + /* + * Creates and sign this certificate with the private key corresponding to the public key of + * the certificate (hence the name "self-signed certificate"). + */ + X509Certificate cert = certGenerator.generate(privKey); + + /* + * Checks that this certificate has indeed been correctly signed. + */ + cert.verify(pubKey); + + return cert; + } + + /** + * Creates a self-signed certificate from a PGP Secret Key. + * + * @param pgpSecKey + * PGP Secret Key (from which one can extract the public and private keys and other + * attributes). + * @param pgpPrivKey + * PGP Private Key corresponding to the Secret Key (password callbacks should be done + * before calling this method) + * @param subjAltNameURI + * optional URI to embed in the subject alternative-name + * @return self-signed certificate + * @throws PGPException + * @throws NoSuchProviderException + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws SignatureException + * @throws CertificateException + * + * @author Bruno Harbulot + */ + public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey, + PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException, + NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, + SignatureException, CertificateException { + // get public key from secret key + PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey(); + + // LOGGER.info("Key ID: " + Long.toHexString(pgpPubKey.getKeyID() & 0xffffffffL)); + + /* + * The X.509 Name to be the subject DN is prepared. The CN is extracted from the Secret Key + * user ID. + */ + Vector x509NameOids = new Vector(); + Vector x509NameValues = new Vector(); + + x509NameOids.add(X509Name.O); + x509NameValues.add(DN_COMMON_PART_O); + + x509NameOids.add(X509Name.OU); + x509NameValues.add(DN_COMMON_PART_OU); + + for (@SuppressWarnings("unchecked") + Iterator it = (Iterator) pgpSecKey.getUserIDs(); it.hasNext();) { + Object attrib = it.next(); + x509NameOids.add(X509Name.CN); + x509NameValues.add("CryptoCall"); + // x509NameValues.add(attrib.toString()); + } + + /* + * Currently unused. + */ + Log.d(Constants.TAG, "User attributes: "); + for (@SuppressWarnings("unchecked") + Iterator it = (Iterator) pgpSecKey.getUserAttributes(); it.hasNext();) { + Object attrib = it.next(); + Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass()); + } + + X509Name x509name = new X509Name(x509NameOids, x509NameValues); + + Log.d(Constants.TAG, "Subject DN: " + x509name); + + /* + * To check the signature from the certificate on the recipient side, the creation time + * needs to be embedded in the certificate. It seems natural to make this creation time be + * the "not-before" date of the X.509 certificate. Unlimited PGP keys have a validity of 0 + * second. In this case, the "not-after" date will be the same as the not-before date. This + * is something that needs to be checked by the service receiving this certificate. + */ + Date creationTime = pgpPubKey.getCreationTime(); + Log.d(Constants.TAG, + "pgp pub key creation time=" + DateFormat.getDateInstance().format(creationTime)); + Log.d(Constants.TAG, "pgp valid seconds=" + pgpPubKey.getValidSeconds()); + Date validTo = null; + if (pgpPubKey.getValidSeconds() > 0) { + validTo = new Date(creationTime.getTime() + 1000L * pgpPubKey.getValidSeconds()); + } + + X509Certificate selfSignedCert = createSelfSignedCert( + pgpPubKey.getKey(PgpMain.BOUNCY_CASTLE_PROVIDER_NAME), pgpPrivKey.getKey(), + x509name, creationTime, validTo, subjAltNameURI); + + return selfSignedCert; + } + + /** + * This is a password callback handler that will fill in a password automatically. Useful to + * configure passwords in advance, but should be used with caution depending on how much you + * allow passwords to be stored within your application. + * + * @author Bruno Harbulot. + * + */ + public final static class PredefinedPasswordCallbackHandler implements CallbackHandler { + + private char[] password; + private String prompt; + + public PredefinedPasswordCallbackHandler(String password) { + this(password == null ? null : password.toCharArray(), null); + } + + public PredefinedPasswordCallbackHandler(char[] password) { + this(password, null); + } + + public PredefinedPasswordCallbackHandler(String password, String prompt) { + this(password == null ? null : password.toCharArray(), prompt); + } + + public PredefinedPasswordCallbackHandler(char[] password, String prompt) { + this.password = password; + this.prompt = prompt; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof PasswordCallback) { + PasswordCallback pwCallback = (PasswordCallback) callback; + if ((this.prompt == null) || (this.prompt.equals(pwCallback.getPrompt()))) { + pwCallback.setPassword(this.password); + } + } else { + throw new UnsupportedCallbackException(callback, "Unrecognised callback."); + } + } + } + + protected final Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + } +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/IKeychainKeyService.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/IKeychainKeyService.aidl deleted file mode 100644 index ecea2b8ff..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/IKeychainKeyService.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * 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.service; - -import org.sufficientlysecure.keychain.service.handler.IKeychainGetKeyringsHandler; - -/** - * All methods are oneway, which means they are asynchronous and non-blocking. - * Results are returned into given Handler, which has to be implemented on client side. - */ -interface IKeychainKeyService { - - oneway void getPublicKeyRings(in long[] masterKeyIds, in boolean asAsciiArmoredStringArray, - in IKeychainGetKeyringsHandler handler); - - oneway void getSecretKeyRings(in long[] masterKeyIds, in boolean asAsciiArmoredStringArray, - in IKeychainGetKeyringsHandler handler); -} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainKeyService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainKeyService.java deleted file mode 100644 index e43dca12f..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainKeyService.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.service; - -import java.util.ArrayList; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.service.IKeychainKeyService; -import org.sufficientlysecure.keychain.service.handler.IKeychainGetKeyringsHandler; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.os.RemoteException; - -public class KeychainKeyService extends Service { - Context mContext; - - @Override - public void onCreate() { - super.onCreate(); - mContext = this; - Log.d(Constants.TAG, "ApgKeyService, onCreate()"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(Constants.TAG, "ApgKeyService, onDestroy()"); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - /** - * Synchronized implementation of getPublicKeyRings - */ - private synchronized void getPublicKeyRingsSafe(long[] masterKeyIds, - boolean asAsciiArmoredStringArray, IKeychainGetKeyringsHandler handler) - throws RemoteException { - if (asAsciiArmoredStringArray) { - ArrayList output = ProviderHelper.getPublicKeyRingsAsArmoredString(mContext, - masterKeyIds); - - handler.onSuccess(null, output); - } else { - byte[] outputBytes = ProviderHelper - .getPublicKeyRingsAsByteArray(mContext, masterKeyIds); - handler.onSuccess(outputBytes, null); - } - } - - /** - * Synchronized implementation of getSecretKeyRings - */ - private synchronized void getSecretKeyRingsSafe(long[] masterKeyIds, - boolean asAsciiArmoredStringArray, IKeychainGetKeyringsHandler handler) - throws RemoteException { - if (asAsciiArmoredStringArray) { - ArrayList output = ProviderHelper.getSecretKeyRingsAsArmoredString(mContext, - masterKeyIds); - - handler.onSuccess(null, output); - } else { - byte[] outputBytes = ProviderHelper - .getSecretKeyRingsAsByteArray(mContext, masterKeyIds); - handler.onSuccess(outputBytes, null); - } - - } - - /** - * This is the implementation of the interface IApgKeyService. All methods are oneway, meaning - * asynchronous and return to the client using handlers. - * - * The real PGP code is located in PGPMain. - */ - private final IKeychainKeyService.Stub mBinder = new IKeychainKeyService.Stub() { - - @Override - public void getPublicKeyRings(long[] masterKeyIds, boolean asAsciiArmoredStringArray, - IKeychainGetKeyringsHandler handler) throws RemoteException { - getPublicKeyRingsSafe(masterKeyIds, asAsciiArmoredStringArray, handler); - } - - @Override - public void getSecretKeyRings(long[] masterKeyIds, boolean asAsciiArmoredStringArray, - IKeychainGetKeyringsHandler handler) throws RemoteException { - getSecretKeyRingsSafe(masterKeyIds, asAsciiArmoredStringArray, handler); - } - - }; - - /** - * As we can not throw an exception through Android RPC, we assign identifiers to the exception - * types. - * - * @param e - * @return - */ - // private int getExceptionId(Exception e) { - // if (e instanceof NoSuchProviderException) { - // return 0; - // } else if (e instanceof NoSuchAlgorithmException) { - // return 1; - // } else if (e instanceof SignatureException) { - // return 2; - // } else if (e instanceof IOException) { - // return 3; - // } else if (e instanceof ApgGeneralException) { - // return 4; - // } else if (e instanceof PGPException) { - // return 5; - // } else { - // return -1; - // } - // } - -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl deleted file mode 100644 index c3a7d1faf..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * 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.service.handler; - -interface IKeychainGetKeyringsHandler { - /** - * Either outputBytes or outputString is given. One of them is null - * - */ - oneway void onSuccess(in byte[] outputBytes, in List outputString); - - - oneway void onException(in int exceptionNumber, in String message); -} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java new file mode 100644 index 000000000..beb31d081 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.service.remote; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.security.cert.X509Certificate; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; + +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openssl.PEMWriter; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.helper.PgpHelper; +import org.sufficientlysecure.keychain.helper.PgpMain; +import org.sufficientlysecure.keychain.helper.PgpToX509; +import org.sufficientlysecure.keychain.util.Log; + +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; + +public class ExtendedApiService extends RemoteService { + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private void selfSignedX509CertSafe(String subjAltNameURI, IExtendedApiCallback callback, + AppSettings appSettings) throws RemoteException { + + // TODO: for pgp keyrings with password + CallbackHandler pgpPwdCallbackHandler = new PgpToX509.PredefinedPasswordCallbackHandler(""); + + try { + long keyId = appSettings.getKeyId(); + PGPSecretKey pgpSecretKey = PgpHelper.getSigningKey(this, keyId); + + PasswordCallback pgpSecKeyPasswordCallBack = new PasswordCallback("pgp passphrase?", + false); + pgpPwdCallbackHandler.handle(new Callback[] { pgpSecKeyPasswordCallBack }); + PGPPrivateKey pgpPrivKey = pgpSecretKey.extractPrivateKey( + pgpSecKeyPasswordCallBack.getPassword(), PgpMain.BOUNCY_CASTLE_PROVIDER_NAME); + pgpSecKeyPasswordCallBack.clearPassword(); + + X509Certificate selfSignedCert = PgpToX509.createSelfSignedCert(pgpSecretKey, + pgpPrivKey, subjAltNameURI); + + // Write x509cert and privKey into files + // FileOutputStream fosCert = context.openFileOutput(CERT_FILENAME, + // Context.MODE_PRIVATE); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + PEMWriter pemWriterCert = new PEMWriter(new PrintWriter(outStream)); + pemWriterCert.writeObject(selfSignedCert); + pemWriterCert.close(); + + byte[] outputBytes = outStream.toByteArray(); + + callback.onSuccess(outputBytes); + } catch (Exception e) { + Log.e(Constants.TAG, "ExtendedApiService", e); + callback.onError(e.getMessage()); + } + + // TODO: no private key at the moment! Don't give it to others + // PrivateKey privKey = pgpPrivKey.getKey(); + // FileOutputStream fosKey = context.openFileOutput(PRIV_KEY_FILENAME, + // Context.MODE_PRIVATE); + // PEMWriter pemWriterKey = new PEMWriter(new PrintWriter(fosKey)); + // pemWriterKey.writeObject(privKey); + // pemWriterKey.close(); + } + + private final IExtendedApiService.Stub mBinder = new IExtendedApiService.Stub() { + + @Override + public void encrypt(byte[] inputBytes, String passphrase, IExtendedApiCallback callback) + throws RemoteException { + // TODO : implement + + } + + @Override + public void selfSignedX509Cert(final String subjAltNameURI, + final IExtendedApiCallback callback) throws RemoteException { + final AppSettings settings = getAppSettings(); + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + selfSignedX509CertSafe(subjAltNameURI, callback, settings); + } catch (RemoteException e) { + Log.e(Constants.TAG, "OpenPgpService", e); + } + } + }; + + checkAndEnqueue(r); + + } + + }; + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl new file mode 100644 index 000000000..f69f66fd7 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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.service.remote; + +interface IExtendedApiCallback { + + oneway void onSuccess(in byte[] outputBytes); + + oneway void onError(in String error); +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl new file mode 100644 index 000000000..669bd31b5 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiService.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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.service.remote; + +import org.sufficientlysecure.keychain.service.remote.IExtendedApiCallback; + +/** + * 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 IExtendedApiService { + + /** + * Symmetric Encrypt + * + * @param inputBytes + * Byte array you want to encrypt + * @param passphrase + * symmetric passhprase + * @param callback + * Callback where to return results + */ + oneway void encrypt(in byte[] inputBytes, in String passphrase, in IExtendedApiCallback callback); + + /** + * Generates self signed X509 certificate signed by OpenPGP private key (from app settings) + * + * @param subjAltNameURI + * @param callback + * Callback where to return results + */ + oneway void selfSignedX509Cert(in String subjAltNameURI, in IExtendedApiCallback callback); + +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java index 7955a7f48..ed2c12419 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java @@ -49,18 +49,18 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -public class OpenPgpService extends RemoteApiService { +public class OpenPgpService extends RemoteService { @Override public void onCreate() { super.onCreate(); - Log.d(Constants.TAG, "CryptoService, onCreate()"); + Log.d(Constants.TAG, "OpenPgpService, onCreate()"); } @Override public void onDestroy() { super.onDestroy(); - Log.d(Constants.TAG, "CryptoService, onDestroy()"); + Log.d(Constants.TAG, "OpenPgpService, onDestroy()"); } @Override @@ -69,26 +69,26 @@ public class OpenPgpService extends RemoteApiService { } private String getCachedPassphrase(long keyId) { - String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId); + String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId); if (passphrase == null) { Log.d(Constants.TAG, "No passphrase! Activity required!"); // start passphrase dialog Bundle extras = new Bundle(); - extras.putLong(OpenPgpServiceActivity.EXTRA_SECRET_KEY_ID, keyId); + extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId); PassphraseActivityCallback callback = new PassphraseActivityCallback(); Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); - pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_CACHE_PASSPHRASE, + pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, messenger, extras); if (callback.isSuccess()) { Log.d(Constants.TAG, "New passphrase entered!"); // get again after it was entered - passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId); + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId); } else { Log.d(Constants.TAG, "Passphrase dialog canceled!"); @@ -165,12 +165,12 @@ public class OpenPgpService extends RemoteApiService { Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); Bundle extras = new Bundle(); - extras.putLongArray(OpenPgpServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); - extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); - extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_DUBLICATE_USER_IDS, + 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); - pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_SELECT_PUB_KEYS, + pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, messenger, extras); if (callback.isSuccess()) { @@ -238,12 +238,12 @@ public class OpenPgpService extends RemoteApiService { return; } - PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor, + PgpMain.encryptAndSign(getContext(), null, inputData, outputStream, asciiArmor, appSettings.getCompression(), keyIds, null, appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(), appSettings.getHashAlgorithm(), true, passphrase); } else { - PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor, + PgpMain.encryptAndSign(getContext(), null, inputData, outputStream, asciiArmor, appSettings.getCompression(), keyIds, null, appSettings.getEncryptionAlgorithm(), Id.key.none, appSettings.getHashAlgorithm(), true, null); @@ -345,7 +345,7 @@ public class OpenPgpService extends RemoteApiService { // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the // app, Fix this? - // long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream); + // long secretKeyId = PgpMain.getDecryptionKeyId(getContext(), inputStream); // if (secretKeyId == Id.key.none) { // throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound)); // } @@ -363,11 +363,10 @@ public class OpenPgpService extends RemoteApiService { long secretKeyId; try { if (inputStream2.markSupported()) { - inputStream2.mark(200); // should probably set this to the max size of two - // pgpF - // objects, if it even needs to be anything other - // than - // 0. + // 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 = PgpMain.getDecryptionKeyId(this, inputStream2); if (secretKeyId == Id.key.none) { @@ -471,7 +470,7 @@ public class OpenPgpService extends RemoteApiService { encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback, settings, false); } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoService", e); + Log.e(Constants.TAG, "OpenPgpService", e); } } }; @@ -493,7 +492,7 @@ public class OpenPgpService extends RemoteApiService { encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback, settings, true); } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoService", e); + Log.e(Constants.TAG, "OpenPgpService", e); } } }; @@ -513,7 +512,7 @@ public class OpenPgpService extends RemoteApiService { try { signSafe(inputBytes, callback, settings); } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoService", e); + Log.e(Constants.TAG, "OpenPgpService", e); } } }; @@ -535,7 +534,7 @@ public class OpenPgpService extends RemoteApiService { try { decryptAndVerifySafe(inputBytes, callback, settings); } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoService", e); + Log.e(Constants.TAG, "OpenPgpService", e); } } }; diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java deleted file mode 100644 index 5b055f6ab..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.service.remote; - -import java.util.ArrayList; - -import org.sufficientlysecure.htmltextview.HtmlTextView; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; -import org.sufficientlysecure.keychain.helper.PgpMain; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -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.view.View; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -public class OpenPgpServiceActivity extends SherlockFragmentActivity { - - public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; - public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX - + "API_ACTIVITY_CACHE_PASSPHRASE"; - public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX - + "API_ACTIVITY_SELECT_PUB_KEYS"; - - public static final String EXTRA_MESSENGER = "messenger"; - - // passphrase action - public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; - // register action - public static final String EXTRA_PACKAGE_NAME = "package_name"; - // select pub keys action - public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; - public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; - public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids"; - - 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); - - handleActions(getIntent(), savedInstanceState); - } - - @Override - protected void onStop() { - super.onStop(); - - if (!finishHandled) { - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.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(); - - 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); - - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // Allow - - // user needs to select a key! - if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { - Toast.makeText(OpenPgpServiceActivity.this, - R.string.api_register_error_select_key, Toast.LENGTH_LONG) - .show(); - } else { - ProviderHelper.insertApiApp(OpenPgpServiceActivity.this, - mSettingsFragment.getAppSettings()); - - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.RegisterActivityCallback.OKAY; - Bundle data = new Bundle(); - data.putString(OpenPgpService.RegisterActivityCallback.PACKAGE_NAME, - packageName); - msg.setData(data); - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); - } - } - }, R.string.api_register_disallow, new View.OnClickListener() { - @Override - public void onClick(View v) { - // Disallow - - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); - } - }); - - setContentView(R.layout.api_app_register_activity); - - mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( - R.id.api_app_settings_fragment); - - AppSettings settings = new AppSettings(packageName); - mSettingsFragment.setAppSettings(settings); - } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { - long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); - - showPassphraseDialog(secretKeyId); - } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { - long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); - ArrayList missingUserIds = intent - .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); - ArrayList dublicateUserIds = intent - .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); - - String text = new String(); - text += "" + getString(R.string.api_select_pub_keys_text) + ""; - text += "

"; - if (missingUserIds != null && missingUserIds.size() > 0) { - text += getString(R.string.api_select_pub_keys_missing_text); - text += "
"; - text += "
    "; - for (String userId : missingUserIds) { - text += "
  • " + userId + "
  • "; - } - text += "
"; - text += "
"; - } - if (dublicateUserIds != null && dublicateUserIds.size() > 0) { - text += getString(R.string.api_select_pub_keys_dublicates_text); - text += "
"; - text += "
    "; - for (String userId : dublicateUserIds) { - text += "
  • " + userId + "
  • "; - } - text += "
"; - } - - // Inflate a "Done"/"Cancel" custom action bar view - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, - 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, - mSelectFragment.getSelectedMasterKeyIds()); - msg.setData(data); - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); - } - }, R.string.btn_doNotSave, 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(); - } - }); - - setContentView(R.layout.api_app_select_pub_keys_activity); - - // set text on view - HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); - textView.setHtmlFromString(text); - - /* Load select pub keys fragment */ - // Check that the activity is using the layout version with - // the fragment_container FrameLayout - if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { - - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. - if (savedInstanceState != null) { - return; - } - - // Create an instance of the fragment - mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); - - // Add the fragment to the 'fragment_container' FrameLayout - getSupportFragmentManager().beginTransaction() - .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); - } - } else { - Log.e(Constants.TAG, "Wrong action!"); - finish(); - } - } - - /** - * Shows passphrase dialog to cache a new passphrase the user enters for using it later for - * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks - * for a symmetric passphrase - */ - private void showPassphraseDialog(long secretKeyId) { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - } else { - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - } - - finishHandled = true; - finish(); - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - try { - PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, - messenger, secretKeyId); - - passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); - } catch (PgpMain.PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); - } - } -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java deleted file mode 100644 index 898aad90e..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.service.remote; - -import java.util.ArrayList; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.TimeUnit; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; - -/** - * Abstract service for remote APIs that handle app registration and user input. - */ -public abstract class RemoteApiService extends Service { - Context mContext; - - final ArrayBlockingQueue mPoolQueue = new ArrayBlockingQueue(100); - // TODO: Are these parameters okay? - PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, - TimeUnit.SECONDS, mPoolQueue); - - private final Object userInputLock = new Object(); - - /** - * Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting - * threads will be notified and the queue resumed - */ - protected class UserInputCallback extends BaseCallback { - - public void handleUserInput(Message msg) { - } - - @Override - public boolean handleMessage(Message msg) { - handleUserInput(msg); - - // resume - synchronized (userInputLock) { - userInputLock.notifyAll(); - } - mThreadPool.resume(); - return true; - } - - } - - /** - * Extends Handler.Callback with OKAY (1), CANCEL (0) variables - */ - private class BaseCallback implements Handler.Callback { - public static final int OKAY = 1; - public static final int CANCEL = 0; - - @Override - public boolean handleMessage(Message msg) { - return false; - } - - } - - /** - * Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for - * execution - * - * @param r - */ - protected void checkAndEnqueue(Runnable r) { - if (isCallerAllowed(false)) { - mThreadPool.execute(r); - - Log.d(Constants.TAG, "Enqueued runnable…"); - } else { - String[] callingPackages = getPackageManager() - .getPackagesForUid(Binder.getCallingUid()); - - Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!"); - Bundle extras = new Bundle(); - // TODO: currently simply uses first entry - extras.putString(OpenPgpServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); - - RegisterActivityCallback callback = new RegisterActivityCallback(); - Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); - - pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_REGISTER, messenger, - extras); - - if (callback.isAllowed()) { - mThreadPool.execute(r); - Log.d(Constants.TAG, "Enqueued runnable…"); - } else { - Log.d(Constants.TAG, "User disallowed app!"); - } - } - } - - /** - * Locks current thread and pauses execution of runnables and starts activity for user input - * - * @param action - * @param messenger - * @param extras - */ - protected void pauseQueueAndStartServiceActivity(String action, Messenger messenger, - Bundle extras) { - synchronized (userInputLock) { - mThreadPool.pause(); - - Log.d(Constants.TAG, "starting activity..."); - Intent intent = new Intent(getBaseContext(), OpenPgpServiceActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setAction(action); - - extras.putParcelable(OpenPgpServiceActivity.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() { - String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); - - // get app settings for this package - for (int i = 0; i < callingPackages.length; i++) { - String currentPkg = callingPackages[i]; - - Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg); - - AppSettings settings = ProviderHelper.getApiAppSettings(this, uri); - - if (settings != null) - return settings; - } - - 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 - if (isPackageAllowed(packageName, false)) { - synchronized (userInputLock) { - userInputLock.notifyAll(); - } - mThreadPool.resume(); - } else { - // Should not happen! - Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); - mThreadPool.shutdownNow(); - } - } 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 - * @return true if process is allowed to use this service - */ - private boolean isCallerAllowed(boolean allowOnlySelf) { - String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); - - // is calling package allowed to use this service? - for (int i = 0; i < callingPackages.length; i++) { - String currentPkg = callingPackages[i]; - - if (isPackageAllowed(currentPkg, allowOnlySelf)) { - return true; - } - } - - Log.d(Constants.TAG, "Caller is NOT allowed!"); - return false; - } - - /** - * Checks if packageName is a registered app for the API. - * - * @param packageName - * @param allowOnlySelf - * allow only Keychain app itself - * @return - */ - private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) { - Log.d(Constants.TAG, "packageName: " + packageName); - - ArrayList allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext); - Log.d(Constants.TAG, "allowed: " + allowedPkgs); - - // check if package is allowed to use our service - if (allowedPkgs.contains(packageName) && (!allowOnlySelf)) { - Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); - - return true; - } else if (Constants.PACKAGE_NAME.equals(packageName)) { - Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!"); - - return true; - } - - return false; - } - - @Override - public void onCreate() { - super.onCreate(); - mContext = this; - } - -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java new file mode 100644 index 000000000..1db9b5f66 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.service.remote; + +import java.util.ArrayList; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; + +/** + * Abstract service for remote APIs that handle app registration and user input. + */ +public abstract class RemoteService extends Service { + Context mContext; + + private final ArrayBlockingQueue mPoolQueue = new ArrayBlockingQueue(100); + // TODO: Are these parameters okay? + private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, + TimeUnit.SECONDS, mPoolQueue); + + private final Object userInputLock = new Object(); + + /** + * Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting + * threads will be notified and the queue resumed + */ + protected class UserInputCallback extends BaseCallback { + + public void handleUserInput(Message msg) { + } + + @Override + public boolean handleMessage(Message msg) { + handleUserInput(msg); + + // resume + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + return true; + } + + } + + /** + * Extends Handler.Callback with OKAY (1), CANCEL (0) variables + */ + private class BaseCallback implements Handler.Callback { + public static final int OKAY = 1; + public static final int CANCEL = 0; + + @Override + public boolean handleMessage(Message msg) { + return false; + } + + } + + public Context getContext() { + return mContext; + } + + /** + * Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for + * execution + * + * @param r + */ + protected void checkAndEnqueue(Runnable r) { + if (isCallerAllowed(false)) { + mThreadPool.execute(r); + + Log.d(Constants.TAG, "Enqueued runnable…"); + } else { + String[] callingPackages = getPackageManager() + .getPackagesForUid(Binder.getCallingUid()); + + Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!"); + Bundle extras = new Bundle(); + // TODO: currently simply uses first entry + extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); + + RegisterActivityCallback callback = new RegisterActivityCallback(); + Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); + + pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_REGISTER, messenger, + extras); + + if (callback.isAllowed()) { + mThreadPool.execute(r); + Log.d(Constants.TAG, "Enqueued runnable…"); + } else { + Log.d(Constants.TAG, "User disallowed app!"); + } + } + } + + /** + * Locks current thread and pauses execution of runnables and starts activity for user input + * + * @param action + * @param messenger + * @param extras + */ + protected void pauseQueueAndStartServiceActivity(String action, Messenger messenger, + 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); + + 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() { + String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + + // get app settings for this package + for (int i = 0; i < callingPackages.length; i++) { + String currentPkg = callingPackages[i]; + + Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg); + + AppSettings settings = ProviderHelper.getApiAppSettings(this, uri); + + if (settings != null) + return settings; + } + + 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 + if (isPackageAllowed(packageName, false)) { + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + } else { + // Should not happen! + Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); + mThreadPool.shutdownNow(); + } + } 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 + * @return true if process is allowed to use this service + */ + private boolean isCallerAllowed(boolean allowOnlySelf) { + String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + + // is calling package allowed to use this service? + for (int i = 0; i < callingPackages.length; i++) { + String currentPkg = callingPackages[i]; + + if (isPackageAllowed(currentPkg, allowOnlySelf)) { + return true; + } + } + + Log.d(Constants.TAG, "Caller is NOT allowed!"); + return false; + } + + /** + * Checks if packageName is a registered app for the API. + * + * @param packageName + * @param allowOnlySelf + * allow only Keychain app itself + * @return + */ + private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) { + Log.d(Constants.TAG, "packageName: " + packageName); + + ArrayList allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext); + Log.d(Constants.TAG, "allowed: " + allowedPkgs); + + // check if package is allowed to use our service + if (allowedPkgs.contains(packageName) && (!allowOnlySelf)) { + Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName); + + return true; + } else if (Constants.PACKAGE_NAME.equals(packageName)) { + Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!"); + + return true; + } + + return false; + } + + @Override + public void onCreate() { + super.onCreate(); + mContext = this; + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java new file mode 100644 index 000000000..c5bb1e4bd --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.service.remote; + +import java.util.ArrayList; + +import org.sufficientlysecure.htmltextview.HtmlTextView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.helper.PgpMain; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +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.view.View; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +public class RemoteServiceActivity extends SherlockFragmentActivity { + + public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; + public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX + + "API_ACTIVITY_CACHE_PASSPHRASE"; + public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX + + "API_ACTIVITY_SELECT_PUB_KEYS"; + + public static final String EXTRA_MESSENGER = "messenger"; + + // passphrase action + public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; + // register action + public static final String EXTRA_PACKAGE_NAME = "package_name"; + // select pub keys action + public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; + public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; + public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids"; + + 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); + + 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(); + + 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); + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Allow + + // 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(); + } 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); + } + + finishHandled = true; + 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(); + } + }); + + setContentView(R.layout.api_app_register_activity); + + mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + AppSettings settings = new AppSettings(packageName); + mSettingsFragment.setAppSettings(settings); + } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { + long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); + + showPassphraseDialog(secretKeyId); + } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { + long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); + ArrayList missingUserIds = intent + .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); + ArrayList dublicateUserIds = intent + .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); + + String text = new String(); + text += "" + getString(R.string.api_select_pub_keys_text) + ""; + text += "

"; + if (missingUserIds != null && missingUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_missing_text); + text += "
"; + text += "
    "; + for (String userId : missingUserIds) { + text += "
  • " + userId + "
  • "; + } + text += "
"; + text += "
"; + } + if (dublicateUserIds != null && dublicateUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_dublicates_text); + text += "
"; + text += "
    "; + for (String userId : dublicateUserIds) { + text += "
  • " + userId + "
  • "; + } + text += "
"; + } + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, + 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, + mSelectFragment.getSelectedMasterKeyIds()); + msg.setData(data); + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + + finishHandled = true; + finish(); + } + }, R.string.btn_doNotSave, 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(); + } + }); + + setContentView(R.layout.api_app_select_pub_keys_activity); + + // set text on view + HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); + textView.setHtmlFromString(text); + + /* Load select pub keys fragment */ + // Check that the activity is using the layout version with + // the fragment_container FrameLayout + if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { + + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragment + mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); + + // Add the fragment to the 'fragment_container' FrameLayout + getSupportFragmentManager().beginTransaction() + .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); + } + } else { + Log.e(Constants.TAG, "Wrong action!"); + finish(); + } + } + + /** + * Shows passphrase dialog to cache a new passphrase the user enters for using it later for + * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks + * for a symmetric passphrase + */ + private void showPassphraseDialog(long secretKeyId) { + // Message is received after passphrase is cached + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + } else { + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + } + + finishHandled = true; + finish(); + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + try { + PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this, + messenger, secretKeyId); + + passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); + } catch (PgpMain.PgpGeneralException e) { + Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); + // send message to handler to start encryption directly + returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); + } + } +} -- cgit v1.2.3