diff --git a/OpenPGP-Keychain/AndroidManifest.xml b/OpenPGP-Keychain/AndroidManifest.xml index 47ff181b3..6febe57f4 100644 --- a/OpenPGP-Keychain/AndroidManifest.xml +++ b/OpenPGP-Keychain/AndroidManifest.xml @@ -358,29 +358,6 @@ android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" android:exported="false" /> - - - - - - - - - - - - - - - - - - - - - - - --> - + + android:process=":remote_api" + android:taskAffinity=":remote_api" /> - + + android:process=":remote_api" > @@ -427,6 +404,22 @@ android:name="api_version" android:value="1" /> + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/libs/scpkix-jdk15on-1.47.0.2.jar b/OpenPGP-Keychain/libs/scpkix-jdk15on-1.47.0.2.jar new file mode 100644 index 000000000..e47eb02fc Binary files /dev/null and b/OpenPGP-Keychain/libs/scpkix-jdk15on-1.47.0.2.jar differ 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/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/handler/IKeychainGetKeyringsHandler.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl similarity index 58% rename from OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl rename to OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl index c3a7d1faf..f69f66fd7 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/handler/IKeychainGetKeyringsHandler.aidl +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/IExtendedApiCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann + * 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. @@ -13,16 +13,12 @@ * 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); +package org.sufficientlysecure.keychain.service.remote; +interface IExtendedApiCallback { + + oneway void onSuccess(in byte[] outputBytes); - oneway void onException(in int exceptionNumber, in String message); + 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/RemoteApiService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java similarity index 92% rename from OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java rename to OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java index 898aad90e..1db9b5f66 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteApiService.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteService.java @@ -40,12 +40,12 @@ import android.os.Messenger; /** * Abstract service for remote APIs that handle app registration and user input. */ -public abstract class RemoteApiService extends Service { +public abstract class RemoteService extends Service { Context mContext; - final ArrayBlockingQueue mPoolQueue = new ArrayBlockingQueue(100); + private final ArrayBlockingQueue mPoolQueue = new ArrayBlockingQueue(100); // TODO: Are these parameters okay? - PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, + private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, mPoolQueue); private final Object userInputLock = new Object(); @@ -87,6 +87,10 @@ public abstract class RemoteApiService extends Service { } + public Context getContext() { + return mContext; + } + /** * Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for * execution @@ -105,12 +109,12 @@ public abstract class RemoteApiService extends Service { 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]); + extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); RegisterActivityCallback callback = new RegisterActivityCallback(); Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); - pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_REGISTER, messenger, + pauseQueueAndStartServiceActivity(RemoteServiceActivity.ACTION_REGISTER, messenger, extras); if (callback.isAllowed()) { @@ -135,11 +139,11 @@ public abstract class RemoteApiService extends Service { mThreadPool.pause(); Log.d(Constants.TAG, "starting activity..."); - Intent intent = new Intent(getBaseContext(), OpenPgpServiceActivity.class); + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(action); - extras.putParcelable(OpenPgpServiceActivity.EXTRA_MESSENGER, messenger); + extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger); intent.putExtras(extras); startActivity(intent); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java similarity index 95% rename from OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java rename to OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java index 5b055f6ab..c5bb1e4bd 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/OpenPgpServiceActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java @@ -41,7 +41,7 @@ import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragmentActivity; -public class OpenPgpServiceActivity extends 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 @@ -84,7 +84,7 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity { if (!finishHandled) { Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL; + msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL; try { mMessenger.send(msg); } catch (RemoteException e) { @@ -120,17 +120,17 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity { // user needs to select a key! if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { - Toast.makeText(OpenPgpServiceActivity.this, + Toast.makeText(RemoteServiceActivity.this, R.string.api_register_error_select_key, Toast.LENGTH_LONG) .show(); } else { - ProviderHelper.insertApiApp(OpenPgpServiceActivity.this, + ProviderHelper.insertApiApp(RemoteServiceActivity.this, mSettingsFragment.getAppSettings()); Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.RegisterActivityCallback.OKAY; + msg.arg1 = RemoteService.RegisterActivityCallback.OKAY; Bundle data = new Bundle(); - data.putString(OpenPgpService.RegisterActivityCallback.PACKAGE_NAME, + data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME, packageName); msg.setData(data); try { @@ -149,7 +149,7 @@ public class OpenPgpServiceActivity extends SherlockFragmentActivity { // Disallow Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL; + msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL; try { mMessenger.send(msg); } catch (RemoteException e) { diff --git a/README.md b/README.md index 61a6cc0cc..4f70b97ad 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ OpenPGP Keychain specific Intent actions: * ``org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE`` * without extras starts Barcode Scanner to get QR Code -## Remote Service API +## OpenPGP Remote API To do asyncronous fast encryption/decryption/sign/verify operations bind to the remote service. The API Demo contains all required AIDL files and a demo activity. @@ -118,6 +118,10 @@ for integration. [4] https://play.google.com/stor/apps/details?id=org.sufficientlysecure.keychain.demo +## Extended Remote API + +TODO + # Libraries All JAR-Libraries are provided in this repository under "libs", all Android Library projects are under "libraries".