diff options
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain')
48 files changed, 3064 insertions, 2945 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java index 22f30cb1f..a1571e491 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java @@ -78,7 +78,7 @@ public final class Id { public static final int filename = 0x00007003; // public static final int output_filename = 0x00007004; public static final int key_server_preference = 0x00007005; - public static final int look_up_key_id = 0x00007006; +// public static final int look_up_key_id = 0x00007006; public static final int export_to_server = 0x00007007; public static final int import_from_qr_code = 0x00007008; public static final int sign_key = 0x00007009; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java new file mode 100644 index 000000000..fb97f3a5c --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -0,0 +1,782 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2010 Thialfihar <thi@thialfihar.org> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.content.Context; +import android.os.Bundle; + +import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SignatureException; +import java.util.Iterator; + +/** + * This class uses a Builder pattern! + */ +public class PgpDecryptVerify { + private Context context; + private InputData data; + private OutputStream outStream; + + private ProgressDialogUpdater progress; + boolean assumeSymmetric; + String passphrase; + + private PgpDecryptVerify(Builder builder) { + // private Constructor can only be called from Builder + this.context = builder.context; + this.data = builder.data; + this.outStream = builder.outStream; + + this.progress = builder.progress; + this.assumeSymmetric = builder.assumeSymmetric; + this.passphrase = builder.passphrase; + } + + public static class Builder { + // mandatory parameter + private Context context; + private InputData data; + private OutputStream outStream; + + // optional + private ProgressDialogUpdater progress = null; + private boolean assumeSymmetric = false; + private String passphrase = ""; + + public Builder(Context context, InputData data, OutputStream outStream) { + this.context = context; + this.data = data; + this.outStream = outStream; + } + + public Builder progress(ProgressDialogUpdater progress) { + this.progress = progress; + return this; + } + + public Builder assumeSymmetric(boolean assumeSymmetric) { + this.assumeSymmetric = assumeSymmetric; + return this; + } + + public Builder passphrase(String passphrase) { + this.passphrase = passphrase; + return this; + } + + public PgpDecryptVerify build() { + return new PgpDecryptVerify(this); + } + } + + public void updateProgress(int message, int current, int total) { + if (progress != null) { + progress.setProgress(message, current, total); + } + } + + public void updateProgress(int current, int total) { + if (progress != null) { + progress.setProgress(current, total); + } + } + + public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) + throws PgpGeneralException, IOException { + InputStream in = PGPUtil.getDecoderStream(inputStream); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); + } + + Iterator<?> it = enc.getEncryptedDataObjects(); + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + return true; + } + } + + return false; + } + + /** + * Decrypts and/or verifies data based on parameters of class + * + * @return + * @throws IOException + * @throws PgpGeneralException + * @throws PGPException + * @throws SignatureException + */ + public Bundle execute() + throws IOException, PgpGeneralException, PGPException, SignatureException { + + // automatically works with ascii armor input and binary + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + if (in instanceof ArmoredInputStream) { + ArmoredInputStream aIn = (ArmoredInputStream) in; + // it is ascii armored + Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine()); + + if (aIn.isClearText()) { + // a cleartext signature, verify it with the other method + return verifyCleartextSignature(aIn); + } + // else: ascii armored encryption! go on... + } + + return decryptVerify(in); + } + + /** + * Decrypt and/or verifies binary or ascii armored pgp + * + * @param in + * @return + * @throws IOException + * @throws PgpGeneralException + * @throws PGPException + * @throws SignatureException + */ + private Bundle decryptVerify(InputStream in) + throws IOException, PgpGeneralException, PGPException, SignatureException { + Bundle returnData = new Bundle(); + + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + int currentProgress = 0; + updateProgress(R.string.progress_reading_data, currentProgress, 100); + + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); + } + + InputStream clear; + PGPEncryptedData encryptedData; + + currentProgress += 5; + + // TODO: currently we always only look at the first known key or symmetric encryption, + // there might be more... + if (assumeSymmetric) { + PGPPBEEncryptedData pbe = null; + Iterator<?> it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + pbe = (PGPPBEEncryptedData) obj; + break; + } + } + + if (pbe == null) { + throw new PgpGeneralException( + context.getString(R.string.error_no_symmetric_encryption_packet)); + } + + updateProgress(R.string.progress_preparing_streams, currentProgress, 100); + + PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); + PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( + digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + passphrase.toCharArray()); + + clear = pbe.getDataStream(decryptorFactory); + + encryptedData = pbe; + currentProgress += 5; + } else { + updateProgress(R.string.progress_finding_key, currentProgress, 100); + + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKey secretKey = null; + Iterator<?> it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; + secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID()); + if (secretKey != null) { + pbe = encData; + break; + } + } + } + + if (secretKey == null) { + throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found)); + } + + currentProgress += 5; + updateProgress(R.string.progress_extracting_key, currentProgress, 100); + PGPPrivateKey privateKey = null; + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + passphrase.toCharArray()); + privateKey = secretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + throw new PGPException(context.getString(R.string.error_wrong_passphrase)); + } + if (privateKey == null) { + throw new PgpGeneralException( + context.getString(R.string.error_could_not_extract_private_key)); + } + currentProgress += 5; + updateProgress(R.string.progress_preparing_streams, currentProgress, 100); + + PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); + + clear = pbe.getDataStream(decryptorFactory); + + encryptedData = pbe; + currentProgress += 5; + } + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + Object dataChunk = plainFact.nextObject(); + PGPOnePassSignature signature = null; + PGPPublicKey signatureKey = null; + int signatureIndex = -1; + + if (dataChunk instanceof PGPCompressedData) { + updateProgress(R.string.progress_decompressing_data, currentProgress, 100); + + PGPObjectFactory fact = new PGPObjectFactory( + ((PGPCompressedData) dataChunk).getDataStream()); + dataChunk = fact.nextObject(); + plainFact = fact; + currentProgress += 10; + } + + long signatureKeyId = 0; + if (dataChunk instanceof PGPOnePassSignatureList) { + updateProgress(R.string.progress_processing_signature, currentProgress, 100); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = ProviderHelper + .getPGPPublicKeyByKeyId(context, signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureIndex = i; + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( + context, signatureKeyId); + if (signKeyRing != null) { + userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + } + returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature != null) { + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signature.init(contentVerifierBuilderProvider, signatureKey); + } else { + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + } + + dataChunk = plainFact.nextObject(); + currentProgress += 10; + } + + if (dataChunk instanceof PGPSignatureList) { + dataChunk = plainFact.nextObject(); + } + + if (dataChunk instanceof PGPLiteralData) { + updateProgress(R.string.progress_decrypting, currentProgress, 100); + + PGPLiteralData literalData = (PGPLiteralData) dataChunk; + + byte[] buffer = new byte[1 << 16]; + InputStream dataIn = literalData.getInputStream(); + + int startProgress = currentProgress; + int endProgress = 100; + if (signature != null) { + endProgress = 90; + } else if (encryptedData.isIntegrityProtected()) { + endProgress = 95; + } + + int n; + // TODO: progress calculation is broken here! Try to rework it based on commented code! +// int progress = 0; + long startPos = data.getStreamPosition(); + while ((n = dataIn.read(buffer)) > 0) { + outStream.write(buffer, 0, n); +// progress += n; + if (signature != null) { + try { + signature.update(buffer, 0, n); + } catch (SignatureException e) { + returnData + .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); + signature = null; + } + } + // TODO: dead code?! + // unknown size, but try to at least have a moving, slowing down progress bar +// currentProgress = startProgress + (endProgress - startProgress) * progress +// / (progress + 100000); + if (data.getSize() - startPos == 0) { + currentProgress = endProgress; + } else { + currentProgress = (int) (startProgress + (endProgress - startProgress) + * (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); + } + updateProgress(currentProgress, 100); + } + + if (signature != null) { + updateProgress(R.string.progress_verifying_signature, 90, 100); + + PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); + PGPSignature messageSignature = signatureList.get(signatureIndex); + + // these are not cleartext signatures! + returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false); + + //Now check binding signatures + boolean keyBinding_isok = verifyKeyBinding(context, messageSignature, signatureKey); + boolean sig_isok = signature.verify(messageSignature); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok); + } + } + + // TODO: test if this integrity really check works! + if (encryptedData.isIntegrityProtected()) { + updateProgress(R.string.progress_verifying_integrity, 95, 100); + + if (encryptedData.verify()) { + // passed + Log.d(Constants.TAG, "Integrity verification: success!"); + } else { + // failed + Log.d(Constants.TAG, "Integrity verification: failed!"); + throw new PgpGeneralException(context.getString(R.string.error_integrity_check_failed)); + } + } else { + // no integrity check + Log.e(Constants.TAG, "Encrypted data was not integrity protected!"); + } + + updateProgress(R.string.progress_done, 100, 100); + return returnData; + } + + /** + * This method verifies cleartext signatures + * as defined in http://tools.ietf.org/html/rfc4880#section-7 + * <p/> + * The method is heavily based on + * pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java + * + * @return + * @throws IOException + * @throws PgpGeneralException + * @throws PGPException + * @throws SignatureException + */ + private Bundle verifyCleartextSignature(ArmoredInputStream aIn) + throws IOException, PgpGeneralException, PGPException, SignatureException { + Bundle returnData = new Bundle(); + // cleartext signatures are never encrypted ;) + returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, true); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + updateProgress(R.string.progress_done, 0, 100); + + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, aIn); + byte[] lineSep = getLineSeparator(); + + byte[] line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + + while (lookAhead != -1 && aIn.isClearText()) { + lookAhead = readInputLine(lineOut, lookAhead, aIn); + line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + } + + out.close(); + + byte[] clearText = out.toByteArray(); + outStream.write(clearText); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); + + updateProgress(R.string.progress_processing_signature, 60, 100); + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + + PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); + if (sigList == null) { + throw new PgpGeneralException(context.getString(R.string.error_corrupt_data)); + } + PGPSignature signature = null; + long signatureKeyId = 0; + PGPPublicKey signatureKey = null; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + + if (signatureKey == null) { + signature = null; + } else { + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, + signatureKeyId); + if (signKeyRing != null) { + userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); + } + returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature == null) { + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); + updateProgress(R.string.progress_done, 100, 100); + return returnData; + } + + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = + new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + signature.init(contentVerifierBuilderProvider, signatureKey); + + InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); + + lookAhead = readInputLine(lineOut, sigIn); + + processLine(signature, lineOut.toByteArray()); + + if (lookAhead != -1) { + do { + lookAhead = readInputLine(lineOut, lookAhead, sigIn); + + signature.update((byte) '\r'); + signature.update((byte) '\n'); + + processLine(signature, lineOut.toByteArray()); + } while (lookAhead != -1); + } + + boolean sig_isok = signature.verify(); + + //Now check binding signatures + boolean keyBinding_isok = verifyKeyBinding(context, signature, signatureKey); + + returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok); + + updateProgress(R.string.progress_done, 100, 100); + return returnData; + } + + private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) { + long signatureKeyId = signature.getKeyID(); + boolean keyBinding_isok = false; + String userId = null; + PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, + signatureKeyId); + PGPPublicKey mKey = null; + if (signKeyRing != null) { + mKey = PgpKeyHelper.getMasterKey(signKeyRing); + } + if (signature.getKeyID() != mKey.getKeyID()) { + keyBinding_isok = verifyKeyBinding(mKey, signatureKey); + } else { //if the key used to make the signature was the master key, no need to check binding sigs + keyBinding_isok = true; + } + return keyBinding_isok; + } + + private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + boolean subkeyBinding_isok = false; + boolean tmp_subkeyBinding_isok = false; + boolean primkeyBinding_isok = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + Iterator<PGPSignature> itr = signingPublicKey.getSignatures(); + + subkeyBinding_isok = false; + tmp_subkeyBinding_isok = false; + primkeyBinding_isok = false; + while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? + //gpg has an invalid subkey binding error on key import I think, but doesn't shout + //about keys without subkey signing. Can't get it to import a slightly broken one + //either, so we will err on bad subkey binding here. + PGPSignature sig = itr.next(); + if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { + //check and if ok, check primary key binding. + try { + sig.init(contentVerifierBuilderProvider, masterPublicKey); + tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey); + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + + if (tmp_subkeyBinding_isok) + subkeyBinding_isok = true; + if (tmp_subkeyBinding_isok) { + primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + } + } + } + return (subkeyBinding_isok & primkeyBinding_isok); + } + + private static boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { + boolean primkeyBinding_isok = false; + JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureList eSigList; + + if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { + try { + eSigList = Pkts.getEmbeddedSignatures(); + } catch (IOException e) { + return false; + } catch (PGPException e) { + return false; + } + for (int j = 0; j < eSigList.size(); ++j) { + PGPSignature emSig = eSigList.get(j); + if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { + try { + emSig.init(contentVerifierBuilderProvider, signingPublicKey); + primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey); + if (primkeyBinding_isok) + break; + } catch (PGPException e) { + continue; + } catch (SignatureException e) { + continue; + } + } + } + } + return primkeyBinding_isok; + } + + /** + * Mostly taken from ClearSignedFileProcessor in Bouncy Castle + * + * @param sig + * @param line + * @throws SignatureException + * @throws IOException + */ + private static void processLine(PGPSignature sig, byte[] line) + throws SignatureException, IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sig.update(line, 0, length); + } + } + + private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) + throws IOException { + bOut.reset(); + + int lookAhead = -1; + int ch; + + while ((ch = fIn.read()) >= 0) { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + + return lookAhead; + } + + private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) + throws IOException { + bOut.reset(); + + int ch = lookAhead; + + do { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } while ((ch = fIn.read()) >= 0); + + if (ch < 0) { + lookAhead = -1; + } + + return lookAhead; + } + + private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + throws IOException { + int lookAhead = fIn.read(); + + if (lastCh == '\r' && lookAhead == '\n') { + bOut.write(lookAhead); + lookAhead = fIn.read(); + } + + return lookAhead; + } + + private static int getLengthWithoutSeparator(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isLineEnding(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isLineEnding(byte b) { + return b == '\r' || b == '\n'; + } + + private static int getLengthWithoutWhiteSpace(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isWhiteSpace(byte b) { + return b == '\r' || b == '\n' || b == '\t' || b == ' '; + } + + private static byte[] getLineSeparator() { + String nl = System.getProperty("line.separator"); + byte[] nlBytes = new byte[nl.length()]; + + for (int i = 0; i != nlBytes.length; i++) { + nlBytes[i] = (byte) nl.charAt(i); + } + + return nlBytes; + } +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 78d42cbf9..c1c6f3088 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -42,6 +42,8 @@ import android.content.Context; public class PgpKeyHelper { + private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); + public static Date getCreationDate(PGPPublicKey key) { return key.getCreationTime(); } @@ -591,8 +593,7 @@ public class PgpKeyHelper { * "Max Mustermann (this is a comment)" * "Max Mustermann [this is nothing]" */ - Pattern withComment = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); - Matcher matcher = withComment.matcher(userId); + Matcher matcher = USER_ID_PATTERN.matcher(userId); if (matcher.matches()) { result[0] = matcher.group(1); result[1] = matcher.group(3); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 6055aebfc..80831d35f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -504,7 +504,7 @@ public class PgpKeyOperation { updateProgress(R.string.progress_done, 100, 100); } - public PGPPublicKeyRing signKey(long masterKeyId, long pubKeyId, String passphrase) + public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase) throws PgpGeneralException, PGPException, SignatureException { if (passphrase == null) { throw new PgpGeneralException("Unable to obtain passphrase"); @@ -512,14 +512,14 @@ public class PgpKeyOperation { PGPPublicKeyRing pubring = ProviderHelper .getPGPPublicKeyRingByKeyId(mContext, pubKeyId); - PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); - if (signingKey == null) { + PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); + if (certificationKey == null) { throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); } PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); + PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); if (signaturePrivateKey == null) { throw new PgpGeneralException( mContext.getString(R.string.error_could_not_extract_private_key)); @@ -527,7 +527,7 @@ public class PgpKeyOperation { // TODO: SHA256 fixed? JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( - signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) + certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java deleted file mode 100644 index 1402be435..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpOperation.java +++ /dev/null @@ -1,1154 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2010 Thialfihar <thi@thialfihar.org> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.pgp; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.util.Date; -import java.util.Iterator; - -import org.spongycastle.bcpg.ArmoredInputStream; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.BCPGInputStream; -import org.spongycastle.bcpg.BCPGOutputStream; - -import org.spongycastle.bcpg.SignaturePacket; - -import org.spongycastle.bcpg.SignatureSubpacket; -import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.openpgp.PGPCompressedData; -import org.spongycastle.openpgp.PGPCompressedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedData; -import org.spongycastle.openpgp.PGPEncryptedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedDataList; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPLiteralDataGenerator; -import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPOnePassSignature; -import org.spongycastle.openpgp.PGPOnePassSignatureList; -import org.spongycastle.openpgp.PGPPBEEncryptedData; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.PGPSignatureList; -import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.PGPUtil; -import org.spongycastle.openpgp.PGPV3SignatureGenerator; -import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; -import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; -import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; - -import android.content.Context; -import android.os.Bundle; - -public class PgpOperation { - private Context mContext; - private ProgressDialogUpdater mProgress; - private InputData mData; - private OutputStream mOutStream; - - public PgpOperation(Context context, ProgressDialogUpdater progress, InputData data, - OutputStream outStream) { - super(); - this.mContext = context; - this.mProgress = progress; - this.mData = data; - this.mOutStream = outStream; - } - - public void updateProgress(int message, int current, int total) { - if (mProgress != null) { - mProgress.setProgress(message, current, total); - } - } - - public void updateProgress(int current, int total) { - if (mProgress != null) { - mProgress.setProgress(current, total); - } - } - - public void signAndEncrypt(boolean useAsciiArmor, int compression, long[] encryptionKeyIds, - String encryptionPassphrase, int symmetricEncryptionAlgorithm, long signatureKeyId, - int signatureHashAlgorithm, boolean signatureForceV3, String signaturePassphrase) - throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, - NoSuchAlgorithmException, SignatureException { - - if (encryptionKeyIds == null) { - encryptionKeyIds = new long[0]; - } - - ArmoredOutputStream armorOut = null; - OutputStream out = null; - OutputStream encryptOut = null; - if (useAsciiArmor) { - armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext)); - out = armorOut; - } else { - out = mOutStream; - } - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (encryptionKeyIds.length == 0 && encryptionPassphrase == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_encryption_keys_or_passphrase)); - } - - if (signatureKeyId != Id.key.none) { - signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId); - signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId); - if (signingKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - if (signaturePassphrase == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_signature_passphrase)); - } - - updateProgress(R.string.progress_extracting_signature_key, 0, 100); - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); - signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - } - updateProgress(R.string.progress_preparing_streams, 5, 100); - - // encrypt and compress input file content - JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder( - symmetricEncryptionAlgorithm).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) - .setWithIntegrityPacket(true); - - PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(encryptorBuilder); - - if (encryptionKeyIds.length == 0) { - // Symmetric encryption - Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); - - JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = new JcePBEKeyEncryptionMethodGenerator( - encryptionPassphrase.toCharArray()); - cPk.addMethod(symmetricEncryptionGenerator); - } else { - // Asymmetric encryption - for (long id : encryptionKeyIds) { - PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id); - if (key != null) { - - JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = new JcePublicKeyKeyEncryptionMethodGenerator( - key); - cPk.addMethod(pubKeyEncryptionGenerator); - } - } - } - encryptOut = cPk.open(out, new byte[1 << 16]); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - if (signatureKeyId != Id.key.none) { - updateProgress(R.string.progress_preparing_signature, 10, 100); - - // content signer based on signing key algorithm and choosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( - signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - if (signatureForceV3) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper - .getMasterKey(signingKeyRing)); - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - } - - PGPCompressedDataGenerator compressGen = null; - BCPGOutputStream bcpgOut = null; - if (compression == Id.choice.compression.none) { - bcpgOut = new BCPGOutputStream(encryptOut); - } else { - compressGen = new PGPCompressedDataGenerator(compression); - bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); - } - if (signatureKeyId != Id.key.none) { - if (signatureForceV3) { - signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); - } else { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } - } - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), - new byte[1 << 16]); - updateProgress(R.string.progress_encrypting, 20, 100); - - long done = 0; - int n = 0; - byte[] buffer = new byte[1 << 16]; - InputStream in = mData.getInputStream(); - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); - if (signatureKeyId != Id.key.none) { - if (signatureForceV3) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - done += n; - if (mData.getSize() != 0) { - updateProgress((int) (20 + (95 - 20) * done / mData.getSize()), 100); - } - } - - literalGen.close(); - - if (signatureKeyId != Id.key.none) { - updateProgress(R.string.progress_generating_signature, 95, 100); - if (signatureForceV3) { - signatureV3Generator.generate().encode(pOut); - } else { - signatureGenerator.generate().encode(pOut); - } - } - if (compressGen != null) { - compressGen.close(); - } - encryptOut.close(); - if (useAsciiArmor) { - armorOut.close(); - } - - updateProgress(R.string.progress_done, 100, 100); - } - - public void signText(long signatureKeyId, String signaturePassphrase, - int signatureHashAlgorithm, boolean forceV3Signature) throws PgpGeneralException, - PGPException, IOException, NoSuchAlgorithmException, SignatureException { - - ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext)); - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (signatureKeyId == 0) { - armorOut.close(); - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key)); - } - - signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId); - signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId); - if (signingKey == null) { - armorOut.close(); - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - if (signaturePassphrase == null) { - armorOut.close(); - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase)); - } - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); - signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - armorOut.close(); - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - updateProgress(R.string.progress_preparing_streams, 0, 100); - - updateProgress(R.string.progress_preparing_signature, 30, 100); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - // content signer based on signing key algorithm and choosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey - .getPublicKey().getAlgorithm(), signatureHashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - updateProgress(R.string.progress_signing, 40, 100); - - armorOut.beginClearText(signatureHashAlgorithm); - - InputStream inStream = mData.getInputStream(); - final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); - - final byte[] newline = "\r\n".getBytes("UTF-8"); - - if (forceV3Signature) { - processLine(reader.readLine(), armorOut, signatureV3Generator); - } else { - processLine(reader.readLine(), armorOut, signatureGenerator); - } - - while (true) { - final String line = reader.readLine(); - - if (line == null) { - armorOut.write(newline); - break; - } - - armorOut.write(newline); - if (forceV3Signature) { - signatureV3Generator.update(newline); - processLine(line, armorOut, signatureV3Generator); - } else { - signatureGenerator.update(newline); - processLine(line, armorOut, signatureGenerator); - } - } - - armorOut.endClearText(); - - BCPGOutputStream bOut = new BCPGOutputStream(armorOut); - if (forceV3Signature) { - signatureV3Generator.generate().encode(bOut); - } else { - signatureGenerator.generate().encode(bOut); - } - armorOut.close(); - - updateProgress(R.string.progress_done, 100, 100); - } - - public void generateSignature(boolean armored, boolean binary, long signatureKeyId, - String signaturePassPhrase, int hashAlgorithm, boolean forceV3Signature) - throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, - SignatureException { - - OutputStream out = null; - - // Ascii Armor (Base64) - ArmoredOutputStream armorOut = null; - if (armored) { - armorOut = new ArmoredOutputStream(mOutStream); - armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext)); - out = armorOut; - } else { - out = mOutStream; - } - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (signatureKeyId == 0) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key)); - } - - signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, signatureKeyId); - signingKey = PgpKeyHelper.getSigningKey(mContext, signatureKeyId); - if (signingKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); - } - - if (signaturePassPhrase == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase)); - } - - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassPhrase.toCharArray()); - signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); - if (signaturePrivateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - updateProgress(R.string.progress_preparing_streams, 0, 100); - - updateProgress(R.string.progress_preparing_signature, 30, 100); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; - if (binary) { - type = PGPSignature.BINARY_DOCUMENT; - } - - // content signer based on signing key algorithm and choosen hash algorithm - JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey - .getPublicKey().getAlgorithm(), hashAlgorithm) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); - signatureV3Generator.init(type, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); - signatureGenerator.init(type, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - updateProgress(R.string.progress_signing, 40, 100); - - InputStream inStream = mData.getInputStream(); - if (binary) { - byte[] buffer = new byte[1 << 16]; - int n = 0; - while ((n = inStream.read(buffer)) > 0) { - if (forceV3Signature) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - } else { - final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); - final byte[] newline = "\r\n".getBytes("UTF-8"); - - while (true) { - final String line = reader.readLine(); - - if (line == null) { - break; - } - - if (forceV3Signature) { - processLine(line, null, signatureV3Generator); - signatureV3Generator.update(newline); - } else { - processLine(line, null, signatureGenerator); - signatureGenerator.update(newline); - } - } - } - - BCPGOutputStream bOut = new BCPGOutputStream(out); - if (forceV3Signature) { - signatureV3Generator.generate().encode(bOut); - } else { - signatureGenerator.generate().encode(bOut); - } - out.close(); - mOutStream.close(); - - if (mProgress != null) - mProgress.setProgress(R.string.progress_done, 100, 100); - } - - public static boolean hasSymmetricEncryption(Context context, InputStream inputStream) - throws PgpGeneralException, IOException { - InputStream in = PGPUtil.getDecoderStream(inputStream); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); - } - - Iterator<?> it = enc.getEncryptedDataObjects(); - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - return true; - } - } - - return false; - } - - public Bundle decryptAndVerify(String passphrase, boolean assumeSymmetric) throws IOException, - PgpGeneralException, PGPException, SignatureException { - if (passphrase == null) { - passphrase = ""; - } - - Bundle returnData = new Bundle(); - InputStream in = PGPUtil.getDecoderStream(mData.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - long signatureKeyId = 0; - - int currentProgress = 0; - updateProgress(R.string.progress_reading_data, currentProgress, 100); - - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_invalid_data)); - } - - InputStream clear = null; - PGPEncryptedData encryptedData = null; - - currentProgress += 5; - - // TODO: currently we always only look at the first known key or symmetric encryption, - // there might be more... - if (assumeSymmetric) { - PGPPBEEncryptedData pbe = null; - Iterator<?> it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - pbe = (PGPPBEEncryptedData) obj; - break; - } - } - - if (pbe == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_no_symmetric_encryption_packet)); - } - - updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - - PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); - PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( - digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.toCharArray()); - - clear = pbe.getDataStream(decryptorFactory); - - encryptedData = pbe; - currentProgress += 5; - } else { - updateProgress(R.string.progress_finding_key, currentProgress, 100); - - PGPPublicKeyEncryptedData pbe = null; - PGPSecretKey secretKey = null; - Iterator<?> it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID()); - if (secretKey != null) { - pbe = encData; - break; - } - } - } - - if (secretKey == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found)); - } - - currentProgress += 5; - updateProgress(R.string.progress_extracting_key, currentProgress, 100); - PGPPrivateKey privateKey = null; - try { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - passphrase.toCharArray()); - privateKey = secretKey.extractPrivateKey(keyDecryptor); - } catch (PGPException e) { - throw new PGPException(mContext.getString(R.string.error_wrong_passphrase)); - } - if (privateKey == null) { - throw new PgpGeneralException( - mContext.getString(R.string.error_could_not_extract_private_key)); - } - currentProgress += 5; - updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - - PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey); - - clear = pbe.getDataStream(decryptorFactory); - - encryptedData = pbe; - currentProgress += 5; - } - - PGPObjectFactory plainFact = new PGPObjectFactory(clear); - Object dataChunk = plainFact.nextObject(); - PGPOnePassSignature signature = null; - PGPPublicKey signatureKey = null; - int signatureIndex = -1; - - if (dataChunk instanceof PGPCompressedData) { - updateProgress(R.string.progress_decompressing_data, currentProgress, 100); - - PGPObjectFactory fact = new PGPObjectFactory( - ((PGPCompressedData) dataChunk).getDataStream()); - dataChunk = fact.nextObject(); - plainFact = fact; - currentProgress += 10; - } - - if (dataChunk instanceof PGPOnePassSignatureList) { - updateProgress(R.string.progress_processing_signature, currentProgress, 100); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); - PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = ProviderHelper - .getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - signature = null; - } else { - signatureIndex = i; - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( - mContext, signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature != null) { - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - signature.init(contentVerifierBuilderProvider, signatureKey); - } else { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); - } - - dataChunk = plainFact.nextObject(); - currentProgress += 10; - } - - if (dataChunk instanceof PGPSignatureList) { - dataChunk = plainFact.nextObject(); - } - - if (dataChunk instanceof PGPLiteralData) { - updateProgress(R.string.progress_decrypting, currentProgress, 100); - - PGPLiteralData literalData = (PGPLiteralData) dataChunk; - OutputStream out = mOutStream; - - byte[] buffer = new byte[1 << 16]; - InputStream dataIn = literalData.getInputStream(); - - int startProgress = currentProgress; - int endProgress = 100; - if (signature != null) { - endProgress = 90; - } else if (encryptedData.isIntegrityProtected()) { - endProgress = 95; - } - int n = 0; - int done = 0; - long startPos = mData.getStreamPosition(); - while ((n = dataIn.read(buffer)) > 0) { - out.write(buffer, 0, n); - done += n; - if (signature != null) { - try { - signature.update(buffer, 0, n); - } catch (SignatureException e) { - returnData - .putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); - signature = null; - } - } - // unknown size, but try to at least have a moving, slowing down progress bar - currentProgress = startProgress + (endProgress - startProgress) * done - / (done + 100000); - if (mData.getSize() - startPos == 0) { - currentProgress = endProgress; - } else { - currentProgress = (int) (startProgress + (endProgress - startProgress) - * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos)); - } - updateProgress(currentProgress, 100); - } - - if (signature != null) { - updateProgress(R.string.progress_verifying_signature, 90, 100); - - PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); - PGPSignature messageSignature = signatureList.get(signatureIndex); - - //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(mContext, messageSignature, signatureKey); - boolean sig_isok = signature.verify(messageSignature); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok); - } - } - - // TODO: add integrity somewhere - if (encryptedData.isIntegrityProtected()) { - updateProgress(R.string.progress_verifying_integrity, 95, 100); - - if (encryptedData.verify()) { - // passed - } else { - // failed - } - } else { - // no integrity check - } - - updateProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public Bundle verifyText(boolean lookupUnknownKey) throws IOException, PgpGeneralException, - PGPException, SignatureException { - Bundle returnData = new Bundle(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArmoredInputStream aIn = new ArmoredInputStream(mData.getInputStream()); - - updateProgress(R.string.progress_done, 0, 100); - - // mostly taken from ClearSignedFileProcessor - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, aIn); - byte[] lineSep = getLineSeparator(); - - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - - while (lookAhead != -1 && aIn.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, aIn); - line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - } - - out.close(); - - byte[] clearText = out.toByteArray(); - mOutStream.write(clearText); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true); - - updateProgress(R.string.progress_processing_signature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); - - PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); - if (sigList == null) { - throw new PgpGeneralException(mContext.getString(R.string.error_corrupt_data)); - } - PGPSignature signature = null; - long signatureKeyId = 0; - PGPPublicKey signatureKey = null; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - // if key is not known and we want to lookup unknown ones... - if (signatureKey == null && lookupUnknownKey) { - - returnData = new Bundle(); - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY, true); - - // return directly now, decrypt will be done again after importing unknown key - return returnData; - } - - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, - signatureKeyId); - if (signKeyRing != null) { - userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); - } - returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature == null) { - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true); - if (mProgress != null) - mProgress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - signature.init(contentVerifierBuilderProvider, signatureKey); - - InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); - - lookAhead = readInputLine(lineOut, sigIn); - - processLine(signature, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, sigIn); - - signature.update((byte) '\r'); - signature.update((byte) '\n'); - - processLine(signature, lineOut.toByteArray()); - } while (lookAhead != -1); - } - - boolean sig_isok = signature.verify(); - - //Now check binding signatures - boolean keyBinding_isok = verifyKeyBinding(mContext, signature, signatureKey); - - returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok); - - updateProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public boolean verifyKeyBinding(Context mContext, PGPSignature signature, PGPPublicKey signatureKey) - { - long signatureKeyId = signature.getKeyID(); - boolean keyBinding_isok = false; - String userId = null; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext, - signatureKeyId); - PGPPublicKey mKey = null; - if (signKeyRing != null) { - mKey = PgpKeyHelper.getMasterKey(signKeyRing); - } - if (signature.getKeyID() != mKey.getKeyID()) { - keyBinding_isok = verifyKeyBinding(mKey, signatureKey); - } else { //if the key used to make the signature was the master key, no need to check binding sigs - keyBinding_isok = true; - } - return keyBinding_isok; - } - - public boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) - { - boolean subkeyBinding_isok = false; - boolean tmp_subkeyBinding_isok = false; - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - Iterator<PGPSignature> itr = signingPublicKey.getSignatures(); - - subkeyBinding_isok = false; - tmp_subkeyBinding_isok = false; - primkeyBinding_isok = false; - while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? - //gpg has an invalid subkey binding error on key import I think, but doesn't shout - //about keys without subkey signing. Can't get it to import a slightly broken one - //either, so we will err on bad subkey binding here. - PGPSignature sig = itr.next(); - if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { - //check and if ok, check primary key binding. - try { - sig.init(contentVerifierBuilderProvider, masterPublicKey); - tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey); - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - - if (tmp_subkeyBinding_isok) - subkeyBinding_isok = true; - if (tmp_subkeyBinding_isok) { - primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - } - } - } - return (subkeyBinding_isok & primkeyBinding_isok); - } - - private boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) - { - boolean primkeyBinding_isok = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureList eSigList; - - if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { - try { - eSigList = Pkts.getEmbeddedSignatures(); - } catch (IOException e) { - return false; - } catch (PGPException e) { - return false; - } - for (int j = 0; j < eSigList.size(); ++j) { - PGPSignature emSig = eSigList.get(j); - if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { - try { - emSig.init(contentVerifierBuilderProvider, signingPublicKey); - primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey); - if (primkeyBinding_isok) - break; - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - } - } - } - return primkeyBinding_isok; - } - - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPV3SignatureGenerator pSignatureGenerator) throws IOException, - SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - - // taken from ClearSignedFileProcessor in BC - private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, - IOException { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sig.update(line, 0, length); - } - } - - private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - private static int getLengthWithoutSeparator(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isLineEnding(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static int getLengthWithoutWhiteSpace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isWhiteSpace(byte b) { - return b == '\r' || b == '\n' || b == '\t' || b == ' '; - } - - private static byte[] getLineSeparator() { - String nl = System.getProperty("line.separator"); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; - } -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java new file mode 100644 index 000000000..ba1182c1b --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2010 Thialfihar <thi@thialfihar.org> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.pgp; + +import android.content.Context; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPV3SignatureGenerator; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Date; + +/** + * This class uses a Builder pattern! + */ +public class PgpSignEncrypt { + private Context context; + private InputData data; + private OutputStream outStream; + + private ProgressDialogUpdater progress; + private boolean enableAsciiArmorOutput; + private int compressionId; + private long[] encryptionKeyIds; + private String encryptionPassphrase; + private int symmetricEncryptionAlgorithm; + private long signatureKeyId; + private int signatureHashAlgorithm; + private boolean signatureForceV3; + private String signaturePassphrase; + + private PgpSignEncrypt(Builder builder) { + // private Constructor can only be called from Builder + this.context = builder.context; + this.data = builder.data; + this.outStream = builder.outStream; + + this.progress = builder.progress; + this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput; + this.compressionId = builder.compressionId; + this.encryptionKeyIds = builder.encryptionKeyIds; + this.encryptionPassphrase = builder.encryptionPassphrase; + this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm; + this.signatureKeyId = builder.signatureKeyId; + this.signatureHashAlgorithm = builder.signatureHashAlgorithm; + this.signatureForceV3 = builder.signatureForceV3; + this.signaturePassphrase = builder.signaturePassphrase; + } + + public static class Builder { + // mandatory parameter + private Context context; + private InputData data; + private OutputStream outStream; + + // optional + private ProgressDialogUpdater progress = null; + private boolean enableAsciiArmorOutput = false; + private int compressionId = Id.choice.compression.none; + private long[] encryptionKeyIds = new long[0]; + private String encryptionPassphrase = null; + private int symmetricEncryptionAlgorithm = 0; + private long signatureKeyId = Id.key.none; + private int signatureHashAlgorithm = 0; + private boolean signatureForceV3 = false; + private String signaturePassphrase = null; + + public Builder(Context context, InputData data, OutputStream outStream) { + this.context = context; + this.data = data; + this.outStream = outStream; + } + + public Builder progress(ProgressDialogUpdater progress) { + this.progress = progress; + return this; + } + + public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) { + this.enableAsciiArmorOutput = enableAsciiArmorOutput; + return this; + } + + public Builder compressionId(int compressionId) { + this.compressionId = compressionId; + return this; + } + + public Builder encryptionKeyIds(long[] encryptionKeyIds) { + this.encryptionKeyIds = encryptionKeyIds; + return this; + } + + public Builder encryptionPassphrase(String encryptionPassphrase) { + this.encryptionPassphrase = encryptionPassphrase; + return this; + } + + public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { + this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; + return this; + } + + public Builder signatureKeyId(long signatureKeyId) { + this.signatureKeyId = signatureKeyId; + return this; + } + + public Builder signatureHashAlgorithm(int signatureHashAlgorithm) { + this.signatureHashAlgorithm = signatureHashAlgorithm; + return this; + } + + public Builder signatureForceV3(boolean signatureForceV3) { + this.signatureForceV3 = signatureForceV3; + return this; + } + + public Builder signaturePassphrase(String signaturePassphrase) { + this.signaturePassphrase = signaturePassphrase; + return this; + } + + public PgpSignEncrypt build() { + return new PgpSignEncrypt(this); + } + } + + public void updateProgress(int message, int current, int total) { + if (progress != null) { + progress.setProgress(message, current, total); + } + } + + public void updateProgress(int current, int total) { + if (progress != null) { + progress.setProgress(current, total); + } + } + + /** + * Signs and/or encrypts data based on parameters of class + * + * @throws IOException + * @throws PgpGeneralException + * @throws PGPException + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws SignatureException + */ + public void execute() + throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, + NoSuchAlgorithmException, SignatureException { + + boolean enableSignature = signatureKeyId != Id.key.none; + boolean enableEncryption = (encryptionKeyIds.length != 0 || encryptionPassphrase != null); + boolean enableCompression = (enableEncryption && compressionId != Id.choice.compression.none); + + Log.d(Constants.TAG, "enableSignature:" + enableSignature + + "\nenableEncryption:" + enableEncryption + + "\nenableCompression:" + enableCompression + + "\nenableAsciiArmorOutput:" + enableAsciiArmorOutput); + + int signatureType; + if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { + // for sign-only ascii text + signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; + } else { + signatureType = PGPSignature.BINARY_DOCUMENT; + } + + ArmoredOutputStream armorOut = null; + OutputStream out; + if (enableAsciiArmorOutput) { + armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + + /* Get keys for signature generation for later usage */ + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + if (enableSignature) { + signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); + signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); + if (signingKey == null) { + throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); + } + + if (signaturePassphrase == null) { + throw new PgpGeneralException( + context.getString(R.string.error_no_signature_passphrase)); + } + + updateProgress(R.string.progress_extracting_signature_key, 0, 100); + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); + signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralException( + context.getString(R.string.error_could_not_extract_private_key)); + } + } + updateProgress(R.string.progress_preparing_streams, 5, 100); + + /* Initialize PGPEncryptedDataGenerator for later usage */ + PGPEncryptedDataGenerator cPk = null; + if (enableEncryption) { + // has Integrity packet enabled! + JcePGPDataEncryptorBuilder encryptorBuilder = + new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) + .setWithIntegrityPacket(true); + + cPk = new PGPEncryptedDataGenerator(encryptorBuilder); + + if (encryptionKeyIds.length == 0) { + // Symmetric encryption + Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); + + JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = + new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray()); + cPk.addMethod(symmetricEncryptionGenerator); + } else { + // Asymmetric encryption + for (long id : encryptionKeyIds) { + PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id); + if (key != null) { + JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = + new JcePublicKeyKeyEncryptionMethodGenerator(key); + cPk.addMethod(pubKeyEncryptionGenerator); + } + } + } + } + + /* Initialize signature generator object for later usage */ + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (enableSignature) { + updateProgress(R.string.progress_preparing_signature, 10, 100); + + // content signer based on signing key algorithm and chosen hash algorithm + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( + signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + if (signatureForceV3) { + signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); + signatureV3Generator.init(signatureType, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(signatureType, signaturePrivateKey); + + String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + } + + PGPCompressedDataGenerator compressGen = null; + OutputStream pOut; + OutputStream encryptionOut = null; + BCPGOutputStream bcpgOut; + if (enableEncryption) { + /* actual encryption */ + + encryptionOut = cPk.open(out, new byte[1 << 16]); + + if (enableCompression) { + compressGen = new PGPCompressedDataGenerator(compressionId); + bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); + } else { + bcpgOut = new BCPGOutputStream(encryptionOut); + } + + if (enableSignature) { + if (signatureForceV3) { + signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); + } else { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } + } + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + // file name not needed, so empty string + pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), + new byte[1 << 16]); + updateProgress(R.string.progress_encrypting, 20, 100); + + long progress = 0; + int n; + byte[] buffer = new byte[1 << 16]; + InputStream in = data.getInputStream(); + while ((n = in.read(buffer)) > 0) { + pOut.write(buffer, 0, n); + + // update signature buffer if signature is requested + if (enableSignature) { + if (signatureForceV3) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } + } + + progress += n; + if (data.getSize() != 0) { + updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100); + } + } + + literalGen.close(); + } else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { + /* sign-only of ascii text */ + + updateProgress(R.string.progress_signing, 40, 100); + + // write directly on armor output stream + armorOut.beginClearText(signatureHashAlgorithm); + + InputStream in = data.getInputStream(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + final byte[] newline = "\r\n".getBytes("UTF-8"); + + if (signatureForceV3) { + processLine(reader.readLine(), armorOut, signatureV3Generator); + } else { + processLine(reader.readLine(), armorOut, signatureGenerator); + } + + while (true) { + String line = reader.readLine(); + + if (line == null) { + armorOut.write(newline); + break; + } + + armorOut.write(newline); + + // update signature buffer with input line + if (signatureForceV3) { + signatureV3Generator.update(newline); + processLine(line, armorOut, signatureV3Generator); + } else { + signatureGenerator.update(newline); + processLine(line, armorOut, signatureGenerator); + } + } + + armorOut.endClearText(); + + pOut = new BCPGOutputStream(armorOut); + } else { + // TODO: implement sign-only for files! + pOut = null; + Log.e(Constants.TAG, "not supported!"); + } + + if (enableSignature) { + updateProgress(R.string.progress_generating_signature, 95, 100); + if (signatureForceV3) { + signatureV3Generator.generate().encode(pOut); + } else { + signatureGenerator.generate().encode(pOut); + } + } + + // closing outputs + // NOTE: closing needs to be done in the correct order! + // TODO: closing bcpgOut and pOut??? + if (enableEncryption) { + if (enableCompression) { + compressGen.close(); + } + + encryptionOut.close(); + } + if (enableAsciiArmorOutput) { + armorOut.close(); + } + + out.close(); + outStream.close(); + + updateProgress(R.string.progress_done, 100, 100); + } + + // TODO: merge this into execute method! + // TODO: allow binary input for this class + public void generateSignature() + throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException, + SignatureException { + + OutputStream out; + if (enableAsciiArmorOutput) { + // Ascii Armor (Radix-64) + ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + + if (signatureKeyId == 0) { + throw new PgpGeneralException(context.getString(R.string.error_no_signature_key)); + } + + PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); + PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); + if (signingKey == null) { + throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); + } + + if (signaturePassphrase == null) { + throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase)); + } + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); + PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); + if (signaturePrivateKey == null) { + throw new PgpGeneralException( + context.getString(R.string.error_could_not_extract_private_key)); + } + updateProgress(R.string.progress_preparing_streams, 0, 100); + + updateProgress(R.string.progress_preparing_signature, 30, 100); + + int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; +// if (binary) { +// type = PGPSignature.BINARY_DOCUMENT; +// } + + // content signer based on signing key algorithm and chosen hash algorithm + JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey + .getPublicKey().getAlgorithm(), signatureHashAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + if (signatureForceV3) { + signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); + signatureV3Generator.init(type, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(type, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + updateProgress(R.string.progress_signing, 40, 100); + + InputStream inStream = data.getInputStream(); +// if (binary) { +// byte[] buffer = new byte[1 << 16]; +// int n = 0; +// while ((n = inStream.read(buffer)) > 0) { +// if (signatureForceV3) { +// signatureV3Generator.update(buffer, 0, n); +// } else { +// signatureGenerator.update(buffer, 0, n); +// } +// } +// } else { + final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); + final byte[] newline = "\r\n".getBytes("UTF-8"); + + String line; + while ((line = reader.readLine()) != null) { + if (signatureForceV3) { + processLine(line, null, signatureV3Generator); + signatureV3Generator.update(newline); + } else { + processLine(line, null, signatureGenerator); + signatureGenerator.update(newline); + } + } +// } + + BCPGOutputStream bOut = new BCPGOutputStream(out); + if (signatureForceV3) { + signatureV3Generator.generate().encode(bOut); + } else { + signatureGenerator.generate().encode(bOut); + } + out.close(); + outStream.close(); + + updateProgress(R.string.progress_done, 100, 100); + } + + + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPSignatureGenerator pSignatureGenerator) + throws IOException, SignatureException { + + if (pLine == null) { + return; + } + + final char[] chars = pLine.toCharArray(); + int len = chars.length; + + while (len > 0) { + if (!Character.isWhitespace(chars[len - 1])) { + break; + } + len--; + } + + final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); + + if (pArmoredOutput != null) { + pArmoredOutput.write(data); + } + pSignatureGenerator.update(data); + } + + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPV3SignatureGenerator pSignatureGenerator) + throws IOException, SignatureException { + + if (pLine == null) { + return; + } + + final char[] chars = pLine.toCharArray(); + int len = chars.length; + + while (len > 0) { + if (!Character.isWhitespace(chars[len - 1])) { + break; + } + len--; + } + + final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); + + if (pArmoredOutput != null) { + pArmoredOutput.write(data); + } + pSignatureGenerator.update(data); + } + +} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 3a00f3101..5f18ed6f9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -110,7 +110,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); - // Upgrade from oldVersion through all methods to newest one + // Upgrade from oldVersion through all cases to newest one for (int version = oldVersion; version < newVersion; ++version) { Log.w(Constants.TAG, "Upgrading database to version " + version); @@ -123,14 +123,17 @@ public class KeychainDatabase extends SQLiteOpenHelper { break; case 4: db.execSQL(CREATE_API_APPS); + break; case 5: // new column: package_signature db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS); db.execSQL(CREATE_API_APPS); + break; case 6: // new column: fingerprint db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT + " BLOB;"); + break; default: break; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index cd3007353..781f36758 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -359,7 +359,9 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID); // TODO: deprecated master key id //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); + projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT); + projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED); projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 12bc33995..ba931c61a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; @@ -210,6 +211,13 @@ public class ProviderHelper { ++userIdRank; } + for (PGPSignature certification : new IterableIterator<PGPSignature>(masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) { + //TODO: how to do this?? we need to verify the signatures again and again when they are displayed... +// if (certification.verify +// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank)); + } + + try { context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); } catch (RemoteException e) { @@ -562,6 +570,26 @@ public class ProviderHelper { return fingerprint; } + public static String getUserId(Context context, Uri queryUri) { + String[] projection = new String[]{UserIds.USER_ID}; + Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null); + + String userId = null; + try { + if (cursor != null && cursor.moveToFirst()) { + int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID); + + userId = cursor.getString(col); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return userId; + } + public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri, long[] masterKeyIds) { ArrayList<String> output = new ArrayList<String>(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 5e5735c88..9e517b93e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -43,10 +43,11 @@ import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.OtherHelper; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpImportExport; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; -import org.sufficientlysecure.keychain.pgp.PgpOperation; +import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -95,7 +96,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING"; public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING"; - public static final String ACTION_SIGN_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; + public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; /* keys for data bundle */ @@ -119,11 +120,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial public static final String ENCRYPT_PROVIDER_URI = "provider_uri"; // decrypt/verify - public static final String DECRYPT_SIGNED_ONLY = "signed_only"; public static final String DECRYPT_RETURN_BYTES = "return_binary"; public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric"; - public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey"; // save keyring public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase"; @@ -166,8 +165,8 @@ public class KeychainIntentService extends IntentService implements ProgressDial public static final String DOWNLOAD_KEY_LIST = "query_key_id"; // sign key - public static final String SIGN_KEY_MASTER_KEY_ID = "sign_key_master_key_id"; - public static final String SIGN_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; + public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id"; + public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; /* * possible data keys as result send over messenger @@ -189,10 +188,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial public static final String RESULT_SIGNATURE = "signature"; public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id"; public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id"; + public static final String RESULT_CLEARTEXT_SIGNATURE_ONLY = "signature_only"; public static final String RESULT_SIGNATURE_SUCCESS = "signature_success"; public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown"; - public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key"; // import public static final String RESULT_IMPORT_ADDED = "added"; @@ -241,7 +240,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial String action = intent.getAction(); - // execute action from extra bundle + // executeServiceMethod action from extra bundle if (ACTION_ENCRYPT_SIGN.equals(action)) { try { /* Input */ @@ -322,27 +321,41 @@ public class KeychainIntentService extends IntentService implements ProgressDial } /* Operation */ - PgpOperation operation = new PgpOperation(this, this, inputData, outStream); + PgpSignEncrypt.Builder builder = + new PgpSignEncrypt.Builder(this, inputData, outStream); + builder.progress(this); + if (generateSignature) { Log.d(Constants.TAG, "generating signature..."); - operation.generateSignature(useAsciiArmor, false, secretKeyId, - PassphraseCacheService.getCachedPassphrase(this, secretKeyId), - Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences - .getPreferences(this).getForceV3Signatures()); + builder.enableAsciiArmorOutput(useAsciiArmor) + .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) + .signatureKeyId(secretKeyId) + .signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + + builder.build().generateSignature(); } else if (signOnly) { Log.d(Constants.TAG, "sign only..."); - operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase( - this, secretKeyId), Preferences.getPreferences(this) - .getDefaultHashAlgorithm(), Preferences.getPreferences(this) - .getForceV3Signatures()); + builder.enableAsciiArmorOutput(useAsciiArmor) + .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) + .signatureKeyId(secretKeyId) + .signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + + builder.build().execute(); } else { Log.d(Constants.TAG, "encrypt..."); - operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds, - encryptionPassphrase, Preferences.getPreferences(this) - .getDefaultEncryptionAlgorithm(), secretKeyId, Preferences - .getPreferences(this).getDefaultHashAlgorithm(), Preferences - .getPreferences(this).getForceV3Signatures(), - PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + builder.enableAsciiArmorOutput(useAsciiArmor) + .compressionId(compressionId) + .symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) + .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) + .encryptionKeyIds(encryptionKeyIds) + .encryptionPassphrase(encryptionPassphrase) + .signatureKeyId(secretKeyId) + .signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) + .signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + + builder.build().execute(); } outStream.close(); @@ -395,12 +408,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID); byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES); - boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY); boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES); boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC); - boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY); - InputStream inStream = null; long inLength = -1; InputData inputData = null; @@ -474,14 +484,13 @@ public class KeychainIntentService extends IntentService implements ProgressDial // verifyText and decrypt returning additional resultData values for the // verification of signatures - PgpOperation operation = new PgpOperation(this, this, inputData, outStream); - if (signedOnly) { - resultData = operation.verifyText(lookupUnknownKey); - } else { - resultData = operation.decryptAndVerify( - PassphraseCacheService.getCachedPassphrase(this, secretKeyId), - assumeSymmetricEncryption); - } + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream); + builder.progress(this); + + builder.assumeSymmetric(assumeSymmetricEncryption) + .passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); + + resultData = builder.build().execute(); outStream.close(); @@ -785,19 +794,19 @@ public class KeychainIntentService extends IntentService implements ProgressDial } catch (Exception e) { sendErrorToHandler(e); } - } else if (ACTION_SIGN_KEYRING.equals(action)) { + } else if (ACTION_CERTIFY_KEYRING.equals(action)) { try { /* Input */ - long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID); - long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID); + long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID); + long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID); /* Operation */ String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); PgpKeyOperation keyOperation = new PgpKeyOperation(this, this); - PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId, + PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId, signaturePassPhrase); // store the signed key in our local cache diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java index 5a338c757..dfea7eb04 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentServiceHandler.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; import android.widget.Toast; public class KeychainIntentServiceHandler extends Handler { @@ -60,7 +61,14 @@ public class KeychainIntentServiceHandler extends Handler { } public void showProgressDialog(FragmentActivity activity) { - mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog"); + // TODO: This is a hack!, see http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult + final FragmentManager manager = activity.getSupportFragmentManager(); + Handler handler = new Handler(); + handler.post(new Runnable() { + public void run() { + mProgressDialogFragment.show(manager, "progressDialog"); + } + }); } @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java deleted file mode 100644 index 555303238..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/NoUserIdsException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sufficientlysecure.keychain.service.exception; - -public class NoUserIdsException extends Exception { - - private static final long serialVersionUID = 7009311527126696207L; - - public NoUserIdsException(String message) { - super(message); - } -}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java deleted file mode 100644 index 1152d6796..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/UserInteractionRequiredException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sufficientlysecure.keychain.service.exception; - -public class UserInteractionRequiredException extends Exception { - - private static final long serialVersionUID = -60128148603511936L; - - public UserInteractionRequiredException(String message) { - super(message); - } -}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java deleted file mode 100644 index 14b774eb5..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPassphraseException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sufficientlysecure.keychain.service.exception; - -public class WrongPassphraseException extends Exception { - - private static final long serialVersionUID = -5309689232853485740L; - - public WrongPassphraseException(String message) { - super(message); - } -}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java index 025929cfa..64c4c5e96 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java @@ -109,6 +109,15 @@ public class AppSettingsFragment extends Fragment implements return view; } + /** + * Set error String on key selection + * + * @param error + */ + public void setErrorOnSelectKeyFragment(String error) { + mSelectKeyFragment.setError(error); + } + private void initView(View view) { mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById( R.id.api_app_settings_select_key_fragment); @@ -182,7 +191,7 @@ public class AppSettingsFragment extends Fragment implements // TODO: Better: collapse/expand animation // final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, - // Animation.RELATIVE_TO_SELF, 0.0f); + // Animation.RELATIVE_TO_SELF, 0.0f);u // animation2.setDuration(150); mAdvancedSettingsButton.setOnClickListener(new OnClickListener() { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java deleted file mode 100644 index 427e6bb8f..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/ExtendedApiService.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 <http://www.gnu.org/licenses/>. - */ - -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.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.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) { - - // TODO: for pgp keyrings with password - CallbackHandler pgpPwdCallbackHandler = new PgpToX509.PredefinedPasswordCallbackHandler(""); - - try { - long keyId = appSettings.getKeyId(); - PGPSecretKey pgpSecretKey = PgpKeyHelper.getSigningKey(this, keyId); - - PasswordCallback pgpSecKeyPasswordCallBack = new PasswordCallback("pgp passphrase?", - false); - pgpPwdCallbackHandler.handle(new Callback[] { pgpSecKeyPasswordCallBack }); - PGPPrivateKey pgpPrivKey = pgpSecretKey.extractPrivateKey( - pgpSecKeyPasswordCallBack.getPassword(), Constants.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); - try { - callback.onError(e.getMessage()); - } catch (RemoteException e1) { - Log.e(Constants.TAG, "ExtendedApiService", e); - } - } - - // 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() { - selfSignedX509CertSafe(subjAltNameURI, callback, settings); - } - }; - - checkAndEnqueue(r); - } - - }; - -} diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java index 575f76a22..34213bd3b 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/OpenPgpService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,108 +17,46 @@ package org.sufficientlysecure.keychain.service.remote; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.regex.Matcher; +import android.app.PendingIntent; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; -import org.openintents.openpgp.IOpenPgpCallback; -import org.openintents.openpgp.IOpenPgpKeyIdsCallback; import org.openintents.openpgp.IOpenPgpService; -import org.openintents.openpgp.OpenPgpData; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpConstants; import org.spongycastle.util.Arrays; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.Preferences; -import org.sufficientlysecure.keychain.pgp.PgpHelper; -import org.sufficientlysecure.keychain.pgp.PgpOperation; -import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.service.exception.NoUserIdsException; -import org.sufficientlysecure.keychain.service.exception.UserInteractionRequiredException; -import org.sufficientlysecure.keychain.service.exception.WrongPassphraseException; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; public class OpenPgpService extends RemoteService { - private String getCachedPassphrase(long keyId, boolean allowUserInteraction) - throws UserInteractionRequiredException { - String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId); - - if (passphrase == null) { - if (!allowUserInteraction) { - throw new UserInteractionRequiredException( - "Passphrase not found in cache, please enter your passphrase!"); - } + private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551; + private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552; - Log.d(Constants.TAG, "No passphrase! Activity required!"); - - // start passphrase dialog - PassphraseActivityCallback callback = new PassphraseActivityCallback(); - Bundle extras = new Bundle(); - extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId); - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, callback, - extras); - - if (callback.isSuccess()) { - Log.d(Constants.TAG, "New passphrase entered!"); - - // get again after it was entered - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId); - } else { - Log.d(Constants.TAG, "Passphrase dialog canceled!"); - - return null; - } - - } - - return passphrase; - } - - public class PassphraseActivityCallback extends UserInputCallback { - - private boolean success = false; - - public boolean isSuccess() { - return success; - } - - @Override - public void handleUserInput(Message msg) { - if (msg.arg1 == OKAY) { - success = true; - } else { - success = false; - } - } - }; /** * Search database for key ids based on emails. - * + * * @param encryptionUserIds * @return */ - private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction) - throws UserInteractionRequiredException { + private Bundle getKeyIdsFromEmails(Bundle params, String[] encryptionUserIds) { // find key ids to given emails in database ArrayList<Long> keyIds = new ArrayList<Long>(); @@ -152,96 +90,129 @@ public class OpenPgpService extends RemoteService { } // allow the user to verify pub key selection - if (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) { - SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback(); - - Bundle extras = new Bundle(); - extras.putLongArray(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); - extras.putStringArrayList(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); - extras.putStringArrayList(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, - dublicateUserIds); - - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback, - extras); - - if (callback.isSuccess()) { - Log.d(Constants.TAG, "New selection of pub keys!"); - keyIdsArray = callback.getPubKeyIds(); - } else { - Log.d(Constants.TAG, "Pub key selection canceled!"); - return null; - } - } - - // if no user interaction is allow throw exceptions on duplicate or missing pub keys - if (!allowUserInteraction) { - if (missingUserIdsCheck) - throw new UserInteractionRequiredException( - "Pub keys for these user ids are missing:" + missingUserIds.toString()); - if (dublicateUserIdsCheck) - throw new UserInteractionRequiredException( - "More than one pub key with these user ids exist:" - + dublicateUserIds.toString()); + if (missingUserIdsCheck || dublicateUserIdsCheck) { + // build PendingIntent for passphrase input + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS); + intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); + intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); + intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds); + intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0); + + // return PendingIntent to be executed by client + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED); + result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi); + + return result; } if (keyIdsArray.length == 0) { return null; } - return keyIdsArray; - } - - public class SelectPubKeysActivityCallback extends UserInputCallback { - public static final String PUB_KEY_IDS = "pub_key_ids"; - private boolean success = false; - private long[] pubKeyIds; + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS); + result.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIdsArray); + return result; + } - public boolean isSuccess() { - return success; - } + private Bundle getPassphraseBundleIntent(Bundle params, long keyId) { + // build PendingIntent for passphrase input + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE); + intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId); + // pass params through to activity that it can be returned again later to repeat pgp operation + intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0); + + // return PendingIntent to be executed by client + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED); + result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi); + + return result; + } - public long[] getPubKeyIds() { - return pubKeyIds; - } + private Bundle signImpl(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output, + AppSettings appSettings) { + try { + boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true); - @Override - public void handleUserInput(Message msg) { - if (msg.arg1 == OKAY) { - success = true; - pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS); + // get passphrase from cache, if key has "no" passphrase, this returns an empty String + String passphrase; + if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) { + passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE); } else { - success = false; + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId()); + } + if (passphrase == null) { + // get PendingIntent for passphrase input, add it to given params and return to client + Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId()); + return passphraseBundle; } - } - }; - private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction, - IOpenPgpKeyIdsCallback callback, AppSettings appSettings) { - try { - long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction); - if (keyIds == null) { - throw new NoUserIdsException("No user ids!"); + // Get Input- and OutputStream from ParcelFileDescriptor + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); + OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + try { + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength); + + // sign-only + PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); + builder.enableAsciiArmorOutput(asciiArmor) + .signatureHashAlgorithm(appSettings.getHashAlgorithm()) + .signatureForceV3(false) + .signatureKeyId(appSettings.getKeyId()) + .signaturePassphrase(passphrase); + builder.build().execute(); + } finally { + is.close(); + os.close(); } - callback.onSuccess(keyIds); - } catch (UserInteractionRequiredException e) { - callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage()); - } catch (NoUserIdsException e) { - callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage()); + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS); + return result; } catch (Exception e) { - callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + return result; } } - private synchronized void encryptAndSignSafe(OpenPgpData inputData, - final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction, - IOpenPgpCallback callback, AppSettings appSettings, boolean sign) { + private Bundle encryptAndSignImpl(Bundle params, ParcelFileDescriptor input, + ParcelFileDescriptor output, AppSettings appSettings, + boolean sign) { try { - // TODO: other options of OpenPgpData! - byte[] inputBytes = getInput(inputData); - boolean asciiArmor = false; - if (outputData.getType() == OpenPgpData.TYPE_STRING) { - asciiArmor = true; + boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true); + + long[] keyIds; + if (params.containsKey(OpenPgpConstants.PARAMS_KEY_IDS)) { + keyIds = params.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS); + } else if (params.containsKey(OpenPgpConstants.PARAMS_USER_IDS)) { + // get key ids based on given user ids + String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS); + // give params through to activity... + Bundle result = getKeyIdsFromEmails(params, userIds); + + if (result.getInt(OpenPgpConstants.RESULT_CODE, 0) == OpenPgpConstants.RESULT_CODE_SUCCESS) { + keyIds = result.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS); + } else { + // if not success -> result contains a PendingIntent for user interaction + return result; + } + } else { + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!")); + return result; } // add own key for encryption @@ -249,350 +220,338 @@ public class OpenPgpService extends RemoteService { keyIds[keyIds.length - 1] = appSettings.getKeyId(); // build InputData and write into OutputStream - InputStream inputStream = new ByteArrayInputStream(inputBytes); - long inputLength = inputBytes.length; - InputData inputDt = new InputData(inputStream, inputLength); - - OutputStream outputStream = new ByteArrayOutputStream(); + // Get Input- and OutputStream from ParcelFileDescriptor + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); + OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + try { + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength); + + PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); + builder.enableAsciiArmorOutput(asciiArmor) + .compressionId(appSettings.getCompression()) + .symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm()) + .encryptionKeyIds(keyIds); + + if (sign) { + String passphrase; + if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) { + passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE); + } else { + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), + appSettings.getKeyId()); + } + if (passphrase == null) { + // get PendingIntent for passphrase input, add it to given params and return to client + Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId()); + return passphraseBundle; + } - PgpOperation operation = new PgpOperation(getContext(), null, inputDt, outputStream); - if (sign) { - String passphrase = getCachedPassphrase(appSettings.getKeyId(), - allowUserInteraction); - if (passphrase == null) { - throw new WrongPassphraseException("No or wrong passphrase!"); + // sign and encrypt + builder.signatureHashAlgorithm(appSettings.getHashAlgorithm()) + .signatureForceV3(false) + .signatureKeyId(appSettings.getKeyId()) + .signaturePassphrase(passphrase); + } else { + // encrypt only + builder.signatureKeyId(Id.key.none); } - - operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null, - appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(), - appSettings.getHashAlgorithm(), true, passphrase); - } else { - operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null, - appSettings.getEncryptionAlgorithm(), Id.key.none, - appSettings.getHashAlgorithm(), true, null); + // execute PGP operation! + builder.build().execute(); + } finally { + is.close(); + os.close(); } - outputStream.close(); - - byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray(); - - OpenPgpData output = null; - if (asciiArmor) { - output = new OpenPgpData(new String(outputBytes)); - } else { - output = new OpenPgpData(outputBytes); - } - - // return over handler on client side - callback.onSuccess(output, null); - } catch (UserInteractionRequiredException e) { - callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage()); - } catch (WrongPassphraseException e) { - callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage()); - } catch (Exception e) { - callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); - } - } - - // TODO: asciiArmor?! - private void signSafe(byte[] inputBytes, boolean allowUserInteraction, - IOpenPgpCallback callback, AppSettings appSettings) { - try { - // build InputData and write into OutputStream - InputStream inputStream = new ByteArrayInputStream(inputBytes); - long inputLength = inputBytes.length; - InputData inputData = new InputData(inputStream, inputLength); - - OutputStream outputStream = new ByteArrayOutputStream(); - - String passphrase = getCachedPassphrase(appSettings.getKeyId(), allowUserInteraction); - if (passphrase == null) { - throw new WrongPassphraseException("No or wrong passphrase!"); - } - - PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream); - operation.signText(appSettings.getKeyId(), passphrase, appSettings.getHashAlgorithm(), - Preferences.getPreferences(this).getForceV3Signatures()); - - outputStream.close(); - - byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray(); - OpenPgpData output = new OpenPgpData(new String(outputBytes)); - - // return over handler on client side - callback.onSuccess(output, null); - } catch (UserInteractionRequiredException e) { - callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage()); - } catch (WrongPassphraseException e) { - callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage()); + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS); + return result; } catch (Exception e) { - callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + return result; } } - private synchronized void decryptAndVerifySafe(byte[] inputBytes, boolean allowUserInteraction, - IOpenPgpCallback callback, AppSettings appSettings) { + private Bundle decryptAndVerifyImpl(Bundle params, ParcelFileDescriptor input, + ParcelFileDescriptor output, AppSettings appSettings) { try { - // TODO: this is not really needed - // checked if it is text with BEGIN and END tags - String message = new String(inputBytes); - Log.d(Constants.TAG, "in: " + message); - boolean signedOnly = false; - Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message); - if (matcher.matches()) { - Log.d(Constants.TAG, "PGP_MESSAGE matched"); - message = matcher.group(1); - // replace non breakable spaces - message = message.replaceAll("\\xa0", " "); - - // overwrite inputBytes - inputBytes = message.getBytes(); - } else { - matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message); - if (matcher.matches()) { - signedOnly = true; - Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); - message = matcher.group(1); - // replace non breakable spaces - message = message.replaceAll("\\xa0", " "); - - // overwrite inputBytes - inputBytes = message.getBytes(); + // Get Input- and OutputStream from ParcelFileDescriptor + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); + OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output); + OpenPgpSignatureResult sigResult = null; + try { + +// PGPUtil.getDecoderStream(is) + // TODOs API 2.0: + // implement verify-only! + // fix the mess: http://stackoverflow.com/questions/148130/how-do-i-peek-at-the-first-two-bytes-in-an-inputstream + // should we allow to decrypt everything under every key id or only the one set? + // TODO: instead of trying to get the passphrase before + // pause stream when passphrase is missing and then resume + + + // TODO: this is not really needed + // checked if it is text with BEGIN and END tags +// String message = new String(inputBytes); +// Log.d(Constants.TAG, "in: " + message); +// boolean signedOnly = false; +// Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message); +// if (matcher.matches()) { +// Log.d(Constants.TAG, "PGP_MESSAGE matched"); +// message = matcher.group(1); +// // replace non breakable spaces +// message = message.replaceAll("\\xa0", " "); +// +// // overwrite inputBytes +// inputBytes = message.getBytes(); +// } else { +// matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message); +// if (matcher.matches()) { +// signedOnly = true; +// Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); +// message = matcher.group(1); +// // replace non breakable spaces +// message = message.replaceAll("\\xa0", " "); +// +// // overwrite inputBytes +// inputBytes = message.getBytes(); +// } else { +// Log.d(Constants.TAG, "Nothing matched! Binary?"); +// } +// } + // END TODO + +// Log.d(Constants.TAG, "in: " + new String(inputBytes)); + + // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the + // app, Fix this? + +// String passphrase = null; +// if (!signedOnly) { +// // BEGIN Get key +// // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it +// // better! +// InputStream inputStream2 = new ByteArrayInputStream(inputBytes); +// +// // TODO: duplicates functions from DecryptActivity! +// long secretKeyId; +// try { +// if (inputStream2.markSupported()) { +// // should probably set this to the max size of two +// // pgpF objects, if it even needs to be anything other +// // than 0. +// inputStream2.mark(200); +// } +// secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2); +// if (secretKeyId == Id.key.none) { +// throw new PgpGeneralException(getString(R.string.error_no_secret_key_found)); +// } +// } catch (NoAsymmetricEncryptionException e) { +// if (inputStream2.markSupported()) { +// inputStream2.reset(); +// } +// secretKeyId = Id.key.symmetric; +// if (!PgpDecryptVerify.hasSymmetricEncryption(this, inputStream2)) { +// throw new PgpGeneralException( +// getString(R.string.error_no_known_encryption_found)); +// } +// // we do not support symmetric decryption from the API! +// throw new Exception("Symmetric decryption is not supported!"); +// } +// +// Log.d(Constants.TAG, "secretKeyId " + secretKeyId); + + // NOTE: currently this only gets the passphrase for the key set for this client + String passphrase; + if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) { + passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE); } else { - Log.d(Constants.TAG, "Nothing matched! Binary?"); + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId()); } - } - // END TODO - - Log.d(Constants.TAG, "in: " + new String(inputBytes)); - - // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the - // app, Fix this? - - String passphrase = null; - if (!signedOnly) { - // BEGIN Get key - // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it - // better! - InputStream inputStream2 = new ByteArrayInputStream(inputBytes); - - // TODO: duplicates functions from DecryptActivity! - long secretKeyId; - try { - if (inputStream2.markSupported()) { - // should probably set this to the max size of two - // pgpF objects, if it even needs to be anything other - // than 0. - inputStream2.mark(200); - } - secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2); - if (secretKeyId == Id.key.none) { - throw new PgpGeneralException(getString(R.string.error_no_secret_key_found)); - } - } catch (NoAsymmetricEncryptionException e) { - if (inputStream2.markSupported()) { - inputStream2.reset(); - } - secretKeyId = Id.key.symmetric; - if (!PgpOperation.hasSymmetricEncryption(this, inputStream2)) { - throw new PgpGeneralException( - getString(R.string.error_no_known_encryption_found)); - } - // we do not support symmetric decryption from the API! - throw new Exception("Symmetric decryption is not supported!"); + if (passphrase == null) { + // get PendingIntent for passphrase input, add it to given params and return to client + Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId()); + return passphraseBundle; } +// } - Log.d(Constants.TAG, "secretKeyId " + secretKeyId); + // build InputData and write into OutputStream + long inputLength = is.available(); + InputData inputData = new InputData(is, inputLength); - passphrase = getCachedPassphrase(secretKeyId, allowUserInteraction); - if (passphrase == null) { - throw new WrongPassphraseException("No or wrong passphrase!"); - } - } - // build InputData and write into OutputStream - InputStream inputStream = new ByteArrayInputStream(inputBytes); - long inputLength = inputBytes.length; - InputData inputData = new InputData(inputStream, inputLength); + Bundle outputBundle; + PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os); - OutputStream outputStream = new ByteArrayOutputStream(); +// if (signedOnly) { +// outputBundle = builder.build().verifyText(); +// } else { + builder.assumeSymmetric(false) + .passphrase(passphrase); - Bundle outputBundle; - PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream); - if (signedOnly) { - // TODO: download missing keys from keyserver? - outputBundle = operation.verifyText(false); - } else { - outputBundle = operation.decryptAndVerify(passphrase, false); - } + // Do we want to do this: instead of trying to get the passphrase before + // pause stream when passphrase is missing and then resume??? - outputStream.close(); + // TODO: this also decrypts with other secret keys without passphrase!!! + outputBundle = builder.build().execute(); +// } - byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray(); +// outputStream.close(); - // get signature informations from bundle - boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE); +// byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray(); - OpenPgpSignatureResult sigResult = null; - if (signature) { - long signatureKeyId = outputBundle - .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID); - String signatureUserId = outputBundle - .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID); - boolean signatureSuccess = outputBundle - .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS); - boolean signatureUnknown = outputBundle - .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN); - - int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR; - if (signatureSuccess) { - signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_TRUSTED; - } else if (signatureUnknown) { - signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY; - } + // get signature informations from bundle + boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false); - sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId, - signedOnly, signatureKeyId); + if (signature) { + long signatureKeyId = outputBundle + .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0); + String signatureUserId = outputBundle + .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID); + boolean signatureSuccess = outputBundle + .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false); + boolean signatureUnknown = outputBundle + .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, false); + boolean signatureOnly = outputBundle + .getBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false); + + int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR; + if (signatureSuccess) { + signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED; + } else if (signatureUnknown) { + signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY; + } + + sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId, + signatureOnly, signatureKeyId); + } + } finally { + is.close(); + os.close(); } - OpenPgpData output = new OpenPgpData(new String(outputBytes)); - - // return over handler on client side - callback.onSuccess(output, sigResult); - } catch (UserInteractionRequiredException e) { - callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage()); - } catch (WrongPassphraseException e) { - callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage()); + + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS); + result.putParcelable(OpenPgpConstants.RESULT_SIGNATURE, sigResult); + return result; } catch (Exception e) { - callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage()); + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + return result; } } + private Bundle getKeyIdsImpl(Bundle params) { + // get key ids based on given user ids + String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS); + Bundle result = getKeyIdsFromEmails(params, userIds); + return result; + } + /** - * Returns error to IOpenPgpCallback - * - * @param callback - * @param errorId - * @param message + * Check requirements: + * - params != null + * - has supported API version + * - is allowed to call the service (access has been granted) + * + * @param params + * @return null if everything is okay, or a Bundle with an error/PendingIntent */ - private void callbackOpenPgpError(IOpenPgpCallback callback, int errorId, String message) { - try { - callback.onError(new OpenPgpError(0, message)); - } catch (Exception t) { - Log.e(Constants.TAG, - "Exception while returning OpenPgpError to client via callback.onError()", t); + private Bundle checkRequirements(Bundle params) { + // params Bundle is required! + if (params == null) { + Bundle result = new Bundle(); + OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!"); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + return result; } - } - private void callbackOpenPgpError(IOpenPgpKeyIdsCallback callback, int errorId, String message) { - try { - callback.onError(new OpenPgpError(0, message)); - } catch (Exception t) { - Log.e(Constants.TAG, - "Exception while returning OpenPgpError to client via callback.onError()", t); + // version code is required and needs to correspond to version code of service! + if (params.getInt(OpenPgpConstants.PARAMS_API_VERSION) != OpenPgpConstants.API_VERSION) { + Bundle result = new Bundle(); + OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + return result; } + + // check if caller is allowed to access openpgp keychain + Bundle result = isAllowed(params); + if (result != null) { + return result; + } + + return null; } + // TODO: multi-threading private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { @Override - public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds, - final IOpenPgpCallback callback) throws RemoteException { - final AppSettings settings = getAppSettings(); - - Runnable r = new Runnable() { - @Override - public void run() { - encryptAndSignSafe(input, output, keyIds, true, callback, settings, false); - } - }; - - checkAndEnqueue(r); - } + public Bundle sign(Bundle params, final ParcelFileDescriptor input, final ParcelFileDescriptor output) { + final AppSettings appSettings = getAppSettings(); - @Override - public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output, - final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException { - final AppSettings settings = getAppSettings(); - - Runnable r = new Runnable() { - @Override - public void run() { - encryptAndSignSafe(input, output, keyIds, true, callback, settings, true); - } - }; + Bundle errorResult = checkRequirements(params); + if (errorResult != null) { + return errorResult; + } - checkAndEnqueue(r); + return signImpl(params, input, output, appSettings); } @Override - public void sign(final OpenPgpData input, final OpenPgpData output, - final IOpenPgpCallback callback) throws RemoteException { - final AppSettings settings = getAppSettings(); - - Runnable r = new Runnable() { - @Override - public void run() { - signSafe(getInput(input), true, callback, settings); - } - }; + public Bundle encrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) { + final AppSettings appSettings = getAppSettings(); + + Bundle errorResult = checkRequirements(params); + if (errorResult != null) { + return errorResult; + } - checkAndEnqueue(r); + return encryptAndSignImpl(params, input, output, appSettings, false); } @Override - public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output, - final IOpenPgpCallback callback) throws RemoteException { + public Bundle signAndEncrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) { + final AppSettings appSettings = getAppSettings(); - final AppSettings settings = getAppSettings(); - - Runnable r = new Runnable() { - @Override - public void run() { - decryptAndVerifySafe(getInput(input), true, callback, settings); - } - }; + Bundle errorResult = checkRequirements(params); + if (errorResult != null) { + return errorResult; + } - checkAndEnqueue(r); + return encryptAndSignImpl(params, input, output, appSettings, true); } @Override - public void getKeyIds(final String[] userIds, final boolean allowUserInteraction, - final IOpenPgpKeyIdsCallback callback) throws RemoteException { - - final AppSettings settings = getAppSettings(); + public Bundle decryptAndVerify(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) { + final AppSettings appSettings = getAppSettings(); - Runnable r = new Runnable() { - @Override - public void run() { - getKeyIdsSafe(userIds, allowUserInteraction, callback, settings); - } - }; + Bundle errorResult = checkRequirements(params); + if (errorResult != null) { + return errorResult; + } - checkAndEnqueue(r); + return decryptAndVerifyImpl(params, input, output, appSettings); } - }; - - private static byte[] getInput(OpenPgpData data) { - // TODO: support Uri and ParcelFileDescriptor - - byte[] inBytes = null; - switch (data.getType()) { - case OpenPgpData.TYPE_STRING: - inBytes = data.getString().getBytes(); - break; - - case OpenPgpData.TYPE_BYTE_ARRAY: - inBytes = data.getBytes(); - break; + @Override + public Bundle getKeyIds(Bundle params) { + Bundle errorResult = checkRequirements(params); + if (errorResult != null) { + return errorResult; + } - default: - Log.e(Constants.TAG, "Uri and ParcelFileDescriptor not supported right now!"); - break; + return getKeyIdsImpl(params); } - return inBytes; - } + }; @Override public IBinder onBind(Intent intent) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java index bc513d532..cfd2b9ec3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,17 +19,16 @@ package org.sufficientlysecure.keychain.service.remote; import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.TimeUnit; +import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.util.OpenPgpConstants; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; +import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -50,66 +49,19 @@ import android.os.Messenger; public abstract class RemoteService extends Service { Context mContext; - private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100); - // TODO: Are these parameters okay? - private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, - TimeUnit.SECONDS, mPoolQueue); + private static final int PRIVATE_REQUEST_CODE_REGISTER = 651; + private static final int PRIVATE_REQUEST_CODE_ERROR = 652; - private final Object userInputLock = new Object(); - - /** - * Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting - * threads will be notified and the queue resumed - */ - protected class UserInputCallback extends BaseCallback { - - public void handleUserInput(Message msg) { - } - - @Override - public boolean handleMessage(Message msg) { - handleUserInput(msg); - - // resume - synchronized (userInputLock) { - userInputLock.notifyAll(); - } - mThreadPool.resume(); - return true; - } - - } - - /** - * Extends Handler.Callback with OKAY (1), CANCEL (0) variables - */ - private class BaseCallback implements Handler.Callback { - public static final int OKAY = 1; - public static final int CANCEL = 0; - - @Override - public boolean handleMessage(Message msg) { - return false; - } - - } public Context getContext() { return mContext; } - /** - * Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for - * execution - * - * @param r - */ - protected void checkAndEnqueue(Runnable r) { + protected Bundle isAllowed(Bundle params) { try { if (isCallerAllowed(false)) { - mThreadPool.execute(r); - Log.d(Constants.TAG, "Enqueued runnable…"); + return null; } else { String[] callingPackages = getPackageManager().getPackagesForUid( Binder.getCallingUid()); @@ -121,32 +73,46 @@ public abstract class RemoteService extends Service { packageSignature = getPackageSignature(packageName); } catch (NameNotFoundException e) { Log.e(Constants.TAG, "Should not happen, returning!", e); - return; - } - Log.e(Constants.TAG, - "Not allowed to use service! Starting activity for registration!"); - Bundle extras = new Bundle(); - extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); - extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); - RegisterActivityCallback callback = new RegisterActivityCallback(); - - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback, - extras); - - if (callback.isAllowed()) { - mThreadPool.execute(r); - Log.d(Constants.TAG, "Enqueued runnable…"); - } else { - Log.d(Constants.TAG, "User disallowed app!"); + // return error + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR); + result.putParcelable(OpenPgpConstants.RESULT_ERRORS, + new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); + return result; } + Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!"); + + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_REGISTER); + intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); + intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, intent, 0); + + // return PendingIntent to be executed by client + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED); + result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi); + + return result; } } catch (WrongPackageSignatureException e) { - Log.e(Constants.TAG, e.getMessage()); + Log.e(Constants.TAG, "wrong signature!", e); + + Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); + intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); + intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature)); + intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + + PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_ERROR, intent, 0); + + // return PendingIntent to be executed by client + Bundle result = new Bundle(); + result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED); + result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi); - Bundle extras = new Bundle(); - extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, - getString(R.string.api_error_wrong_signature)); - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras); + return result; } } @@ -161,40 +127,8 @@ public abstract class RemoteService extends Service { } /** - * Locks current thread and pauses execution of runnables and starts activity for user input - * - * @param action - * @param messenger - * @param extras - */ - protected void pauseAndStartUserInteraction(String action, BaseCallback callback, Bundle extras) { - synchronized (userInputLock) { - mThreadPool.pause(); - - Log.d(Constants.TAG, "starting activity..."); - Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setAction(action); - - Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); - - extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger); - intent.putExtras(extras); - - startActivity(intent); - - // lock current thread for user input - try { - userInputLock.wait(); - } catch (InterruptedException e) { - Log.e(Constants.TAG, "CryptoService", e); - } - } - } - - /** * Retrieves AppSettings from database for the application calling this remote service - * + * * @return */ protected AppSettings getAppSettings() { @@ -215,66 +149,11 @@ public abstract class RemoteService extends Service { return null; } - class RegisterActivityCallback extends BaseCallback { - public static final String PACKAGE_NAME = "package_name"; - - private boolean allowed = false; - private String packageName; - - public boolean isAllowed() { - return allowed; - } - - public String getPackageName() { - return packageName; - } - - @Override - public boolean handleMessage(Message msg) { - if (msg.arg1 == OKAY) { - allowed = true; - packageName = msg.getData().getString(PACKAGE_NAME); - - // resume threads - try { - if (isPackageAllowed(packageName)) { - synchronized (userInputLock) { - userInputLock.notifyAll(); - } - mThreadPool.resume(); - } else { - // Should not happen! - Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); - mThreadPool.shutdownNow(); - } - } catch (WrongPackageSignatureException e) { - Log.e(Constants.TAG, e.getMessage()); - - Bundle extras = new Bundle(); - extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, - getString(R.string.api_error_wrong_signature)); - pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, - extras); - } - } else { - allowed = false; - - synchronized (userInputLock) { - userInputLock.notifyAll(); - } - mThreadPool.resume(); - } - return true; - } - - } - /** * Checks if process that binds to this service (i.e. the package name corresponding to the * process) is in the list of allowed package names. - * - * @param allowOnlySelf - * allow only Keychain app itself + * + * @param allowOnlySelf allow only Keychain app itself * @return true if process is allowed to use this service * @throws WrongPackageSignatureException */ @@ -308,7 +187,7 @@ public abstract class RemoteService extends Service { /** * Checks if packageName is a registered app for the API. Does not return true for own package! - * + * * @param packageName * @return * @throws WrongPackageSignatureException diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java index f1e203733..af8e3ade8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/RemoteServiceActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +17,15 @@ package org.sufficientlysecure.keychain.service.remote; -import java.util.ArrayList; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v7.app.ActionBarActivity; +import android.view.View; +import org.openintents.openpgp.util.OpenPgpConstants; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -30,15 +37,7 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.v7.app.ActionBarActivity; -import android.view.View; -import android.widget.Toast; +import java.util.ArrayList; public class RemoteServiceActivity extends ActionBarActivity { @@ -64,17 +63,11 @@ public class RemoteServiceActivity extends ActionBarActivity { // error message public static final String EXTRA_ERROR_MESSAGE = "error_message"; - private Messenger mMessenger; - // register view private AppSettingsFragment mSettingsFragment; // select pub keys view private SelectPublicKeyFragment mSelectFragment; - // has the user clicked one of the buttons - // or do we need to handle the callback in onStop() - private boolean finishHandled; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -82,36 +75,12 @@ public class RemoteServiceActivity extends ActionBarActivity { handleActions(getIntent(), savedInstanceState); } - @Override - protected void onStop() { - super.onStop(); - - if (!finishHandled) { - Message msg = Message.obtain(); - msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - } - } - protected void handleActions(Intent intent, Bundle savedInstanceState) { - finishHandled = false; String action = intent.getAction(); - Bundle extras = intent.getExtras(); + final Bundle extras = intent.getExtras(); - if (extras == null) { - extras = new Bundle(); - } - mMessenger = extras.getParcelable(EXTRA_MESSENGER); - - /** - * com.android.crypto actions - */ if (ACTION_REGISTER.equals(action)) { final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); @@ -125,44 +94,27 @@ public class RemoteServiceActivity extends ActionBarActivity { // user needs to select a key! if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { - Toast.makeText(RemoteServiceActivity.this, - R.string.api_register_error_select_key, Toast.LENGTH_LONG) - .show(); + mSettingsFragment.setErrorOnSelectKeyFragment( + getString(R.string.api_register_error_select_key)); } else { ProviderHelper.insertApiApp(RemoteServiceActivity.this, mSettingsFragment.getAppSettings()); - Message msg = Message.obtain(); - msg.arg1 = RemoteService.RegisterActivityCallback.OKAY; - Bundle data = new Bundle(); - data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME, - packageName); - msg.setData(data); - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); + // give params through for new service call + Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS); + + Intent finishIntent = new Intent(); + finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, oldParams); + RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent); + RemoteServiceActivity.this.finish(); } } }, R.string.api_register_disallow, new View.OnClickListener() { @Override public void onClick(View v) { // Disallow - - Message msg = Message.obtain(); - msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); } } ); @@ -176,8 +128,9 @@ public class RemoteServiceActivity extends ActionBarActivity { mSettingsFragment.setAppSettings(settings); } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); + Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS); - showPassphraseDialog(secretKeyId); + showPassphraseDialog(oldParams, secretKeyId); } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); ArrayList<String> missingUserIds = intent @@ -185,8 +138,8 @@ public class RemoteServiceActivity extends ActionBarActivity { ArrayList<String> dublicateUserIds = intent .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); - String text = new String(); - text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>"; + // TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids + String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>"; text += "<br/><br/>"; if (missingUserIds != null && missingUserIds.size() > 0) { text += getString(R.string.api_select_pub_keys_missing_text); @@ -213,40 +166,22 @@ public class RemoteServiceActivity extends ActionBarActivity { new View.OnClickListener() { @Override public void onClick(View v) { - // ok - - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY; - Bundle data = new Bundle(); - data.putLongArray( - OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS, + // add key ids to params Bundle for new request + Bundle params = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS); + params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds()); - msg.setData(data); - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - finishHandled = true; - finish(); + Intent finishIntent = new Intent(); + finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent); + RemoteServiceActivity.this.finish(); } }, R.string.btn_do_not_save, new View.OnClickListener() { @Override public void onClick(View v) { // cancel - - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL; - ; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } - - finishHandled = true; - finish(); + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); } } ); @@ -279,8 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity { } else if (ACTION_ERROR_MESSAGE.equals(action)) { String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE); - String text = new String(); - text += "<font color=\"red\">" + errorMessage + "</font>"; + String text = "<font color=\"red\">" + errorMessage + "</font>"; // Inflate a "Done" custom action bar view ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, @@ -288,7 +222,8 @@ public class RemoteServiceActivity extends ActionBarActivity { @Override public void onClick(View v) { - finish(); + RemoteServiceActivity.this.setResult(RESULT_OK); + RemoteServiceActivity.this.finish(); } }); @@ -298,7 +233,8 @@ public class RemoteServiceActivity extends ActionBarActivity { HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); textView.setHtmlFromString(text); } else { - Log.e(Constants.TAG, "Wrong action!"); + Log.e(Constants.TAG, "Action does not exist!"); + setResult(RESULT_CANCELED); finish(); } } @@ -308,31 +244,21 @@ public class RemoteServiceActivity extends ActionBarActivity { * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks * for a symmetric passphrase */ - private void showPassphraseDialog(long secretKeyId) { + private void showPassphraseDialog(final Bundle params, long secretKeyId) { // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } + // return given params again, for calling the service method again + Intent finishIntent = new Intent(); + finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent); } else { - Message msg = Message.obtain(); - msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL; - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "CryptoServiceActivity", e); - } + RemoteServiceActivity.this.setResult(RESULT_CANCELED); } - finishHandled = true; - finish(); + RemoteServiceActivity.this.finish(); } }; @@ -345,9 +271,12 @@ public class RemoteServiceActivity extends ActionBarActivity { passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly - returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); + Log.d(Constants.TAG, "No passphrase for this secret key, do pgp operation directly!"); + // return given params again, for calling the service method again + Intent finishIntent = new Intent(); + finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params); + setResult(RESULT_OK, finishIntent); + finish(); } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/WrongPackageSignatureException.java index cef002265..cc08548e8 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/exception/WrongPackageSignatureException.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/remote/WrongPackageSignatureException.java @@ -1,4 +1,4 @@ -package org.sufficientlysecure.keychain.service.exception; +package org.sufficientlysecure.keychain.service.remote; public class WrongPackageSignatureException extends Exception { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index d9fb294d5..aab6b81d9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SignKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -56,7 +56,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton; /** * Signs the specified public key with the specified secret master key */ -public class SignKeyActivity extends ActionBarActivity implements +public class CertifyKeyActivity extends ActionBarActivity implements SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { private BootstrapButton mSignButton; private CheckBox mUploadKeyCheckbox; @@ -72,7 +72,7 @@ public class SignKeyActivity extends ActionBarActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.sign_key_activity); + setContentView(R.layout.certify_key_activity); final ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayShowTitleEnabled(true); @@ -164,8 +164,8 @@ public class SignKeyActivity extends ActionBarActivity implements passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog"); } catch (PgpGeneralException e) { - Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!"); - // send message to handler to start encryption directly + Log.d(Constants.TAG, "No passphrase for this secret key!"); + // send message to handler to start certification directly returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY); } } @@ -196,8 +196,8 @@ public class SignKeyActivity extends ActionBarActivity implements String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId); if (passphrase == null) { showPassphraseDialog(mMasterKeyId); - return; // bail out; need to wait until the user has entered the passphrase - // before trying again + // bail out; need to wait until the user has entered the passphrase before trying again + return; } else { startSigning(); } @@ -218,13 +218,13 @@ public class SignKeyActivity extends ActionBarActivity implements // Send all information needed to service to sign key in other thread Intent intent = new Intent(this, KeychainIntentService.class); - intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING); + intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING); // fill values for this action Bundle data = new Bundle(); - data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId); - data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId); + data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId); + data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); @@ -237,14 +237,12 @@ public class SignKeyActivity extends ActionBarActivity implements if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Toast.makeText(SignKeyActivity.this, R.string.key_sign_success, + Toast.makeText(CertifyKeyActivity.this, R.string.key_sign_success, Toast.LENGTH_SHORT).show(); // check if we need to send the key to the server or not if (mUploadKeyCheckbox.isChecked()) { - /* - * upload the newly signed key to the key server - */ + // upload the newly signed key to the keyserver uploadKey(); } else { setResult(RESULT_OK); @@ -291,7 +289,7 @@ public class SignKeyActivity extends ActionBarActivity implements super.handleMessage(message); if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - Toast.makeText(SignKeyActivity.this, R.string.key_send_success, + Toast.makeText(CertifyKeyActivity.this, R.string.key_send_success, Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 26657479f..652310cd2 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.pgp.PgpOperation; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.ProviderHelper; @@ -43,7 +43,6 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.LookupUnknownKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -61,7 +60,7 @@ import android.view.animation.AnimationUtils; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewFlipper; @@ -78,14 +77,20 @@ public class DecryptActivity extends DrawerActivity { /* EXTRA keys for input */ public static final String EXTRA_TEXT = "text"; + private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; + private static final int RESULT_CODE_FILE = 0x00007003; + private long mSignatureKeyId = 0; private boolean mReturnResult = false; + + // TODO: replace signed only checks with something more intelligent + // PgpDecryptVerify should handle all automatically!!! private boolean mSignedOnly = false; private boolean mAssumeSymmetricEncryption = false; private EditText mMessage = null; - private LinearLayout mSignatureLayout = null; + private RelativeLayout mSignatureLayout = null; private ImageView mSignatureStatusImage = null; private TextView mUserId = null; private TextView mUserIdRest = null; @@ -100,6 +105,7 @@ public class DecryptActivity extends DrawerActivity { private EditText mFilename = null; private CheckBox mDeleteAfter = null; private BootstrapButton mBrowse = null; + private BootstrapButton mLookupKey = null; private String mInputFilename = null; private String mOutputFilename = null; @@ -107,14 +113,10 @@ public class DecryptActivity extends DrawerActivity { private Uri mContentUri = null; private boolean mReturnBinary = false; - private long mUnknownSignatureKeyId = 0; - private long mSecretKeyId = Id.key.none; private FileDialogFragment mFileDialog; - private boolean mLookupUnknownKey = true; - private boolean mDecryptImmediately = false; private BootstrapButton mDecryptButton; @@ -154,7 +156,7 @@ public class DecryptActivity extends DrawerActivity { mSourceLabel.setOnClickListener(nextSourceClickListener); mMessage = (EditText) findViewById(R.id.message); - mSignatureLayout = (LinearLayout) findViewById(R.id.signature); + mSignatureLayout = (RelativeLayout) findViewById(R.id.signature); mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status); mUserId = (TextView) findViewById(R.id.mainUserId); mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); @@ -171,7 +173,15 @@ public class DecryptActivity extends DrawerActivity { mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*", - Id.request.filename); + RESULT_CODE_FILE); + } + }); + + mLookupKey = (BootstrapButton) findViewById(R.id.lookup_key); + mLookupKey.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + lookupUnknownKey(mSignatureKeyId); } }); @@ -239,7 +249,7 @@ public class DecryptActivity extends DrawerActivity { DecryptActivity.this, mSignatureKeyId); if (key != null) { Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEY_SERVER); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId); startActivity(intent); } @@ -263,14 +273,14 @@ public class DecryptActivity extends DrawerActivity { if (mDecryptImmediately || (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText() - .length() > 0 || mContentUri != null))) { + .length() > 0 || mContentUri != null))) { decryptClicked(); } } /** * Handles all actions with this intent - * + * * @param intent */ private void handleActions(Intent intent) { @@ -287,13 +297,13 @@ public class DecryptActivity extends DrawerActivity { * Android's Action */ if (Intent.ACTION_SEND.equals(action) && type != null) { - // When sending to Keychain Encrypt via share menu + // When sending to Keychain Decrypt via share menu if ("text/plain".equals(type)) { // Plain text String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (sharedText != null) { // handle like normal text decryption, override action and extras to later - // execute ACTION_DECRYPT in main actions + // executeServiceMethod ACTION_DECRYPT in main actions extras.putString(EXTRA_TEXT, sharedText); action = ACTION_DECRYPT; } @@ -375,21 +385,21 @@ public class DecryptActivity extends DrawerActivity { private void updateSource() { switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - mDecryptButton.setText(getString(R.string.btn_decrypt)); - break; - } + case R.id.sourceFile: { + mSourceLabel.setText(R.string.label_file); + mDecryptButton.setText(getString(R.string.btn_decrypt)); + break; + } - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - mDecryptButton.setText(getString(R.string.btn_decrypt)); - break; - } + case R.id.sourceMessage: { + mSourceLabel.setText(R.string.label_message); + mDecryptButton.setText(getString(R.string.btn_decrypt)); + break; + } - default: { - break; - } + default: { + break; + } } } @@ -449,7 +459,7 @@ public class DecryptActivity extends DrawerActivity { } else { if (mDecryptTarget == Id.target.file) { askForOutputFilename(); - } else { + } else { // mDecryptTarget == Id.target.message decryptStart(); } } @@ -527,7 +537,7 @@ public class DecryptActivity extends DrawerActivity { try { if (inStream.markSupported()) { inStream.mark(200); // should probably set this to the max size of two pgpF - // objects, if it even needs to be anything other than 0. + // objects, if it even needs to be anything other than 0. } mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream); if (mSecretKeyId == Id.key.none) { @@ -539,7 +549,7 @@ public class DecryptActivity extends DrawerActivity { inStream.reset(); } mSecretKeyId = Id.key.symmetric; - if (!PgpOperation.hasSymmetricEncryption(this, inStream)) { + if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) { throw new PgpGeneralException( getString(R.string.error_no_known_encryption_found)); } @@ -559,7 +569,7 @@ public class DecryptActivity extends DrawerActivity { data = "\n\n" + data; intent.putExtra(EncryptActivity.EXTRA_TEXT, data); intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId); - intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId }); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[]{mSignatureKeyId}); startActivity(intent); } @@ -587,28 +597,10 @@ public class DecryptActivity extends DrawerActivity { } private void lookupUnknownKey(long unknownKeyId) { - // Message is received after passphrase is cached - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) { - // the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment - // starts a new Intent which then returns data - } else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) { - // decrypt again, but don't lookup unknown keys! - mLookupUnknownKey = false; - decryptStart(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment - .newInstance(messenger, unknownKeyId); - - lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog"); + Intent intent = new Intent(this, ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); + intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId); + startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY); } private void decryptStart() { @@ -644,8 +636,6 @@ public class DecryptActivity extends DrawerActivity { data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId); - data.putBoolean(KeychainIntentService.DECRYPT_SIGNED_ONLY, mSignedOnly); - data.putBoolean(KeychainIntentService.DECRYPT_LOOKUP_UNKNOWN_KEY, mLookupUnknownKey); data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary); data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption); @@ -662,15 +652,6 @@ public class DecryptActivity extends DrawerActivity { // get returned data bundle Bundle returnData = message.getData(); - // if key is unknown show lookup dialog - if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY) - && mLookupUnknownKey) { - mUnknownSignatureKeyId = returnData - .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID); - lookupUnknownKey(mUnknownSignatureKeyId); - return; - } - mSignatureKeyId = 0; mSignatureLayout.setVisibility(View.GONE); @@ -685,26 +666,26 @@ public class DecryptActivity extends DrawerActivity { } switch (mDecryptTarget) { - case Id.target.message: - String decryptedMessage = returnData - .getString(KeychainIntentService.RESULT_DECRYPTED_STRING); - mMessage.setText(decryptedMessage); - mMessage.setHorizontallyScrolling(false); - - break; - - case Id.target.file: - if (mDeleteAfter.isChecked()) { - // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); - deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); - } - break; - - default: - // shouldn't happen - break; + case Id.target.message: + String decryptedMessage = returnData + .getString(KeychainIntentService.RESULT_DECRYPTED_STRING); + mMessage.setText(decryptedMessage); + mMessage.setHorizontallyScrolling(false); + + break; + + case Id.target.file: + if (mDeleteAfter.isChecked()) { + // Create and show dialog to delete original file + DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); + } + break; + + default: + // shouldn't happen + break; } @@ -727,19 +708,24 @@ public class DecryptActivity extends DrawerActivity { if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) { mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); + mLookupKey.setVisibility(View.GONE); } else if (returnData .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) { mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.VISIBLE); Toast.makeText(DecryptActivity.this, - R.string.unknown_signature_key_touch_to_look_up, + R.string.unknown_signature, Toast.LENGTH_LONG).show(); } else { mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + mLookupKey.setVisibility(View.GONE); } mSignatureLayout.setVisibility(View.VISIBLE); } } - }; + } + + ; }; // Create a new Messenger for the communication back @@ -756,36 +742,37 @@ public class DecryptActivity extends DrawerActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(this, data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + case RESULT_CODE_FILE: { + if (resultCode == RESULT_OK && data != null) { + try { + String path = FileHelper.getPath(this, data.getData()); + Log.d(Constants.TAG, "path=" + path); + + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } } + return; } - return; - } - // this request is returned after LookupUnknownKeyDialogFragment started - // ImportKeysActivity and user looked uo key - case Id.request.look_up_key_id: { - Log.d(Constants.TAG, "Returning from Lookup Key..."); - // decrypt again without lookup - mLookupUnknownKey = false; - decryptStart(); - return; - } + // this request is returned after LookupUnknownKeyDialogFragment started + // ImportKeysActivity and user looked uo key + case RESULT_CODE_LOOKUP_KEY: { + Log.d(Constants.TAG, "Returning from Lookup Key..."); + if (resultCode == RESULT_OK) { + // decrypt again + decryptStart(); + } + return; + } - default: { - break; - } - } + default: { + super.onActivityResult(requestCode, resultCode, data); - super.onActivityResult(requestCode, resultCode, data); + break; + } + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 81446db99..85bfba224 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -173,7 +173,7 @@ public class EncryptActivity extends DrawerActivity { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (sharedText != null) { // handle like normal text encryption, override action and extras to later - // execute ACTION_ENCRYPT in main actions + // executeServiceMethod ACTION_ENCRYPT in main actions extras.putString(EXTRA_TEXT, sharedText); extras.putBoolean(EXTRA_ASCII_ARMOR, true); action = ACTION_ENCRYPT; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java index be7c50dbb..12d689274 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java @@ -33,18 +33,7 @@ import android.view.ViewGroup; import android.widget.TextView; -public class HelpFragmentAbout extends Fragment { - - /** - * Workaround for Android Bug. See - * http://stackoverflow.com/questions/8748064/starting-activity-from - * -fragment-causes-nullpointerexception - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - setUserVisibleHint(true); - } +public class HelpAboutFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java index 2fe7c56e6..9ccd7e088 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -20,12 +20,13 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; @@ -37,8 +38,6 @@ public class HelpActivity extends ActionBarActivity { ViewPager mViewPager; TabsAdapter mTabsAdapter; - TextView tabCenter; - TextView tabText; @Override public void onCreate(Bundle savedInstanceState) { @@ -63,93 +62,21 @@ public class HelpActivity extends ActionBarActivity { } Bundle startBundle = new Bundle(); - startBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_start); + startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), - HelpFragmentHtml.class, startBundle, (selectedTab == 0 ? true : false)); + HelpHtmlFragment.class, startBundle, (selectedTab == 0 ? true : false)); Bundle nfcBundle = new Bundle(); - nfcBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_nfc_beam); + nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), - HelpFragmentHtml.class, nfcBundle, (selectedTab == 1 ? true : false)); + HelpHtmlFragment.class, nfcBundle, (selectedTab == 1 ? true : false)); Bundle changelogBundle = new Bundle(); - changelogBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_changelog); + changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), - HelpFragmentHtml.class, changelogBundle, (selectedTab == 2 ? true : false)); + HelpHtmlFragment.class, changelogBundle, (selectedTab == 2 ? true : false)); mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), - HelpFragmentAbout.class, null, (selectedTab == 3 ? true : false)); - } - - public static class TabsAdapter extends FragmentPagerAdapter implements ActionBar.TabListener, - ViewPager.OnPageChangeListener { - private final Context mContext; - private final ActionBar mActionBar; - private final ViewPager mViewPager; - private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); - - static final class TabInfo { - private final Class<?> clss; - private final Bundle args; - - TabInfo(Class<?> _class, Bundle _args) { - clss = _class; - args = _args; - } - } - - public TabsAdapter(ActionBarActivity activity, ViewPager pager) { - super(activity.getSupportFragmentManager()); - mContext = activity; - mActionBar = activity.getSupportActionBar(); - mViewPager = pager; - mViewPager.setAdapter(this); - mViewPager.setOnPageChangeListener(this); - } - - public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args, boolean selected) { - TabInfo info = new TabInfo(clss, args); - tab.setTag(info); - tab.setTabListener(this); - mTabs.add(info); - mActionBar.addTab(tab, selected); - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return mTabs.size(); - } - - @Override - public Fragment getItem(int position) { - TabInfo info = mTabs.get(position); - return Fragment.instantiate(mContext, info.clss.getName(), info.args); - } - - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - public void onPageSelected(int position) { - mActionBar.setSelectedNavigationItem(position); - } - - public void onPageScrollStateChanged(int state) { - } - - public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { - Object tag = tab.getTag(); - for (int i = 0; i < mTabs.size(); i++) { - if (mTabs.get(i) == tag) { - mViewPager.setCurrentItem(i); - } - } - } - - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { - } + HelpAboutFragment.class, null, (selectedTab == 3 ? true : false)); } }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java index fb6747db0..3afb5bbe0 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpFragmentHtml.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpHtmlFragment.java @@ -28,7 +28,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ScrollView; -public class HelpFragmentHtml extends Fragment { +public class HelpHtmlFragment extends Fragment { private Activity mActivity; private int htmlFile; @@ -36,10 +36,10 @@ public class HelpFragmentHtml extends Fragment { public static final String ARG_HTML_FILE = "htmlFile"; /** - * Create a new instance of HelpFragmentHtml, providing "htmlFile" as an argument. + * Create a new instance of HelpHtmlFragment, providing "htmlFile" as an argument. */ - static HelpFragmentHtml newInstance(int htmlFile) { - HelpFragmentHtml f = new HelpFragmentHtml(); + static HelpHtmlFragment newInstance(int htmlFile) { + HelpHtmlFragment f = new HelpHtmlFragment(); // Supply html raw file input as an argument. Bundle args = new Bundle(); @@ -49,17 +49,6 @@ public class HelpFragmentHtml extends Fragment { return f; } - /** - * Workaround for Android Bug. See - * http://stackoverflow.com/questions/8748064/starting-activity-from - * -fragment-causes-nullpointerexception - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - setUserVisibleHint(true); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mActivity = getActivity(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index dc3a07def..a5027ac1c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2011 Senecaso * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,16 +47,19 @@ import android.support.v7.app.ActionBar; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; -import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; +import com.devspark.appmsg.AppMsg; public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNavigationListener { public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY"; public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_QR_CODE"; - public static final String ACTION_IMPORT_KEY_FROM_KEY_SERVER = Constants.INTENT_PREFIX - + "IMPORT_KEY_FROM_KEY_SERVER"; + public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX + + "IMPORT_KEY_FROM_KEYSERVER"; + // TODO: implement: + public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX + + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN"; // Actions for internal use only: public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX @@ -67,7 +70,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa // only used by ACTION_IMPORT_KEY public static final String EXTRA_KEY_BYTES = "key_bytes"; - // only used by ACTION_IMPORT_KEY_FROM_KEY_SERVER + // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER public static final String EXTRA_QUERY = "query"; public static final String EXTRA_KEY_ID = "key_id"; public static final String EXTRA_FINGERPRINT = "fingerprint"; @@ -78,6 +81,16 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa private Fragment mCurrentFragment; private BootstrapButton mImportButton; + private static final Class[] NAVIGATION_CLASSES = new Class[]{ + ImportKeysServerFragment.class, + ImportKeysFileFragment.class, + ImportKeysQrCodeFragment.class, + ImportKeysClipboardFragment.class, + ImportKeysNFCFragment.class + }; + + private int mCurrentNavPostition = -1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -107,6 +120,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa handleActions(savedInstanceState, getIntent()); } + protected void handleActions(Bundle savedInstanceState, Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); @@ -125,24 +139,23 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) { /* Scanning a fingerprint directly with Barcode Scanner */ - getSupportActionBar().setSelectedNavigationItem(0); - loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[0]); - loadFromFingerprintUri(dataUri); + loadFromFingerprintUri(savedInstanceState, dataUri); } else if (ACTION_IMPORT_KEY.equals(action)) { /* Keychain's own Actions */ - getSupportActionBar().setSelectedNavigationItem(1); - loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[1]); + + // display file fragment + loadNavFragment(1, null); if (dataUri != null) { - // directly load data + // action: directly load data startListFragment(savedInstanceState, null, dataUri, null); } else if (extras.containsKey(EXTRA_KEY_BYTES)) { byte[] importData = intent.getByteArrayExtra(EXTRA_KEY_BYTES); - // directly load data + // action: directly load data startListFragment(savedInstanceState, importData, null, null); } - } else if (ACTION_IMPORT_KEY_FROM_KEY_SERVER.equals(action)) { + } else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)) { String query = null; if (extras.containsKey(EXTRA_QUERY)) { query = extras.getString(EXTRA_QUERY); @@ -161,82 +174,97 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa return; } - // search directly - getSupportActionBar().setSelectedNavigationItem(0); + // display keyserver fragment with query Bundle args = new Bundle(); args.putString(ImportKeysServerFragment.ARG_QUERY, query); - loadFragment(ImportKeysServerFragment.class, args, mNavigationStrings[0]); + loadNavFragment(0, args); + // action: search immediately startListFragment(savedInstanceState, null, null, query); - } else { - // Other actions + } else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { + + // NOTE: this only displays the appropriate fragment, no actions are taken + loadNavFragment(1, null); + + // no immediate actions! startListFragment(savedInstanceState, null, null, null); + } else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) { + // also exposed in AndroidManifest - if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) { - getSupportActionBar().setSelectedNavigationItem(1); - loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[1]); - } else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) { - // also exposed in AndroidManifest - getSupportActionBar().setSelectedNavigationItem(2); - loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[2]); - } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) { - getSupportActionBar().setSelectedNavigationItem(3); - loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[3]); - } + // NOTE: this only displays the appropriate fragment, no actions are taken + loadNavFragment(2, null); + + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); + } else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) { + + // NOTE: this only displays the appropriate fragment, no actions are taken + loadNavFragment(3, null); + + // no immediate actions! + startListFragment(savedInstanceState, null, null, null); + } else { + startListFragment(savedInstanceState, null, null, null); } } private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) { - // Check that the activity is using the layout version with - // the fragment_container FrameLayout - if (findViewById(R.id.import_keys_list_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; - } + // 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 - mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery); + // Create an instance of the fragment + mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery); - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.import_keys_list_container, mListFragment) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); - } + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.import_keys_list_container, mListFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); } + /** + * "Basically, when using a list navigation, onNavigationItemSelected() is automatically + * called when your activity is created/re-created, whether you like it or not. To prevent + * your Fragment's onCreateView() from being called twice, this initial automatic call to + * onNavigationItemSelected() should check whether the Fragment is already in existence + * inside your Activity." + * <p/> + * from http://stackoverflow.com/questions/10983396/fragment-oncreateview-and-onactivitycreated-called-twice/14295474#14295474 + * + * In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint, + * the fragment would be loaded twice resulting in the query being empty after the second load. + * + * Our solution: + * To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment + * checks against mCurrentNavPostition. + * + * @param itemPosition + * @param itemId + * @return + */ @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - // Create new fragment from our own Fragment class - switch (itemPosition) { - case 0: - loadFragment(ImportKeysServerFragment.class, null, mNavigationStrings[itemPosition]); - break; - case 1: - loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[itemPosition]); - break; - case 2: - loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[itemPosition]); - break; - case 3: - loadFragment(ImportKeysClipboardFragment.class, null, mNavigationStrings[itemPosition]); - break; - case 4: - loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[itemPosition]); - break; - - default: - break; - } + Log.d(Constants.TAG, "onNavigationItemSelected"); + + loadNavFragment(itemPosition, null); + return true; } + private void loadNavFragment(int itemPosition, Bundle args) { + if (mCurrentNavPostition != itemPosition) { + getSupportActionBar().setSelectedNavigationItem(itemPosition); + loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]); + mCurrentNavPostition = itemPosition; + } + } + private void loadFragment(Class<?> clss, Bundle args, String tag) { mCurrentFragment = Fragment.instantiate(this, clss.getName(), args); @@ -248,26 +276,26 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa ft.commit(); } - public void loadFromFingerprintUri(Uri dataUri) { + public void loadFromFingerprintUri(Bundle savedInstanceState, Uri dataUri) { String fingerprint = dataUri.toString().split(":")[1].toLowerCase(Locale.ENGLISH); Log.d(Constants.TAG, "fingerprint: " + fingerprint); if (fingerprint.length() < 16) { - Toast.makeText(this, R.string.import_qr_code_too_short_fingerprint, - Toast.LENGTH_LONG).show(); + AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint, + AppMsg.STYLE_ALERT).show(); return; } String query = "0x" + fingerprint; - // search directly - getSupportActionBar().setSelectedNavigationItem(0); + // display keyserver fragment with query Bundle args = new Bundle(); args.putString(ImportKeysServerFragment.ARG_QUERY, query); - loadFragment(ImportKeysServerFragment.class, args, mNavigationStrings[0]); + loadNavFragment(0, args); - startListFragment(null, null, null, query); + // action: search directly + startListFragment(savedInstanceState, null, null, query); } public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) { @@ -364,7 +392,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa } else { toastMessage = getString(R.string.no_keys_added_or_updated); } - Toast.makeText(ImportKeysActivity.this, toastMessage, Toast.LENGTH_SHORT) + AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO) .show(); if (bad > 0) { AlertDialog.Builder alert = new AlertDialog.Builder( @@ -446,7 +474,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa // start service with intent startService(intent); } else { - Toast.makeText(this, R.string.error_nothing_import, Toast.LENGTH_LONG).show(); + AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show(); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 68d318491..b52de5bc9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -122,7 +122,7 @@ public class ImportKeysListFragment extends ListFragment implements mKeyBytes = getArguments().getByteArray(ARG_BYTES); mServerQuery = getArguments().getString(ARG_SERVER_QUERY); - // TODO: this is used when scanning QR Code. Currently it simply uses key server nr 0 + // TODO: this is used when scanning QR Code. Currently it simply uses keyserver nr 0 mKeyServer = Preferences.getPreferences(getActivity()) .getKeyServers()[0]; @@ -136,7 +136,7 @@ public class ImportKeysListFragment extends ListFragment implements getLoaderManager().initLoader(LOADER_ID_BYTES, null, this); } - if (mServerQuery != null) { + if (mServerQuery != null && mKeyServer != null) { // Start out with a progress indicator. setListShown(false); @@ -165,14 +165,19 @@ public class ImportKeysListFragment extends ListFragment implements mServerQuery = serverQuery; mKeyServer = keyServer; - // Start out with a progress indicator. - setListShown(false); + if (mKeyBytes != null || mDataUri != null) { + // Start out with a progress indicator. + setListShown(false); - if (mKeyBytes != null || mDataUri != null) getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); + } + + if (mServerQuery != null && mKeyServer != null) { + // Start out with a progress indicator. + setListShown(false); - if (mServerQuery != null && mKeyServer != null) getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this); + } } @Override diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index 63727ad26..ee91b2434 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -98,22 +98,29 @@ public class ImportKeysQrCodeFragment extends Fragment { IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode, resultCode, data); if (scanResult != null && scanResult.getFormatName() != null) { + String scannedContent = scanResult.getContents(); - Log.d(Constants.TAG, "scanResult content: " + scanResult.getContents()); + Log.d(Constants.TAG, "scannedContent: " + scannedContent); // look if it's fingerprint only - if (scanResult.getContents().toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { + if (scannedContent.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) { importFingerprint(Uri.parse(scanResult.getContents())); return; } // look if it is the whole key - String[] parts = scanResult.getContents().split(","); + String[] parts = scannedContent.split(","); if (parts.length == 3) { importParts(parts); return; } + // is this a full key encoded as qr code? + if (scannedContent.startsWith("-----BEGIN PGP")) { + mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null); + return; + } + // fail... Toast.makeText(getActivity(), R.string.import_qr_code_wrong, Toast.LENGTH_LONG) .show(); @@ -130,7 +137,7 @@ public class ImportKeysQrCodeFragment extends Fragment { } public void importFingerprint(Uri dataUri) { - mImportActivity.loadFromFingerprintUri(dataUri); + mImportActivity.loadFromFingerprintUri(null, dataUri); } private void importParts(String[] parts) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 2e0956d8b..d77015aa7 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -127,21 +127,21 @@ public class ImportKeysServerFragment extends Fragment { mImportActivity = (ImportKeysActivity) getActivity(); // set displayed values - if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) { - String query = getArguments().getString(ARG_QUERY); - mQueryEditText.setText(query, TextView.BufferType.EDITABLE); + if (getArguments() != null) { + if (getArguments().containsKey(ARG_QUERY)) { + String query = getArguments().getString(ARG_QUERY); + mQueryEditText.setText(query, TextView.BufferType.EDITABLE); + + Log.d(Constants.TAG, "query: " + query); + } - String keyServer = null; if (getArguments().containsKey(ARG_KEY_SERVER)) { - keyServer = getArguments().getString(ARG_KEY_SERVER); + String keyServer = getArguments().getString(ARG_KEY_SERVER); int keyServerPos = mServerAdapter.getPosition(keyServer); mServerSpinner.setSelection(keyServerPos); - } else { - keyServer = (String) mServerSpinner.getSelectedItem(); - } - Log.d(Constants.TAG, "query: " + query); - Log.d(Constants.TAG, "keyServer: " + keyServer); + Log.d(Constants.TAG, "keyServer: " + keyServer); + } } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java index 6ae2b9bf9..c28d57627 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; import java.util.Set; import org.sufficientlysecure.keychain.Id; @@ -30,12 +31,16 @@ import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import se.emilsjolander.stickylistheaders.ApiLevelTooLowException; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; + import android.annotation.SuppressLint; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -50,6 +55,7 @@ import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; +import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; @@ -63,7 +69,7 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte private KeyListPublicAdapter mAdapter; private StickyListHeadersListView mStickyList; - // empty layout + // empty list layout private BootstrapButton mButtonEmptyCreate; private BootstrapButton mButtonEmptyImport; @@ -92,9 +98,9 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte @Override public void onClick(View v) { - Intent intentImportFromFile = new Intent(getActivity(), ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); - startActivityForResult(intentImportFromFile, 0); + Intent intent = new Intent(getActivity(), ImportKeysActivity.class); + intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); + startActivityForResult(intent, 0); } }); @@ -109,7 +115,6 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // mKeyListPublicActivity = (KeyListPublicActivity) getActivity(); mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); mStickyList.setOnItemClickListener(this); @@ -159,18 +164,16 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte } switch (item.getItemId()) { - case R.id.menu_key_list_public_multi_encrypt: { - encrypt(ids); - - break; - } - case R.id.menu_key_list_public_multi_delete: { - showDeleteKeyDialog(ids); - - break; - } + case R.id.menu_key_list_public_multi_encrypt: { + encrypt(mode, ids); + break; + } + case R.id.menu_key_list_public_multi_delete: { + showDeleteKeyDialog(mode, ids); + break; + } } - return false; + return true; } @Override @@ -181,7 +184,7 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { if (checked) { count++; mAdapter.setNewSelection(position, checked); @@ -212,8 +215,12 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte } // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, - UserIds.USER_ID }; + static final String[] PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.UserIds.USER_ID, + KeychainContract.Keys.IS_REVOKED + }; static final int USER_ID_INDEX = 2; @@ -270,7 +277,7 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte startActivity(viewIntent); } - public void encrypt(long[] keyRingRowIds) { + public void encrypt(ActionMode mode, long[] keyRingRowIds) { // get master key ids from row ids long[] keyRingIds = new long[keyRingRowIds.length]; for (int i = 0; i < keyRingRowIds.length; i++) { @@ -282,15 +289,44 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds); // used instead of startActivity set actionbar based on callingPackage startActivityForResult(intent, 0); + + mode.finish(); } /** * Show dialog to delete key - * + * * @param keyRingRowIds */ - public void showDeleteKeyDialog(long[] keyRingRowIds) { - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, + public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + Bundle returnData = message.getData(); + if (returnData != null + && returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) { + ArrayList<String> notDeleted = + returnData.getStringArrayList(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED); + String notDeletedMsg = ""; + for (String userId : notDeleted) { + notDeletedMsg += userId + "\n"; + } + Toast.makeText(getActivity(), getString(R.string.error_can_not_delete_contacts, notDeletedMsg) + + getResources().getQuantityString(R.plurals.error_can_not_delete_info, notDeleted.size()), + Toast.LENGTH_LONG).show(); + + mode.finish(); + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, keyRingRowIds, Id.type.public_key); deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java index f2e6131f6..f9d267f27 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; import java.util.Set; import org.sufficientlysecure.keychain.Id; @@ -33,6 +34,9 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -45,6 +49,7 @@ import android.widget.AdapterView; import android.widget.ListView; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView.OnItemClickListener; +import android.widget.Toast; public class KeyListSecretFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener { @@ -103,13 +108,12 @@ public class KeyListSecretFragment extends ListFragment implements } switch (item.getItemId()) { - case R.id.menu_key_list_public_multi_delete: { - showDeleteKeyDialog(ids); - - break; - } + case R.id.menu_key_list_public_multi_delete: { + showDeleteKeyDialog(mode, ids); + break; + } } - return false; + return true; } @Override @@ -120,7 +124,7 @@ public class KeyListSecretFragment extends ListFragment implements @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { if (checked) { count++; mAdapter.setNewSelection(position, checked); @@ -153,8 +157,8 @@ public class KeyListSecretFragment extends ListFragment implements } // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, - UserIds.USER_ID }; + static final String[] PROJECTION = new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, + UserIds.USER_ID}; static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; public Loader<Cursor> onCreateLoader(int id, Bundle args) { @@ -202,13 +206,27 @@ public class KeyListSecretFragment extends ListFragment implements /** * Show dialog to delete key - * + * * @param keyRingRowIds */ - public void showDeleteKeyDialog(long[] keyRingRowIds) { - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, + public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) { + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + mode.finish(); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, keyRingRowIds, Id.type.secret_key); deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index e3881503b..550d3047d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -40,7 +40,7 @@ import android.widget.Toast; import com.beardedhen.androidbootstrap.BootstrapButton; /** - * Sends the selected public key to a key server + * Sends the selected public key to a keyserver */ public class UploadKeyActivity extends ActionBarActivity { private BootstrapButton mUploadButton; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 99d93fbbd..0a452bc8a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -18,8 +18,17 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; -import java.util.Date; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; @@ -27,63 +36,27 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; -import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; import org.sufficientlysecure.keychain.util.Log; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v7.app.ActionBarActivity; -import android.text.format.DateFormat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.beardedhen.androidbootstrap.BootstrapButton; +import java.util.ArrayList; -public class ViewKeyActivity extends ActionBarActivity implements - LoaderManager.LoaderCallbacks<Cursor> { +public class ViewKeyActivity extends ActionBarActivity { ExportHelper mExportHelper; protected Uri mDataUri; - private TextView mName; - private TextView mEmail; - private TextView mComment; - private TextView mAlgorithm; - private TextView mKeyId; - private TextView mExpiry; - private TextView mCreation; - private TextView mFingerprint; - private BootstrapButton mActionEncrypt; - - private ListView mUserIds; - private ListView mKeys; - - private static final int LOADER_ID_KEYRING = 0; - private static final int LOADER_ID_USER_IDS = 1; - private static final int LOADER_ID_KEYS = 2; - private ViewKeyUserIdsAdapter mUserIdsAdapter; - private ViewKeyKeysAdapter mKeysAdapter; + public static final String EXTRA_SELECTED_TAB = "selectedTab"; + + ViewPager mViewPager; + TabsAdapter mTabsAdapter; + + private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006; @Override protected void onCreate(Bundle savedInstanceState) { @@ -91,25 +64,36 @@ public class ViewKeyActivity extends ActionBarActivity implements mExportHelper = new ExportHelper(this); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setIcon(android.R.color.transparent); - getSupportActionBar().setHomeButtonEnabled(true); + // let the actionbar look like Android's contact app + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setIcon(android.R.color.transparent); + actionBar.setHomeButtonEnabled(true); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); setContentView(R.layout.view_key_activity); - mName = (TextView) findViewById(R.id.name); - mEmail = (TextView) findViewById(R.id.email); - mComment = (TextView) findViewById(R.id.comment); - mKeyId = (TextView) findViewById(R.id.key_id); - mAlgorithm = (TextView) findViewById(R.id.algorithm); - mCreation = (TextView) findViewById(R.id.creation); - mExpiry = (TextView) findViewById(R.id.expiry); - mFingerprint = (TextView) findViewById(R.id.fingerprint); - mActionEncrypt = (BootstrapButton) findViewById(R.id.action_encrypt); - mUserIds = (ListView) findViewById(R.id.user_ids); - mKeys = (ListView) findViewById(R.id.keys); - - loadData(getIntent()); + mViewPager = (ViewPager) findViewById(R.id.pager); + + mTabsAdapter = new TabsAdapter(this, mViewPager); + + int selectedTab = 0; + Intent intent = getIntent(); + if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { + selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); + } + + mDataUri = getIntent().getData(); + + Bundle mainBundle = new Bundle(); + mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)), + ViewKeyMainFragment.class, mainBundle, (selectedTab == 0 ? true : false)); + + Bundle certBundle = new Bundle(); + certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri); + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)), + ViewKeyCertsFragment.class, certBundle, (selectedTab == 1 ? true : false)); } @Override @@ -130,9 +114,6 @@ public class ViewKeyActivity extends ActionBarActivity implements case R.id.menu_key_view_update: updateFromKeyserver(mDataUri); return true; - case R.id.menu_key_view_sign: - signKey(mDataUri); - return true; case R.id.menu_key_view_export_keyserver: uploadToKeyserver(mDataUri); return true; @@ -159,230 +140,13 @@ public class ViewKeyActivity extends ActionBarActivity implements copyToClipboard(mDataUri); return true; case R.id.menu_key_view_delete: { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - setResult(RESULT_CANCELED); - finish(); - } - } - }; - - mExportHelper.deleteKey(mDataUri, Id.type.public_key, returnHandler); + deleteKey(mDataUri); return true; } } return super.onOptionsItemSelected(item); } - private void loadData(Intent intent) { - if (intent.getData().equals(mDataUri)) { - Log.d(Constants.TAG, "Same URI, no need to load the data again!"); - return; - } - - mDataUri = intent.getData(); - if (mDataUri == null) { - Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); - finish(); - return; - } - - Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); - - mActionEncrypt.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - long keyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, mDataUri); - - long[] encryptionKeyIds = new long[]{keyId}; - Intent intent = new Intent(ViewKeyActivity.this, EncryptActivity.class); - intent.setAction(EncryptActivity.ACTION_ENCRYPT); - intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, 0); - } - }); - - mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0); - - mUserIds.setAdapter(mUserIdsAdapter); - // mUserIds.setEmptyView(findViewById(android.R.id.empty)); - // mUserIds.setClickable(true); - // mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { - // @Override - // public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { - // } - // }); - - mKeysAdapter = new ViewKeyKeysAdapter(this, null, 0); - - mKeys.setAdapter(mKeysAdapter); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); - getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); - getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this); - } - - static final String[] KEYRING_PROJECTION = new String[]{KeyRings._ID, KeyRings.MASTER_KEY_ID, - UserIds.USER_ID}; - static final int KEYRING_INDEX_ID = 0; - static final int KEYRING_INDEX_MASTER_KEY_ID = 1; - static final int KEYRING_INDEX_USER_ID = 2; - - static final String[] USER_IDS_PROJECTION = new String[]{UserIds._ID, UserIds.USER_ID, - UserIds.RANK,}; - // not the main user id - static final String USER_IDS_SELECTION = UserIds.RANK + " > 0 "; - static final String USER_IDS_SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; - - static final String[] KEYS_PROJECTION = new String[]{Keys._ID, Keys.KEY_ID, - Keys.IS_MASTER_KEY, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN, - Keys.CAN_ENCRYPT, Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT}; - static final String KEYS_SORT_ORDER = Keys.RANK + " ASC"; - static final int KEYS_INDEX_ID = 0; - static final int KEYS_INDEX_KEY_ID = 1; - static final int KEYS_INDEX_IS_MASTER_KEY = 2; - static final int KEYS_INDEX_ALGORITHM = 3; - static final int KEYS_INDEX_KEY_SIZE = 4; - static final int KEYS_INDEX_CAN_CERTIFY = 5; - static final int KEYS_INDEX_CAN_SIGN = 6; - static final int KEYS_INDEX_CAN_ENCRYPT = 7; - static final int KEYS_INDEX_CREATION = 8; - static final int KEYS_INDEX_EXPIRY = 9; - static final int KEYS_INDEX_FINGERPRINT = 10; - - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_KEYRING: { - Uri baseUri = mDataUri; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(this, baseUri, KEYRING_PROJECTION, null, null, null); - } - case LOADER_ID_USER_IDS: { - Uri baseUri = UserIds.buildUserIdsUri(mDataUri); - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null, - USER_IDS_SORT_ORDER); - } - case LOADER_ID_KEYS: { - Uri baseUri = Keys.buildKeysUri(mDataUri); - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(this, baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_KEYRING: - if (data.moveToFirst()) { - // get name, email, and comment from USER_ID - String[] mainUserId = PgpKeyHelper.splitUserId(data - .getString(KEYRING_INDEX_USER_ID)); - if (mainUserId[0] != null) { - setTitle(mainUserId[0]); - mName.setText(mainUserId[0]); - } else { - setTitle(R.string.user_id_no_name); - mName.setText(R.string.user_id_no_name); - } - mEmail.setText(mainUserId[1]); - mComment.setText(mainUserId[2]); - } - - break; - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(data); - break; - case LOADER_ID_KEYS: - // the first key here is our master key - if (data.moveToFirst()) { - // get key id from MASTER_KEY_ID - long keyId = data.getLong(KEYS_INDEX_KEY_ID); - - String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); - mKeyId.setText(keyIdStr); - - // get creation date from CREATION - if (data.isNull(KEYS_INDEX_CREATION)) { - mCreation.setText(R.string.none); - } else { - Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000); - - mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format( - creationDate)); - } - - // get expiry date from EXPIRY - if (data.isNull(KEYS_INDEX_EXPIRY)) { - mExpiry.setText(R.string.none); - } else { - Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000); - - mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format( - expiryDate)); - } - - String algorithmStr = PgpKeyHelper.getAlgorithmInfo( - data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE)); - mAlgorithm.setText(algorithmStr); - - byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT); - if (fingerprintBlob == null) { - // FALLBACK for old database entries - fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri); - } - String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); - fingerprint = fingerprint.replace(" ", "\n"); - - mFingerprint.setText(fingerprint); - } - - mKeysAdapter.swapCursor(data); - break; - - default: - break; - } - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader<Cursor> loader) { - switch (loader.getId()) { - case LOADER_ID_KEYRING: - // No resources need to be freed for this ID - break; - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(null); - break; - case LOADER_ID_KEYS: - mKeysAdapter.swapCursor(null); - break; - default: - break; - } - } - private void uploadToKeyserver(Uri dataUri) { Intent uploadIntent = new Intent(this, UploadKeyActivity.class); uploadIntent.setData(dataUri); @@ -398,21 +162,15 @@ public class ViewKeyActivity extends ActionBarActivity implements } Intent queryIntent = new Intent(this, ImportKeysActivity.class); - queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEY_SERVER); + queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); queryIntent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, updateKeyId); - // TODO: lookup?? - startActivityForResult(queryIntent, Id.request.look_up_key_id); - } - - private void signKey(Uri dataUri) { - Intent signIntent = new Intent(this, SignKeyActivity.class); - signIntent.setData(dataUri); - startActivity(signIntent); + // TODO: lookup with onactivityresult! + startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY); } private void shareKey(Uri dataUri, boolean fingerprintOnly) { - String content = null; + String content; if (fingerprintOnly) { byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri); String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false); @@ -465,4 +223,29 @@ public class ViewKeyActivity extends ActionBarActivity implements dialog.show(getSupportFragmentManager(), "shareNfcDialog"); } + private void deleteKey(Uri dataUri) { + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + Bundle returnData = message.getData(); + if (returnData != null + && returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) { + // we delete only this key, so MESSAGE_NOT_DELETED will solely contain this key + Toast.makeText(ViewKeyActivity.this, + getString(R.string.error_can_not_delete_contact) + + getResources().getQuantityString(R.plurals.error_can_not_delete_info, 1), + Toast.LENGTH_LONG).show(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + } + }; + + mExportHelper.deleteKey(dataUri, Id.type.public_key, returnHandler); + } + } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java index a2e3e4339..59037d9ff 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivityJB.java @@ -23,7 +23,6 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.ProviderHelper; import android.annotation.TargetApi; -import android.database.Cursor; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -35,12 +34,11 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.support.v4.app.LoaderManager; import android.widget.Toast; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback, - OnNdefPushCompleteCallback, LoaderManager.LoaderCallbacks<Cursor> { + OnNdefPushCompleteCallback { // NFC private NfcAdapter mNfcAdapter; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java new file mode 100644 index 000000000..36d3e6ace --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; + + +public class ViewKeyCertsFragment extends Fragment { + + public static final String ARG_DATA_URI = "uri"; + + private BootstrapButton mActionCertify; + + private Uri mDataUri; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false); + + mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + if (dataUri.equals(mDataUri)) { + Log.d(Constants.TAG, "Same URI, no need to load the data again!"); + return; + } + + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + mActionCertify.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + certifyKey(mDataUri); + } + }); + + } + + private void certifyKey(Uri dataUri) { + Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class); + signIntent.setData(dataUri); + startActivity(signIntent); + } + +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java new file mode 100644 index 000000000..495764eaf --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; + + +public class ViewKeyMainFragment extends Fragment implements + LoaderManager.LoaderCallbacks<Cursor>{ + + public static final String ARG_DATA_URI = "uri"; + + private TextView mName; + private TextView mEmail; + private TextView mComment; + private TextView mAlgorithm; + private TextView mKeyId; + private TextView mExpiry; + private TextView mCreation; + private TextView mFingerprint; + private BootstrapButton mActionEncrypt; + + private ListView mUserIds; + private ListView mKeys; + + private static final int LOADER_ID_KEYRING = 0; + private static final int LOADER_ID_USER_IDS = 1; + private static final int LOADER_ID_KEYS = 2; + + private ViewKeyUserIdsAdapter mUserIdsAdapter; + private ViewKeyKeysAdapter mKeysAdapter; + + private Uri mDataUri; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_main_fragment, container, false); + + mName = (TextView) view.findViewById(R.id.name); + mEmail = (TextView) view.findViewById(R.id.email); + mComment = (TextView) view.findViewById(R.id.comment); + mKeyId = (TextView) view.findViewById(R.id.key_id); + mAlgorithm = (TextView) view.findViewById(R.id.algorithm); + mCreation = (TextView) view.findViewById(R.id.creation); + mExpiry = (TextView) view.findViewById(R.id.expiry); + mFingerprint = (TextView) view.findViewById(R.id.fingerprint); + mUserIds = (ListView) view.findViewById(R.id.user_ids); + mKeys = (ListView) view.findViewById(R.id.keys); + mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); + getActivity().finish(); + return; + } + + loadData(dataUri); + } + + private void loadData(Uri dataUri) { + if (dataUri.equals(mDataUri)) { + Log.d(Constants.TAG, "Same URI, no need to load the data again!"); + return; + } + + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + + mActionEncrypt.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + encryptToContact(mDataUri); + } + }); + + mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0); + mUserIds.setAdapter(mUserIdsAdapter); + + mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0); + mKeys.setAdapter(mKeysAdapter); + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); + getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this); + } + + static final String[] KEYRING_PROJECTION = new String[]{KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.UserIds.USER_ID}; + static final int KEYRING_INDEX_ID = 0; + static final int KEYRING_INDEX_MASTER_KEY_ID = 1; + static final int KEYRING_INDEX_USER_ID = 2; + + static final String[] USER_IDS_PROJECTION = new String[]{KeychainContract.UserIds._ID, KeychainContract.UserIds.USER_ID, + KeychainContract.UserIds.RANK,}; + // not the main user id + static final String USER_IDS_SELECTION = KeychainContract.UserIds.RANK + " > 0 "; + static final String USER_IDS_SORT_ORDER = KeychainContract.UserIds.USER_ID + " COLLATE LOCALIZED ASC"; + + static final String[] KEYS_PROJECTION = new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID, + KeychainContract.Keys.IS_MASTER_KEY, KeychainContract.Keys.ALGORITHM, KeychainContract.Keys.KEY_SIZE, KeychainContract.Keys.CAN_CERTIFY, KeychainContract.Keys.CAN_SIGN, + KeychainContract.Keys.CAN_ENCRYPT, KeychainContract.Keys.CREATION, KeychainContract.Keys.EXPIRY, KeychainContract.Keys.FINGERPRINT}; + static final String KEYS_SORT_ORDER = KeychainContract.Keys.RANK + " ASC"; + static final int KEYS_INDEX_ID = 0; + static final int KEYS_INDEX_KEY_ID = 1; + static final int KEYS_INDEX_IS_MASTER_KEY = 2; + static final int KEYS_INDEX_ALGORITHM = 3; + static final int KEYS_INDEX_KEY_SIZE = 4; + static final int KEYS_INDEX_CAN_CERTIFY = 5; + static final int KEYS_INDEX_CAN_SIGN = 6; + static final int KEYS_INDEX_CAN_ENCRYPT = 7; + static final int KEYS_INDEX_CREATION = 8; + static final int KEYS_INDEX_EXPIRY = 9; + static final int KEYS_INDEX_FINGERPRINT = 10; + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_KEYRING: { + Uri baseUri = mDataUri; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, KEYRING_PROJECTION, null, null, null); + } + case LOADER_ID_USER_IDS: { + Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null, + USER_IDS_SORT_ORDER); + } + case LOADER_ID_KEYS: { + Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri); + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_KEYRING: + if (data.moveToFirst()) { + // get name, email, and comment from USER_ID + String[] mainUserId = PgpKeyHelper.splitUserId(data + .getString(KEYRING_INDEX_USER_ID)); + if (mainUserId[0] != null) { + getActivity().setTitle(mainUserId[0]); + mName.setText(mainUserId[0]); + } else { + getActivity().setTitle(R.string.user_id_no_name); + mName.setText(R.string.user_id_no_name); + } + mEmail.setText(mainUserId[1]); + mComment.setText(mainUserId[2]); + } + + break; + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(data); + break; + case LOADER_ID_KEYS: + // the first key here is our master key + if (data.moveToFirst()) { + // get key id from MASTER_KEY_ID + long keyId = data.getLong(KEYS_INDEX_KEY_ID); + + String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); + mKeyId.setText(keyIdStr); + + // get creation date from CREATION + if (data.isNull(KEYS_INDEX_CREATION)) { + mCreation.setText(R.string.none); + } else { + Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000); + + mCreation.setText(DateFormat.getDateFormat(getActivity().getApplicationContext()).format( + creationDate)); + } + + // get expiry date from EXPIRY + if (data.isNull(KEYS_INDEX_EXPIRY)) { + mExpiry.setText(R.string.none); + } else { + Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000); + + mExpiry.setText(DateFormat.getDateFormat(getActivity().getApplicationContext()).format( + expiryDate)); + } + + String algorithmStr = PgpKeyHelper.getAlgorithmInfo( + data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE)); + mAlgorithm.setText(algorithmStr); + + byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT); + if (fingerprintBlob == null) { + // FALLBACK for old database entries + fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri); + } + String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); + fingerprint = fingerprint.replace(" ", "\n"); + + mFingerprint.setText(fingerprint); + } + + mKeysAdapter.swapCursor(data); + break; + + default: + break; + } + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + switch (loader.getId()) { + case LOADER_ID_KEYRING: + // No resources need to be freed for this ID + break; + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(null); + break; + case LOADER_ID_KEYS: + mKeysAdapter.swapCursor(null); + break; + default: + break; + } + } + + + private void encryptToContact(Uri dataUri) { + long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri); + + long[] encryptionKeyIds = new long[]{keyId}; + Intent intent = new Intent(getActivity(), EncryptActivity.class); + intent.setAction(EncryptActivity.ACTION_ENCRYPT); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, 0); + } + + private void certifyKey(Uri dataUri) { + Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class); + signIntent.setData(dataUri); + startActivity(signIntent); + } + + +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index a850fc020..52186b662 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -90,16 +90,11 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { View view = mInflater.inflate(R.layout.import_keys_list_entry, null); TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.user_id_no_name); TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.no_key); TextView fingerprint = (TextView) view.findViewById(R.id.fingerprint); TextView algorithm = (TextView) view.findViewById(R.id.algorithm); - algorithm.setText(""); TextView status = (TextView) view.findViewById(R.id.status); - status.setText(""); // main user id String userId = entry.userIds.get(0); @@ -113,6 +108,8 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { mainUserId.setTextColor(Color.RED); } mainUserId.setText(userIdSplit[0]); + } else { + mainUserId.setText(R.string.user_id_no_name); } // email @@ -124,12 +121,18 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } keyId.setText(entry.hexKeyId); - fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); + + if (entry.fingerPrint != null) { + fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); + fingerprint.setVisibility(View.VISIBLE); + } else { + fingerprint.setVisibility(View.GONE); + } algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); if (entry.revoked) { - status.setText("revoked"); + status.setText(R.string.revoked); } else { status.setVisibility(View.GONE); } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java index 5094e8abd..4a7a9c93a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java @@ -48,7 +48,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { private boolean selected; - private byte[] bytes = new byte[] {}; + private byte[] bytes = new byte[]{}; public ImportKeysListEntry(ImportKeysListEntry b) { this.userIds = b.userIds; @@ -167,7 +167,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { this.revoked = pgpKeyRing.getPublicKey().isRevoked(); this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey() .getFingerprint(), true); - this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId); + this.hexKeyId = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength(); int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java index 753994450..b3bc39127 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListServerLoader.java @@ -79,7 +79,7 @@ public class ImportKeysListServerLoader extends AsyncTaskLoader<List<ImportKeysL } /** - * Query key server + * Query keyserver */ private void queryServer(String query, String keyServer) { HkpKeyServer server = new HkpKeyServer(keyServer); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java index f0e926655..257136cbd 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java @@ -23,10 +23,11 @@ import java.util.Set; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.util.Log; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; + import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; @@ -35,6 +36,7 @@ import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.RelativeLayout; import android.widget.TextView; /** @@ -44,6 +46,7 @@ public class KeyListPublicAdapter extends CursorAdapter implements StickyListHea private LayoutInflater mInflater; private int mSectionColumnIndex; private int mIndexUserId; + private int mIndexIsRevoked; @SuppressLint("UseSparseArrays") private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); @@ -66,48 +69,59 @@ public class KeyListPublicAdapter extends CursorAdapter implements StickyListHea /** * Get column indexes for performance reasons just once in constructor and swapCursor. For a * performance comparison see http://stackoverflow.com/a/17999582 - * + * * @param cursor */ private void initIndex(Cursor cursor) { if (cursor != null) { - mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); + mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); + mIndexIsRevoked = cursor.getColumnIndexOrThrow(KeychainContract.Keys.IS_REVOKED); } } /** * Bind cursor data to the item list view - * + * <p/> * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus * no ViewHolder is required here. */ @Override public void bindView(View view, Context context, Cursor cursor) { TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.user_id_no_name); TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); + TextView revoked = (TextView) view.findViewById(R.id.revoked); String userId = cursor.getString(mIndexUserId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); - if (userIdSplit[0] != null) { mainUserId.setText(userIdSplit[0]); + } else { + mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { mainUserIdRest.setText(userIdSplit[1]); + mainUserIdRest.setVisibility(View.VISIBLE); + } else { + mainUserIdRest.setVisibility(View.GONE); + } + + boolean isRevoked = cursor.getInt(mIndexIsRevoked) > 0; + if (isRevoked) { + revoked.setVisibility(View.VISIBLE); + } else { + revoked.setVisibility(View.GONE); } } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_item, null); + return mInflater.inflate(R.layout.key_list_public_item, null); } /** * Creates a new header view and binds the section headers to it. It uses the ViewHolder * pattern. Most functionality is similar to getView() from Android's CursorAdapter. - * + * <p/> * NOTE: The variables mDataValid and mCursor are available due to the super class * CursorAdapter. */ @@ -159,8 +173,7 @@ public class KeyListPublicAdapter extends CursorAdapter implements StickyListHea } // return the first character of the name as ID because this is what - // headers private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, - // Boolean>();are based upon + // headers are based upon String userId = mCursor.getString(mSectionColumnIndex); if (userId != null && userId.length() > 0) { return userId.subSequence(0, 1).charAt(0); @@ -173,7 +186,9 @@ public class KeyListPublicAdapter extends CursorAdapter implements StickyListHea TextView text; } - /** -------------------------- MULTI-SELECTION METHODS -------------- */ + /** + * -------------------------- MULTI-SELECTION METHODS -------------- + */ public void setNewSelection(int position, boolean value) { mSelection.put(position, value); notifyDataSetChanged(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java index ce9b48bff..11d1e8c17 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java @@ -71,24 +71,26 @@ public class KeyListSecretAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.user_id_no_name); TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); String userId = cursor.getString(mIndexUserId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { mainUserId.setText(userIdSplit[0]); + } else { + mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { mainUserIdRest.setText(userIdSplit[1]); + } else { + mainUserIdRest.setText(""); } } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_item, null); + return mInflater.inflate(R.layout.key_list_secret_item, null); } /** -------------------------- MULTI-SELECTION METHODS -------------- */ diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 7e01faf9b..d44dd5890 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -96,27 +96,31 @@ public class SelectKeyCursorAdapter extends CursorAdapter { boolean valid = cursor.getInt(mIndexProjectionValid) > 0; TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.user_id_no_name); TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.no_key); TextView status = (TextView) view.findViewById(R.id.status); - status.setText(R.string.unknown_status); String userId = cursor.getString(mIndexUserId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[0] != null) { mainUserId.setText(userIdSplit[0]); + } else { + mainUserId.setText(R.string.user_id_no_name); } if (userIdSplit[1] != null) { mainUserIdRest.setText(userIdSplit[1]); + } else { + mainUserIdRest.setText(""); } + // TODO: needed to key id to no? + keyId.setText(R.string.no_key); long masterKeyId = cursor.getLong(mIndexMasterKeyId); keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId)); + // TODO: needed to set unknown_status? + status.setText(R.string.unknown_status); if (valid) { if (mKeyType == Id.type.public_key) { status.setText(R.string.can_encrypt); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java new file mode 100644 index 000000000..924a70897 --- /dev/null +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/TabsAdapter.java @@ -0,0 +1,84 @@ +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; + +import java.util.ArrayList; + +public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, + ViewPager.OnPageChangeListener { + private final Context mContext; + private final ActionBar mActionBar; + private final ViewPager mViewPager; + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + + static final class TabInfo { + private final Class<?> clss; + private final Bundle args; + + TabInfo(Class<?> _class, Bundle _args) { + clss = _class; + args = _args; + } + } + + public TabsAdapter(ActionBarActivity activity, ViewPager pager) { + super(activity.getSupportFragmentManager()); + mContext = activity; + mActionBar = activity.getSupportActionBar(); + mViewPager = pager; + mViewPager.setAdapter(this); + mViewPager.setOnPageChangeListener(this); + } + + public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args, boolean selected) { + TabInfo info = new TabInfo(clss, args); + tab.setTag(info); + tab.setTabListener(this); + mTabs.add(info); + mActionBar.addTab(tab, selected); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + TabInfo info = mTabs.get(position); + return Fragment.instantiate(mContext, info.clss.getName(), info.args); + } + + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + public void onPageSelected(int position) { + mActionBar.setSelectedNavigationItem(position); + } + + public void onPageScrollStateChanged(int state) { + } + + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { + Object tag = tab.getTag(); + for (int i = 0; i < mTabs.size(); i++) { + if (mTabs.get(i) == tag) { + mViewPager.setCurrentItem(i); + } + } + } + + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { + } + + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { + } +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java index d5162c403..54c7eb60e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -76,38 +76,39 @@ public class ViewKeyKeysAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { + TextView keyId = (TextView) view.findViewById(R.id.keyId); + TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); + ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); + ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); + ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)); String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm), cursor.getInt(mIndexKeySize)); - TextView keyId = (TextView) view.findViewById(R.id.keyId); keyId.setText(keyIdStr); - TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); keyDetails.setText("(" + algorithmStr + ")"); - ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); if (cursor.getInt(mIndexIsMasterKey) != 1) { masterKeyIcon.setVisibility(View.INVISIBLE); } else { masterKeyIcon.setVisibility(View.VISIBLE); } - ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); if (cursor.getInt(mIndexCanCertify) != 1) { certifyIcon.setVisibility(View.GONE); } else { certifyIcon.setVisibility(View.VISIBLE); } - ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); if (cursor.getInt(mIndexCanEncrypt) != 1) { encryptIcon.setVisibility(View.GONE); } else { encryptIcon.setVisibility(View.VISIBLE); } - ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); if (cursor.getInt(mIndexCanSign) != 1) { signIcon.setVisibility(View.GONE); } else { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 109bfe929..3c44bff81 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -23,12 +23,16 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.util.Log; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; @@ -36,6 +40,8 @@ import android.os.RemoteException; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import java.util.ArrayList; + public class DeleteKeyDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file"; @@ -43,13 +49,15 @@ public class DeleteKeyDialogFragment extends DialogFragment { public static final int MESSAGE_OKAY = 1; + public static final String MESSAGE_NOT_DELETED = "not_deleted"; + private Messenger mMessenger; /** * Creates new instance of this delete file dialog fragment */ public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds, - int keyType) { + int keyType) { DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); Bundle args = new Bundle(); @@ -77,20 +85,13 @@ public class DeleteKeyDialogFragment extends DialogFragment { builder.setTitle(R.string.warning); if (keyRingRowIds.length == 1) { - // TODO: better way to do this? - String userId = activity.getString(R.string.user_id_no_name); - + Uri dataUri; if (keyType == Id.type.public_key) { - PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity, - keyRingRowIds[0]); - userId = PgpKeyHelper.getMainUserIdSafe(activity, - PgpKeyHelper.getMasterKey(keyRing)); + dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowIds[0])); } else { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity, - keyRingRowIds[0]); - userId = PgpKeyHelper.getMainUserIdSafe(activity, - PgpKeyHelper.getMasterKey(keyRing)); + dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowIds[0])); } + String userId = ProviderHelper.getUserId(activity, dataUri); builder.setMessage(getString( keyType == Id.type.public_key ? R.string.key_deletion_confirmation @@ -104,9 +105,61 @@ public class DeleteKeyDialogFragment extends DialogFragment { @Override public void onClick(DialogInterface dialog, int id) { + ArrayList<String> notDeleted = new ArrayList<String>(); + if (keyType == Id.type.public_key) { - for (long keyRowId : keyRingRowIds) { - ProviderHelper.deletePublicKeyRing(activity, keyRowId); + Uri queryUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(); + String[] projection = new String[]{ + KeychainContract.KeyRings._ID, // 0 + KeychainContract.KeyRings.MASTER_KEY_ID, // 1 + KeychainContract.UserIds.USER_ID // 2 + }; + + // make selection with all entries where _ID is one of the given row ids + String selection = KeychainDatabase.Tables.KEY_RINGS + "." + + KeychainContract.KeyRings._ID + " IN("; + String selectionIDs = ""; + for (int i = 0; i < keyRingRowIds.length; i++) { + selectionIDs += "'" + String.valueOf(keyRingRowIds[i]) + "'"; + if (i+1 < keyRingRowIds.length) + selectionIDs += ","; + } + selection += selectionIDs + ")"; + + Cursor cursor = activity.getContentResolver().query(queryUri, projection, + selection, null, null); + + long rowId; + long masterKeyId; + String userId; + try { + while (cursor != null && cursor.moveToNext()) { + rowId = cursor.getLong(0); + masterKeyId = cursor.getLong(1); + userId = cursor.getString(2); + + Log.d(Constants.TAG, "rowId: " + rowId + ", masterKeyId: " + masterKeyId + + ", userId: " + userId); + + // check if a corresponding secret key exists... + Cursor secretCursor = activity.getContentResolver().query( + KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(String.valueOf(masterKeyId)), + null, null, null, null + ); + if (secretCursor != null && secretCursor.getCount() > 0) { + notDeleted.add(userId); + } else { + // it is okay to delete this key, no secret key found! + ProviderHelper.deletePublicKeyRing(activity, rowId); + } + if (secretCursor != null) { + secretCursor.close(); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } } } else { for (long keyRowId : keyRingRowIds) { @@ -116,7 +169,13 @@ public class DeleteKeyDialogFragment extends DialogFragment { dismiss(); - sendMessageToHandler(MESSAGE_OKAY); + if (notDeleted.size() > 0) { + Bundle data = new Bundle(); + data.putStringArrayList(MESSAGE_NOT_DELETED, notDeleted); + sendMessageToHandler(MESSAGE_OKAY, data); + } else { + sendMessageToHandler(MESSAGE_OKAY, null); + } } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -131,13 +190,15 @@ public class DeleteKeyDialogFragment extends DialogFragment { /** * Send message back to handler which is initialized in a activity - * - * @param what - * Message integer you want to send + * + * @param what Message integer you want to send */ - private void sendMessageToHandler(Integer what) { + private void sendMessageToHandler(Integer what, Bundle data) { Message msg = Message.obtain(); msg.what = what; + if (data != null) { + msg.setData(data); + } try { mMessenger.send(msg); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java deleted file mode 100644 index a0592285f..000000000 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/LookupUnknownKeyDialogFragment.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * 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 <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui.dialog; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.ui.ImportKeysActivity; -import org.sufficientlysecure.keychain.util.Log; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.v4.app.DialogFragment; - -public class LookupUnknownKeyDialogFragment extends DialogFragment { - private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_UNKNOWN_KEY_ID = "unknown_key_id"; - - public static final int MESSAGE_OKAY = 1; - public static final int MESSAGE_CANCEL = 2; - - private Messenger mMessenger; - - /** - * Creates new instance of this dialog fragment - * - * @param messenger - * @param unknownKeyId - * @return - */ - public static LookupUnknownKeyDialogFragment newInstance(Messenger messenger, long unknownKeyId) { - LookupUnknownKeyDialogFragment frag = new LookupUnknownKeyDialogFragment(); - Bundle args = new Bundle(); - args.putLong(ARG_UNKNOWN_KEY_ID, unknownKeyId); - args.putParcelable(ARG_MESSENGER, messenger); - - frag.setArguments(args); - - return frag; - } - - /** - * Creates dialog - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); - - final long unknownKeyId = getArguments().getLong(ARG_UNKNOWN_KEY_ID); - mMessenger = getArguments().getParcelable(ARG_MESSENGER); - - AlertDialog.Builder alert = new AlertDialog.Builder(activity); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.title_unknown_signature_key); - alert.setMessage(getString(R.string.lookup_unknown_key, - PgpKeyHelper.convertKeyIdToHex(unknownKeyId))); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); - - sendMessageToHandler(MESSAGE_OKAY); - - Intent intent = new Intent(activity, ImportKeysActivity.class); - intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEY_SERVER); - intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId); - startActivityForResult(intent, Id.request.look_up_key_id); - } - }); - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); - - sendMessageToHandler(MESSAGE_CANCEL); - } - }); - alert.setCancelable(true); - alert.setOnCancelListener(new OnCancelListener() { - - @Override - public void onCancel(DialogInterface dialog) { - sendMessageToHandler(MESSAGE_CANCEL); - } - }); - - return alert.create(); - } - - /** - * Send message back to handler which is initialized in a activity - * - * @param what - * Message integer you want to send - */ - private void sendMessageToHandler(Integer what) { - Message msg = Message.obtain(); - msg.what = what; - - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); - } catch (NullPointerException e) { - Log.w(Constants.TAG, "Messenger is null!", e); - } - } -}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index 05e52fb47..61fe13ffb 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -50,6 +50,13 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import android.text.Html; +/** + * TODO: + * rewrite to use machine readable output. + * <p/> + * see http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5 + * https://github.com/openpgp-keychain/openpgp-keychain/issues/259 + */ public class HkpKeyServer extends KeyServer { private static class HttpError extends Exception { private static final long serialVersionUID = 1718783705229428893L; @@ -181,8 +188,8 @@ public class HkpKeyServer extends KeyServer { ImportKeysListEntry info = new ImportKeysListEntry(); info.bitStrength = Integer.parseInt(matcher.group(1)); info.algorithm = matcher.group(2); + info.hexKeyId = "0x" + matcher.group(3); info.keyId = PgpKeyHelper.convertHexToKeyId(matcher.group(3)); - info.fingerPrint = PgpKeyHelper.convertKeyIdToHex(info.keyId); String chunks[] = matcher.group(4).split("-"); GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); |