master merge
This commit is contained in:
@@ -78,7 +78,7 @@ public final class Id {
|
||||
public static final int filename = 0x00007003;
|
||||
// public static final int output_filename = 0x00007004;
|
||||
public static final int key_server_preference = 0x00007005;
|
||||
public static final int look_up_key_id = 0x00007006;
|
||||
// public static final int look_up_key_id = 0x00007006;
|
||||
public static final int export_to_server = 0x00007007;
|
||||
public static final int import_from_qr_code = 0x00007008;
|
||||
public static final int sign_key = 0x00007009;
|
||||
|
||||
@@ -0,0 +1,782 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* This class uses a Builder pattern!
|
||||
*/
|
||||
public class PgpDecryptVerify {
|
||||
private Context context;
|
||||
private InputData data;
|
||||
private OutputStream outStream;
|
||||
|
||||
private ProgressDialogUpdater progress;
|
||||
boolean assumeSymmetric;
|
||||
String passphrase;
|
||||
|
||||
private PgpDecryptVerify(Builder builder) {
|
||||
// private Constructor can only be called from Builder
|
||||
this.context = builder.context;
|
||||
this.data = builder.data;
|
||||
this.outStream = builder.outStream;
|
||||
|
||||
this.progress = builder.progress;
|
||||
this.assumeSymmetric = builder.assumeSymmetric;
|
||||
this.passphrase = builder.passphrase;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
// mandatory parameter
|
||||
private Context context;
|
||||
private InputData data;
|
||||
private OutputStream outStream;
|
||||
|
||||
// optional
|
||||
private ProgressDialogUpdater progress = null;
|
||||
private boolean assumeSymmetric = false;
|
||||
private String passphrase = "";
|
||||
|
||||
public Builder(Context context, InputData data, OutputStream outStream) {
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
this.outStream = outStream;
|
||||
}
|
||||
|
||||
public Builder progress(ProgressDialogUpdater progress) {
|
||||
this.progress = progress;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder assumeSymmetric(boolean assumeSymmetric) {
|
||||
this.assumeSymmetric = assumeSymmetric;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder passphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpDecryptVerify build() {
|
||||
return new PgpDecryptVerify(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int message, int current, int total) {
|
||||
if (progress != null) {
|
||||
progress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int current, int total) {
|
||||
if (progress != null) {
|
||||
progress.setProgress(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasSymmetricEncryption(Context context, InputStream inputStream)
|
||||
throws PgpGeneralException, IOException {
|
||||
InputStream in = PGPUtil.getDecoderStream(inputStream);
|
||||
PGPObjectFactory pgpF = new PGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
// the first object might be a PGP marker packet.
|
||||
if (o instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) o;
|
||||
} else {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
|
||||
if (enc == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
|
||||
}
|
||||
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPBEEncryptedData) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts and/or verifies data based on parameters of class
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws PgpGeneralException
|
||||
* @throws PGPException
|
||||
* @throws SignatureException
|
||||
*/
|
||||
public Bundle execute()
|
||||
throws IOException, PgpGeneralException, PGPException, SignatureException {
|
||||
|
||||
// automatically works with ascii armor input and binary
|
||||
InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
|
||||
if (in instanceof ArmoredInputStream) {
|
||||
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
|
||||
if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn);
|
||||
}
|
||||
// else: ascii armored encryption! go on...
|
||||
}
|
||||
|
||||
return decryptVerify(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt and/or verifies binary or ascii armored pgp
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws PgpGeneralException
|
||||
* @throws PGPException
|
||||
* @throws SignatureException
|
||||
*/
|
||||
private Bundle decryptVerify(InputStream in)
|
||||
throws IOException, PgpGeneralException, PGPException, SignatureException {
|
||||
Bundle returnData = new Bundle();
|
||||
|
||||
PGPObjectFactory pgpF = new PGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
int currentProgress = 0;
|
||||
updateProgress(R.string.progress_reading_data, currentProgress, 100);
|
||||
|
||||
if (o instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) o;
|
||||
} else {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
|
||||
if (enc == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
|
||||
}
|
||||
|
||||
InputStream clear;
|
||||
PGPEncryptedData encryptedData;
|
||||
|
||||
currentProgress += 5;
|
||||
|
||||
// TODO: currently we always only look at the first known key or symmetric encryption,
|
||||
// there might be more...
|
||||
if (assumeSymmetric) {
|
||||
PGPPBEEncryptedData pbe = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
// find secret key
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPBEEncryptedData) {
|
||||
pbe = (PGPPBEEncryptedData) obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pbe == null) {
|
||||
throw new PgpGeneralException(
|
||||
context.getString(R.string.error_no_symmetric_encryption_packet));
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||
|
||||
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
|
||||
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
|
||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passphrase.toCharArray());
|
||||
|
||||
clear = pbe.getDataStream(decryptorFactory);
|
||||
|
||||
encryptedData = pbe;
|
||||
currentProgress += 5;
|
||||
} else {
|
||||
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
||||
|
||||
PGPPublicKeyEncryptedData pbe = null;
|
||||
PGPSecretKey secretKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
// find secret key
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID());
|
||||
if (secretKey != null) {
|
||||
pbe = encData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
|
||||
currentProgress += 5;
|
||||
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
||||
PGPPrivateKey privateKey = null;
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passphrase.toCharArray());
|
||||
privateKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
throw new PGPException(context.getString(R.string.error_wrong_passphrase));
|
||||
}
|
||||
if (privateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
context.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
currentProgress += 5;
|
||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||
|
||||
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
|
||||
|
||||
clear = pbe.getDataStream(decryptorFactory);
|
||||
|
||||
encryptedData = pbe;
|
||||
currentProgress += 5;
|
||||
}
|
||||
|
||||
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
|
||||
Object dataChunk = plainFact.nextObject();
|
||||
PGPOnePassSignature signature = null;
|
||||
PGPPublicKey signatureKey = null;
|
||||
int signatureIndex = -1;
|
||||
|
||||
if (dataChunk instanceof PGPCompressedData) {
|
||||
updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
|
||||
|
||||
PGPObjectFactory fact = new PGPObjectFactory(
|
||||
((PGPCompressedData) dataChunk).getDataStream());
|
||||
dataChunk = fact.nextObject();
|
||||
plainFact = fact;
|
||||
currentProgress += 10;
|
||||
}
|
||||
|
||||
long signatureKeyId = 0;
|
||||
if (dataChunk instanceof PGPOnePassSignatureList) {
|
||||
updateProgress(R.string.progress_processing_signature, currentProgress, 100);
|
||||
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
|
||||
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
signature = sigList.get(i);
|
||||
signatureKey = ProviderHelper
|
||||
.getPGPPublicKeyByKeyId(context, signature.getKeyID());
|
||||
if (signatureKeyId == 0) {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
}
|
||||
if (signatureKey == null) {
|
||||
signature = null;
|
||||
} else {
|
||||
signatureIndex = i;
|
||||
signatureKeyId = signature.getKeyID();
|
||||
String userId = null;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
||||
context, signatureKeyId);
|
||||
if (signKeyRing != null) {
|
||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
||||
}
|
||||
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
|
||||
|
||||
if (signature != null) {
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
signature.init(contentVerifierBuilderProvider, signatureKey);
|
||||
} else {
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
|
||||
}
|
||||
|
||||
dataChunk = plainFact.nextObject();
|
||||
currentProgress += 10;
|
||||
}
|
||||
|
||||
if (dataChunk instanceof PGPSignatureList) {
|
||||
dataChunk = plainFact.nextObject();
|
||||
}
|
||||
|
||||
if (dataChunk instanceof PGPLiteralData) {
|
||||
updateProgress(R.string.progress_decrypting, currentProgress, 100);
|
||||
|
||||
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
|
||||
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
InputStream dataIn = literalData.getInputStream();
|
||||
|
||||
int startProgress = currentProgress;
|
||||
int endProgress = 100;
|
||||
if (signature != null) {
|
||||
endProgress = 90;
|
||||
} else if (encryptedData.isIntegrityProtected()) {
|
||||
endProgress = 95;
|
||||
}
|
||||
|
||||
int n;
|
||||
// TODO: progress calculation is broken here! Try to rework it based on commented code!
|
||||
// int progress = 0;
|
||||
long startPos = data.getStreamPosition();
|
||||
while ((n = dataIn.read(buffer)) > 0) {
|
||||
outStream.write(buffer, 0, n);
|
||||
// progress += n;
|
||||
if (signature != null) {
|
||||
try {
|
||||
signature.update(buffer, 0, n);
|
||||
} catch (SignatureException e) {
|
||||
returnData
|
||||
.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
|
||||
signature = null;
|
||||
}
|
||||
}
|
||||
// TODO: dead code?!
|
||||
// unknown size, but try to at least have a moving, slowing down progress bar
|
||||
// currentProgress = startProgress + (endProgress - startProgress) * progress
|
||||
// / (progress + 100000);
|
||||
if (data.getSize() - startPos == 0) {
|
||||
currentProgress = endProgress;
|
||||
} else {
|
||||
currentProgress = (int) (startProgress + (endProgress - startProgress)
|
||||
* (data.getStreamPosition() - startPos) / (data.getSize() - startPos));
|
||||
}
|
||||
updateProgress(currentProgress, 100);
|
||||
}
|
||||
|
||||
if (signature != null) {
|
||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||
|
||||
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
|
||||
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
||||
|
||||
// these are not cleartext signatures!
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
|
||||
|
||||
//Now check binding signatures
|
||||
boolean keyBinding_isok = verifyKeyBinding(context, messageSignature, signatureKey);
|
||||
boolean sig_isok = signature.verify(messageSignature);
|
||||
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test if this integrity really check works!
|
||||
if (encryptedData.isIntegrityProtected()) {
|
||||
updateProgress(R.string.progress_verifying_integrity, 95, 100);
|
||||
|
||||
if (encryptedData.verify()) {
|
||||
// passed
|
||||
Log.d(Constants.TAG, "Integrity verification: success!");
|
||||
} else {
|
||||
// failed
|
||||
Log.d(Constants.TAG, "Integrity verification: failed!");
|
||||
throw new PgpGeneralException(context.getString(R.string.error_integrity_check_failed));
|
||||
}
|
||||
} else {
|
||||
// no integrity check
|
||||
Log.e(Constants.TAG, "Encrypted data was not integrity protected!");
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method verifies cleartext signatures
|
||||
* as defined in http://tools.ietf.org/html/rfc4880#section-7
|
||||
* <p/>
|
||||
* The method is heavily based on
|
||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws PgpGeneralException
|
||||
* @throws PGPException
|
||||
* @throws SignatureException
|
||||
*/
|
||||
private Bundle verifyCleartextSignature(ArmoredInputStream aIn)
|
||||
throws IOException, PgpGeneralException, PGPException, SignatureException {
|
||||
Bundle returnData = new Bundle();
|
||||
// cleartext signatures are never encrypted ;)
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, true);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
updateProgress(R.string.progress_done, 0, 100);
|
||||
|
||||
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
|
||||
int lookAhead = readInputLine(lineOut, aIn);
|
||||
byte[] lineSep = getLineSeparator();
|
||||
|
||||
byte[] line = lineOut.toByteArray();
|
||||
out.write(line, 0, getLengthWithoutSeparator(line));
|
||||
out.write(lineSep);
|
||||
|
||||
while (lookAhead != -1 && aIn.isClearText()) {
|
||||
lookAhead = readInputLine(lineOut, lookAhead, aIn);
|
||||
line = lineOut.toByteArray();
|
||||
out.write(line, 0, getLengthWithoutSeparator(line));
|
||||
out.write(lineSep);
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
byte[] clearText = out.toByteArray();
|
||||
outStream.write(clearText);
|
||||
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
|
||||
|
||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
|
||||
|
||||
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
|
||||
if (sigList == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_corrupt_data));
|
||||
}
|
||||
PGPSignature signature = null;
|
||||
long signatureKeyId = 0;
|
||||
PGPPublicKey signatureKey = null;
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
signature = sigList.get(i);
|
||||
signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID());
|
||||
if (signatureKeyId == 0) {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
}
|
||||
|
||||
if (signatureKey == null) {
|
||||
signature = null;
|
||||
} else {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
String userId = null;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
|
||||
signatureKeyId);
|
||||
if (signKeyRing != null) {
|
||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
||||
}
|
||||
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
|
||||
|
||||
if (signature == null) {
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
signature.init(contentVerifierBuilderProvider, signatureKey);
|
||||
|
||||
InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
|
||||
|
||||
lookAhead = readInputLine(lineOut, sigIn);
|
||||
|
||||
processLine(signature, lineOut.toByteArray());
|
||||
|
||||
if (lookAhead != -1) {
|
||||
do {
|
||||
lookAhead = readInputLine(lineOut, lookAhead, sigIn);
|
||||
|
||||
signature.update((byte) '\r');
|
||||
signature.update((byte) '\n');
|
||||
|
||||
processLine(signature, lineOut.toByteArray());
|
||||
} while (lookAhead != -1);
|
||||
}
|
||||
|
||||
boolean sig_isok = signature.verify();
|
||||
|
||||
//Now check binding signatures
|
||||
boolean keyBinding_isok = verifyKeyBinding(context, signature, signatureKey);
|
||||
|
||||
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) {
|
||||
long signatureKeyId = signature.getKeyID();
|
||||
boolean keyBinding_isok = false;
|
||||
String userId = null;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
|
||||
signatureKeyId);
|
||||
PGPPublicKey mKey = null;
|
||||
if (signKeyRing != null) {
|
||||
mKey = PgpKeyHelper.getMasterKey(signKeyRing);
|
||||
}
|
||||
if (signature.getKeyID() != mKey.getKeyID()) {
|
||||
keyBinding_isok = verifyKeyBinding(mKey, signatureKey);
|
||||
} else { //if the key used to make the signature was the master key, no need to check binding sigs
|
||||
keyBinding_isok = true;
|
||||
}
|
||||
return keyBinding_isok;
|
||||
}
|
||||
|
||||
private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
||||
boolean subkeyBinding_isok = false;
|
||||
boolean tmp_subkeyBinding_isok = false;
|
||||
boolean primkeyBinding_isok = false;
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
Iterator<PGPSignature> itr = signingPublicKey.getSignatures();
|
||||
|
||||
subkeyBinding_isok = false;
|
||||
tmp_subkeyBinding_isok = false;
|
||||
primkeyBinding_isok = false;
|
||||
while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong?
|
||||
//gpg has an invalid subkey binding error on key import I think, but doesn't shout
|
||||
//about keys without subkey signing. Can't get it to import a slightly broken one
|
||||
//either, so we will err on bad subkey binding here.
|
||||
PGPSignature sig = itr.next();
|
||||
if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
|
||||
//check and if ok, check primary key binding.
|
||||
try {
|
||||
sig.init(contentVerifierBuilderProvider, masterPublicKey);
|
||||
tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey);
|
||||
} catch (PGPException e) {
|
||||
continue;
|
||||
} catch (SignatureException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tmp_subkeyBinding_isok)
|
||||
subkeyBinding_isok = true;
|
||||
if (tmp_subkeyBinding_isok) {
|
||||
primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey);
|
||||
if (primkeyBinding_isok)
|
||||
break;
|
||||
primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey);
|
||||
if (primkeyBinding_isok)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (subkeyBinding_isok & primkeyBinding_isok);
|
||||
}
|
||||
|
||||
private static boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
||||
boolean primkeyBinding_isok = false;
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureList eSigList;
|
||||
|
||||
if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) {
|
||||
try {
|
||||
eSigList = Pkts.getEmbeddedSignatures();
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} catch (PGPException e) {
|
||||
return false;
|
||||
}
|
||||
for (int j = 0; j < eSigList.size(); ++j) {
|
||||
PGPSignature emSig = eSigList.get(j);
|
||||
if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
|
||||
try {
|
||||
emSig.init(contentVerifierBuilderProvider, signingPublicKey);
|
||||
primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey);
|
||||
if (primkeyBinding_isok)
|
||||
break;
|
||||
} catch (PGPException e) {
|
||||
continue;
|
||||
} catch (SignatureException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return primkeyBinding_isok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||
*
|
||||
* @param sig
|
||||
* @param line
|
||||
* @throws SignatureException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void processLine(PGPSignature sig, byte[] line)
|
||||
throws SignatureException, IOException {
|
||||
int length = getLengthWithoutWhiteSpace(line);
|
||||
if (length > 0) {
|
||||
sig.update(line, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
|
||||
throws IOException {
|
||||
bOut.reset();
|
||||
|
||||
int lookAhead = -1;
|
||||
int ch;
|
||||
|
||||
while ((ch = fIn.read()) >= 0) {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
|
||||
throws IOException {
|
||||
bOut.reset();
|
||||
|
||||
int ch = lookAhead;
|
||||
|
||||
do {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
} while ((ch = fIn.read()) >= 0);
|
||||
|
||||
if (ch < 0) {
|
||||
lookAhead = -1;
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
|
||||
throws IOException {
|
||||
int lookAhead = fIn.read();
|
||||
|
||||
if (lastCh == '\r' && lookAhead == '\n') {
|
||||
bOut.write(lookAhead);
|
||||
lookAhead = fIn.read();
|
||||
}
|
||||
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int getLengthWithoutSeparator(byte[] line) {
|
||||
int end = line.length - 1;
|
||||
|
||||
while (end >= 0 && isLineEnding(line[end])) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
private static boolean isLineEnding(byte b) {
|
||||
return b == '\r' || b == '\n';
|
||||
}
|
||||
|
||||
private static int getLengthWithoutWhiteSpace(byte[] line) {
|
||||
int end = line.length - 1;
|
||||
|
||||
while (end >= 0 && isWhiteSpace(line[end])) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpace(byte b) {
|
||||
return b == '\r' || b == '\n' || b == '\t' || b == ' ';
|
||||
}
|
||||
|
||||
private static byte[] getLineSeparator() {
|
||||
String nl = System.getProperty("line.separator");
|
||||
byte[] nlBytes = new byte[nl.length()];
|
||||
|
||||
for (int i = 0; i != nlBytes.length; i++) {
|
||||
nlBytes[i] = (byte) nl.charAt(i);
|
||||
}
|
||||
|
||||
return nlBytes;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import android.content.Context;
|
||||
|
||||
public class PgpKeyHelper {
|
||||
|
||||
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
||||
|
||||
public static Date getCreationDate(PGPPublicKey key) {
|
||||
return key.getCreationTime();
|
||||
}
|
||||
@@ -591,8 +593,7 @@ public class PgpKeyHelper {
|
||||
* "Max Mustermann (this is a comment)"
|
||||
* "Max Mustermann [this is nothing]"
|
||||
*/
|
||||
Pattern withComment = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
Matcher matcher = USER_ID_PATTERN.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
result[0] = matcher.group(1);
|
||||
result[1] = matcher.group(3);
|
||||
|
||||
@@ -504,7 +504,7 @@ public class PgpKeyOperation {
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
public PGPPublicKeyRing signKey(long masterKeyId, long pubKeyId, String passphrase)
|
||||
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase)
|
||||
throws PgpGeneralException, PGPException, SignatureException {
|
||||
if (passphrase == null) {
|
||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||
@@ -512,14 +512,14 @@ public class PgpKeyOperation {
|
||||
PGPPublicKeyRing pubring = ProviderHelper
|
||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||
|
||||
PGPSecretKey signingKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (signingKey == null) {
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
@@ -527,7 +527,7 @@ public class PgpKeyOperation {
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
signingKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
||||
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class uses a Builder pattern!
|
||||
*/
|
||||
public class PgpSignEncrypt {
|
||||
private Context context;
|
||||
private InputData data;
|
||||
private OutputStream outStream;
|
||||
|
||||
private ProgressDialogUpdater progress;
|
||||
private boolean enableAsciiArmorOutput;
|
||||
private int compressionId;
|
||||
private long[] encryptionKeyIds;
|
||||
private String encryptionPassphrase;
|
||||
private int symmetricEncryptionAlgorithm;
|
||||
private long signatureKeyId;
|
||||
private int signatureHashAlgorithm;
|
||||
private boolean signatureForceV3;
|
||||
private String signaturePassphrase;
|
||||
|
||||
private PgpSignEncrypt(Builder builder) {
|
||||
// private Constructor can only be called from Builder
|
||||
this.context = builder.context;
|
||||
this.data = builder.data;
|
||||
this.outStream = builder.outStream;
|
||||
|
||||
this.progress = builder.progress;
|
||||
this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput;
|
||||
this.compressionId = builder.compressionId;
|
||||
this.encryptionKeyIds = builder.encryptionKeyIds;
|
||||
this.encryptionPassphrase = builder.encryptionPassphrase;
|
||||
this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm;
|
||||
this.signatureKeyId = builder.signatureKeyId;
|
||||
this.signatureHashAlgorithm = builder.signatureHashAlgorithm;
|
||||
this.signatureForceV3 = builder.signatureForceV3;
|
||||
this.signaturePassphrase = builder.signaturePassphrase;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
// mandatory parameter
|
||||
private Context context;
|
||||
private InputData data;
|
||||
private OutputStream outStream;
|
||||
|
||||
// optional
|
||||
private ProgressDialogUpdater progress = null;
|
||||
private boolean enableAsciiArmorOutput = false;
|
||||
private int compressionId = Id.choice.compression.none;
|
||||
private long[] encryptionKeyIds = new long[0];
|
||||
private String encryptionPassphrase = null;
|
||||
private int symmetricEncryptionAlgorithm = 0;
|
||||
private long signatureKeyId = Id.key.none;
|
||||
private int signatureHashAlgorithm = 0;
|
||||
private boolean signatureForceV3 = false;
|
||||
private String signaturePassphrase = null;
|
||||
|
||||
public Builder(Context context, InputData data, OutputStream outStream) {
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
this.outStream = outStream;
|
||||
}
|
||||
|
||||
public Builder progress(ProgressDialogUpdater progress) {
|
||||
this.progress = progress;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
this.enableAsciiArmorOutput = enableAsciiArmorOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder compressionId(int compressionId) {
|
||||
this.compressionId = compressionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder encryptionKeyIds(long[] encryptionKeyIds) {
|
||||
this.encryptionKeyIds = encryptionKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder encryptionPassphrase(String encryptionPassphrase) {
|
||||
this.encryptionPassphrase = encryptionPassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signatureKeyId(long signatureKeyId) {
|
||||
this.signatureKeyId = signatureKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
this.signatureHashAlgorithm = signatureHashAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signatureForceV3(boolean signatureForceV3) {
|
||||
this.signatureForceV3 = signatureForceV3;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signaturePassphrase(String signaturePassphrase) {
|
||||
this.signaturePassphrase = signaturePassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncrypt build() {
|
||||
return new PgpSignEncrypt(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int message, int current, int total) {
|
||||
if (progress != null) {
|
||||
progress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int current, int total) {
|
||||
if (progress != null) {
|
||||
progress.setProgress(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs and/or encrypts data based on parameters of class
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws PgpGeneralException
|
||||
* @throws PGPException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws SignatureException
|
||||
*/
|
||||
public void execute()
|
||||
throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
|
||||
NoSuchAlgorithmException, SignatureException {
|
||||
|
||||
boolean enableSignature = signatureKeyId != Id.key.none;
|
||||
boolean enableEncryption = (encryptionKeyIds.length != 0 || encryptionPassphrase != null);
|
||||
boolean enableCompression = (enableEncryption && compressionId != Id.choice.compression.none);
|
||||
|
||||
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
||||
+ "\nenableEncryption:" + enableEncryption
|
||||
+ "\nenableCompression:" + enableCompression
|
||||
+ "\nenableAsciiArmorOutput:" + enableAsciiArmorOutput);
|
||||
|
||||
int signatureType;
|
||||
if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
|
||||
// for sign-only ascii text
|
||||
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
|
||||
} else {
|
||||
signatureType = PGPSignature.BINARY_DOCUMENT;
|
||||
}
|
||||
|
||||
ArmoredOutputStream armorOut = null;
|
||||
OutputStream out;
|
||||
if (enableAsciiArmorOutput) {
|
||||
armorOut = new ArmoredOutputStream(outStream);
|
||||
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
|
||||
out = armorOut;
|
||||
} else {
|
||||
out = outStream;
|
||||
}
|
||||
|
||||
/* Get keys for signature generation for later usage */
|
||||
PGPSecretKey signingKey = null;
|
||||
PGPSecretKeyRing signingKeyRing = null;
|
||||
PGPPrivateKey signaturePrivateKey = null;
|
||||
if (enableSignature) {
|
||||
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
|
||||
signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
|
||||
if (signingKey == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
if (signaturePassphrase == null) {
|
||||
throw new PgpGeneralException(
|
||||
context.getString(R.string.error_no_signature_passphrase));
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
|
||||
signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
context.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
}
|
||||
updateProgress(R.string.progress_preparing_streams, 5, 100);
|
||||
|
||||
/* Initialize PGPEncryptedDataGenerator for later usage */
|
||||
PGPEncryptedDataGenerator cPk = null;
|
||||
if (enableEncryption) {
|
||||
// has Integrity packet enabled!
|
||||
JcePGPDataEncryptorBuilder encryptorBuilder =
|
||||
new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
|
||||
.setWithIntegrityPacket(true);
|
||||
|
||||
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
||||
|
||||
if (encryptionKeyIds.length == 0) {
|
||||
// Symmetric encryption
|
||||
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
|
||||
|
||||
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
|
||||
new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray());
|
||||
cPk.addMethod(symmetricEncryptionGenerator);
|
||||
} else {
|
||||
// Asymmetric encryption
|
||||
for (long id : encryptionKeyIds) {
|
||||
PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id);
|
||||
if (key != null) {
|
||||
JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator =
|
||||
new JcePublicKeyKeyEncryptionMethodGenerator(key);
|
||||
cPk.addMethod(pubKeyEncryptionGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize signature generator object for later usage */
|
||||
PGPSignatureGenerator signatureGenerator = null;
|
||||
PGPV3SignatureGenerator signatureV3Generator = null;
|
||||
if (enableSignature) {
|
||||
updateProgress(R.string.progress_preparing_signature, 10, 100);
|
||||
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
|
||||
signatureV3Generator.init(signatureType, signaturePrivateKey);
|
||||
} else {
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(signatureType, signaturePrivateKey);
|
||||
|
||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, userId);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
}
|
||||
}
|
||||
|
||||
PGPCompressedDataGenerator compressGen = null;
|
||||
OutputStream pOut;
|
||||
OutputStream encryptionOut = null;
|
||||
BCPGOutputStream bcpgOut;
|
||||
if (enableEncryption) {
|
||||
/* actual encryption */
|
||||
|
||||
encryptionOut = cPk.open(out, new byte[1 << 16]);
|
||||
|
||||
if (enableCompression) {
|
||||
compressGen = new PGPCompressedDataGenerator(compressionId);
|
||||
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
|
||||
} else {
|
||||
bcpgOut = new BCPGOutputStream(encryptionOut);
|
||||
}
|
||||
|
||||
if (enableSignature) {
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
|
||||
} else {
|
||||
signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
|
||||
}
|
||||
}
|
||||
|
||||
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
|
||||
// file name not needed, so empty string
|
||||
pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(),
|
||||
new byte[1 << 16]);
|
||||
updateProgress(R.string.progress_encrypting, 20, 100);
|
||||
|
||||
long progress = 0;
|
||||
int n;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
InputStream in = data.getInputStream();
|
||||
while ((n = in.read(buffer)) > 0) {
|
||||
pOut.write(buffer, 0, n);
|
||||
|
||||
// update signature buffer if signature is requested
|
||||
if (enableSignature) {
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator.update(buffer, 0, n);
|
||||
} else {
|
||||
signatureGenerator.update(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
|
||||
progress += n;
|
||||
if (data.getSize() != 0) {
|
||||
updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100);
|
||||
}
|
||||
}
|
||||
|
||||
literalGen.close();
|
||||
} else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
|
||||
/* sign-only of ascii text */
|
||||
|
||||
updateProgress(R.string.progress_signing, 40, 100);
|
||||
|
||||
// write directly on armor output stream
|
||||
armorOut.beginClearText(signatureHashAlgorithm);
|
||||
|
||||
InputStream in = data.getInputStream();
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
final byte[] newline = "\r\n".getBytes("UTF-8");
|
||||
|
||||
if (signatureForceV3) {
|
||||
processLine(reader.readLine(), armorOut, signatureV3Generator);
|
||||
} else {
|
||||
processLine(reader.readLine(), armorOut, signatureGenerator);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
|
||||
if (line == null) {
|
||||
armorOut.write(newline);
|
||||
break;
|
||||
}
|
||||
|
||||
armorOut.write(newline);
|
||||
|
||||
// update signature buffer with input line
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator.update(newline);
|
||||
processLine(line, armorOut, signatureV3Generator);
|
||||
} else {
|
||||
signatureGenerator.update(newline);
|
||||
processLine(line, armorOut, signatureGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
armorOut.endClearText();
|
||||
|
||||
pOut = new BCPGOutputStream(armorOut);
|
||||
} else {
|
||||
// TODO: implement sign-only for files!
|
||||
pOut = null;
|
||||
Log.e(Constants.TAG, "not supported!");
|
||||
}
|
||||
|
||||
if (enableSignature) {
|
||||
updateProgress(R.string.progress_generating_signature, 95, 100);
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator.generate().encode(pOut);
|
||||
} else {
|
||||
signatureGenerator.generate().encode(pOut);
|
||||
}
|
||||
}
|
||||
|
||||
// closing outputs
|
||||
// NOTE: closing needs to be done in the correct order!
|
||||
// TODO: closing bcpgOut and pOut???
|
||||
if (enableEncryption) {
|
||||
if (enableCompression) {
|
||||
compressGen.close();
|
||||
}
|
||||
|
||||
encryptionOut.close();
|
||||
}
|
||||
if (enableAsciiArmorOutput) {
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
out.close();
|
||||
outStream.close();
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
// TODO: merge this into execute method!
|
||||
// TODO: allow binary input for this class
|
||||
public void generateSignature()
|
||||
throws PgpGeneralException, PGPException, IOException, NoSuchAlgorithmException,
|
||||
SignatureException {
|
||||
|
||||
OutputStream out;
|
||||
if (enableAsciiArmorOutput) {
|
||||
// Ascii Armor (Radix-64)
|
||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream);
|
||||
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
|
||||
out = armorOut;
|
||||
} else {
|
||||
out = outStream;
|
||||
}
|
||||
|
||||
if (signatureKeyId == 0) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_no_signature_key));
|
||||
}
|
||||
|
||||
PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
|
||||
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
|
||||
if (signingKey == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
if (signaturePassphrase == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
context.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
updateProgress(R.string.progress_preparing_streams, 0, 100);
|
||||
|
||||
updateProgress(R.string.progress_preparing_signature, 30, 100);
|
||||
|
||||
int type = PGPSignature.CANONICAL_TEXT_DOCUMENT;
|
||||
// if (binary) {
|
||||
// type = PGPSignature.BINARY_DOCUMENT;
|
||||
// }
|
||||
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
|
||||
.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = null;
|
||||
PGPV3SignatureGenerator signatureV3Generator = null;
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
|
||||
signatureV3Generator.init(type, signaturePrivateKey);
|
||||
} else {
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(type, signaturePrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
||||
spGen.setSignerUserID(false, userId);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_signing, 40, 100);
|
||||
|
||||
InputStream inStream = data.getInputStream();
|
||||
// if (binary) {
|
||||
// byte[] buffer = new byte[1 << 16];
|
||||
// int n = 0;
|
||||
// while ((n = inStream.read(buffer)) > 0) {
|
||||
// if (signatureForceV3) {
|
||||
// signatureV3Generator.update(buffer, 0, n);
|
||||
// } else {
|
||||
// signatureGenerator.update(buffer, 0, n);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
|
||||
final byte[] newline = "\r\n".getBytes("UTF-8");
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (signatureForceV3) {
|
||||
processLine(line, null, signatureV3Generator);
|
||||
signatureV3Generator.update(newline);
|
||||
} else {
|
||||
processLine(line, null, signatureGenerator);
|
||||
signatureGenerator.update(newline);
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
BCPGOutputStream bOut = new BCPGOutputStream(out);
|
||||
if (signatureForceV3) {
|
||||
signatureV3Generator.generate().encode(bOut);
|
||||
} else {
|
||||
signatureGenerator.generate().encode(bOut);
|
||||
}
|
||||
out.close();
|
||||
outStream.close();
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
|
||||
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
|
||||
final PGPSignatureGenerator pSignatureGenerator)
|
||||
throws IOException, SignatureException {
|
||||
|
||||
if (pLine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final char[] chars = pLine.toCharArray();
|
||||
int len = chars.length;
|
||||
|
||||
while (len > 0) {
|
||||
if (!Character.isWhitespace(chars[len - 1])) {
|
||||
break;
|
||||
}
|
||||
len--;
|
||||
}
|
||||
|
||||
final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
|
||||
|
||||
if (pArmoredOutput != null) {
|
||||
pArmoredOutput.write(data);
|
||||
}
|
||||
pSignatureGenerator.update(data);
|
||||
}
|
||||
|
||||
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
|
||||
final PGPV3SignatureGenerator pSignatureGenerator)
|
||||
throws IOException, SignatureException {
|
||||
|
||||
if (pLine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final char[] chars = pLine.toCharArray();
|
||||
int len = chars.length;
|
||||
|
||||
while (len > 0) {
|
||||
if (!Character.isWhitespace(chars[len - 1])) {
|
||||
break;
|
||||
}
|
||||
len--;
|
||||
}
|
||||
|
||||
final byte[] data = pLine.substring(0, len).getBytes("UTF-8");
|
||||
|
||||
if (pArmoredOutput != null) {
|
||||
pArmoredOutput.write(data);
|
||||
}
|
||||
pSignatureGenerator.update(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
|
||||
|
||||
// Upgrade from oldVersion through all methods to newest one
|
||||
// Upgrade from oldVersion through all cases to newest one
|
||||
for (int version = oldVersion; version < newVersion; ++version) {
|
||||
Log.w(Constants.TAG, "Upgrading database to version " + version);
|
||||
|
||||
@@ -123,14 +123,17 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
break;
|
||||
case 4:
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 5:
|
||||
// new column: package_signature
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 6:
|
||||
// new column: fingerprint
|
||||
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
|
||||
+ " BLOB;");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
|
||||
@@ -359,7 +359,9 @@ public class KeychainProvider extends ContentProvider {
|
||||
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID);
|
||||
// TODO: deprecated master key id
|
||||
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
|
||||
|
||||
projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);
|
||||
projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
|
||||
|
||||
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@@ -210,6 +211,13 @@ public class ProviderHelper {
|
||||
++userIdRank;
|
||||
}
|
||||
|
||||
for (PGPSignature certification : new IterableIterator<PGPSignature>(masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) {
|
||||
//TODO: how to do this?? we need to verify the signatures again and again when they are displayed...
|
||||
// if (certification.verify
|
||||
// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
context.getContentResolver().applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
|
||||
} catch (RemoteException e) {
|
||||
@@ -562,6 +570,26 @@ public class ProviderHelper {
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
public static String getUserId(Context context, Uri queryUri) {
|
||||
String[] projection = new String[]{UserIds.USER_ID};
|
||||
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
|
||||
|
||||
String userId = null;
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int col = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
|
||||
userId = cursor.getString(col);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static ArrayList<String> getKeyRingsAsArmoredString(Context context, Uri uri,
|
||||
long[] masterKeyIds) {
|
||||
ArrayList<String> output = new ArrayList<String>();
|
||||
|
||||
@@ -43,10 +43,11 @@ import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
@@ -95,7 +96,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
||||
public static final String ACTION_DOWNLOAD_AND_IMPORT_KEYS = Constants.INTENT_PREFIX + "QUERY_KEYRING";
|
||||
|
||||
public static final String ACTION_SIGN_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
||||
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
||||
|
||||
/* keys for data bundle */
|
||||
|
||||
@@ -119,11 +120,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
public static final String ENCRYPT_PROVIDER_URI = "provider_uri";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_SIGNED_ONLY = "signed_only";
|
||||
public static final String DECRYPT_RETURN_BYTES = "return_binary";
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
|
||||
public static final String DECRYPT_LOOKUP_UNKNOWN_KEY = "lookup_unknownKey";
|
||||
|
||||
// save keyring
|
||||
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
|
||||
@@ -166,8 +165,8 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
public static final String DOWNLOAD_KEY_LIST = "query_key_id";
|
||||
|
||||
// sign key
|
||||
public static final String SIGN_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
|
||||
public static final String SIGN_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||
public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
|
||||
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
|
||||
|
||||
/*
|
||||
* possible data keys as result send over messenger
|
||||
@@ -189,10 +188,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
public static final String RESULT_SIGNATURE = "signature";
|
||||
public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id";
|
||||
public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id";
|
||||
public static final String RESULT_CLEARTEXT_SIGNATURE_ONLY = "signature_only";
|
||||
|
||||
public static final String RESULT_SIGNATURE_SUCCESS = "signature_success";
|
||||
public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown";
|
||||
public static final String RESULT_SIGNATURE_LOOKUP_KEY = "lookup_key";
|
||||
|
||||
// import
|
||||
public static final String RESULT_IMPORT_ADDED = "added";
|
||||
@@ -241,7 +240,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
String action = intent.getAction();
|
||||
|
||||
// execute action from extra bundle
|
||||
// executeServiceMethod action from extra bundle
|
||||
if (ACTION_ENCRYPT_SIGN.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
@@ -322,27 +321,41 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
PgpSignEncrypt.Builder builder =
|
||||
new PgpSignEncrypt.Builder(this, inputData, outStream);
|
||||
builder.progress(this);
|
||||
|
||||
if (generateSignature) {
|
||||
Log.d(Constants.TAG, "generating signature...");
|
||||
operation.generateSignature(useAsciiArmor, false, secretKeyId,
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures());
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().generateSignature();
|
||||
} else if (signOnly) {
|
||||
Log.d(Constants.TAG, "sign only...");
|
||||
operation.signText(secretKeyId, PassphraseCacheService.getCachedPassphrase(
|
||||
this, secretKeyId), Preferences.getPreferences(this)
|
||||
.getDefaultHashAlgorithm(), Preferences.getPreferences(this)
|
||||
.getForceV3Signatures());
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "encrypt...");
|
||||
operation.signAndEncrypt(useAsciiArmor, compressionId, encryptionKeyIds,
|
||||
encryptionPassphrase, Preferences.getPreferences(this)
|
||||
.getDefaultEncryptionAlgorithm(), secretKeyId, Preferences
|
||||
.getPreferences(this).getDefaultHashAlgorithm(), Preferences
|
||||
.getPreferences(this).getForceV3Signatures(),
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.compressionId(compressionId)
|
||||
.symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.encryptionKeyIds(encryptionKeyIds)
|
||||
.encryptionPassphrase(encryptionPassphrase)
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
@@ -395,12 +408,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
||||
boolean signedOnly = data.getBoolean(DECRYPT_SIGNED_ONLY);
|
||||
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
|
||||
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
|
||||
|
||||
boolean lookupUnknownKey = data.getBoolean(DECRYPT_LOOKUP_UNKNOWN_KEY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
@@ -474,14 +484,13 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpOperation operation = new PgpOperation(this, this, inputData, outStream);
|
||||
if (signedOnly) {
|
||||
resultData = operation.verifyText(lookupUnknownKey);
|
||||
} else {
|
||||
resultData = operation.decryptAndVerify(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
|
||||
assumeSymmetricEncryption);
|
||||
}
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
|
||||
builder.progress(this);
|
||||
|
||||
builder.assumeSymmetric(assumeSymmetricEncryption)
|
||||
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
resultData = builder.build().execute();
|
||||
|
||||
outStream.close();
|
||||
|
||||
@@ -785,19 +794,19 @@ public class KeychainIntentService extends IntentService implements ProgressDial
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
} else if (ACTION_SIGN_KEYRING.equals(action)) {
|
||||
} else if (ACTION_CERTIFY_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
long masterKeyId = data.getLong(SIGN_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(SIGN_KEY_PUB_KEY_ID);
|
||||
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
|
||||
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
|
||||
|
||||
/* Operation */
|
||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
masterKeyId);
|
||||
|
||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.signKey(masterKeyId, pubKeyId,
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
||||
signaturePassPhrase);
|
||||
|
||||
// store the signed key in our local cache
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
@@ -60,7 +61,14 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
}
|
||||
|
||||
public void showProgressDialog(FragmentActivity activity) {
|
||||
mProgressDialogFragment.show(activity.getSupportFragmentManager(), "progressDialog");
|
||||
// TODO: This is a hack!, see http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
|
||||
final FragmentManager manager = activity.getSupportFragmentManager();
|
||||
Handler handler = new Handler();
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
mProgressDialogFragment.show(manager, "progressDialog");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class NoUserIdsException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 7009311527126696207L;
|
||||
|
||||
public NoUserIdsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class UserInteractionRequiredException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -60128148603511936L;
|
||||
|
||||
public UserInteractionRequiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
|
||||
public class WrongPassphraseException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5309689232853485740L;
|
||||
|
||||
public WrongPassphraseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,15 @@ public class AppSettingsFragment extends Fragment implements
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error String on key selection
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
public void setErrorOnSelectKeyFragment(String error) {
|
||||
mSelectKeyFragment.setError(error);
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_select_key_fragment);
|
||||
@@ -182,7 +191,7 @@ public class AppSettingsFragment extends Fragment implements
|
||||
// TODO: Better: collapse/expand animation
|
||||
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);u
|
||||
// animation2.setDuration(150);
|
||||
|
||||
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@@ -1,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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,108 +17,46 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpCallback;
|
||||
import org.openintents.openpgp.IOpenPgpKeyIdsCallback;
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpData;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.util.Arrays;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.exception.NoUserIdsException;
|
||||
import org.sufficientlysecure.keychain.service.exception.UserInteractionRequiredException;
|
||||
import org.sufficientlysecure.keychain.service.exception.WrongPassphraseException;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpConstants;
|
||||
import org.spongycastle.util.Arrays;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
private String getCachedPassphrase(long keyId, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
|
||||
private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
|
||||
private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
|
||||
|
||||
if (passphrase == null) {
|
||||
if (!allowUserInteraction) {
|
||||
throw new UserInteractionRequiredException(
|
||||
"Passphrase not found in cache, please enter your passphrase!");
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "No passphrase! Activity required!");
|
||||
|
||||
// start passphrase dialog
|
||||
PassphraseActivityCallback callback = new PassphraseActivityCallback();
|
||||
Bundle extras = new Bundle();
|
||||
extras.putLong(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE, callback,
|
||||
extras);
|
||||
|
||||
if (callback.isSuccess()) {
|
||||
Log.d(Constants.TAG, "New passphrase entered!");
|
||||
|
||||
// get again after it was entered
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), keyId);
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Passphrase dialog canceled!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public class PassphraseActivityCallback extends UserInputCallback {
|
||||
|
||||
private boolean success = false;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUserInput(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
*
|
||||
* @param encryptionUserIds
|
||||
* @return
|
||||
*/
|
||||
private long[] getKeyIdsFromEmails(String[] encryptionUserIds, boolean allowUserInteraction)
|
||||
throws UserInteractionRequiredException {
|
||||
private Bundle getKeyIdsFromEmails(Bundle params, String[] encryptionUserIds) {
|
||||
// find key ids to given emails in database
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
|
||||
@@ -152,96 +90,129 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
// allow the user to verify pub key selection
|
||||
if (allowUserInteraction && (missingUserIdsCheck || dublicateUserIdsCheck)) {
|
||||
SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback();
|
||||
if (missingUserIdsCheck || dublicateUserIdsCheck) {
|
||||
// build PendingIntent for passphrase input
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
|
||||
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putLongArray(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||
extras.putStringArrayList(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
||||
extras.putStringArrayList(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS,
|
||||
dublicateUserIds);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS, callback,
|
||||
extras);
|
||||
// return PendingIntent to be executed by client
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
|
||||
|
||||
if (callback.isSuccess()) {
|
||||
Log.d(Constants.TAG, "New selection of pub keys!");
|
||||
keyIdsArray = callback.getPubKeyIds();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Pub key selection canceled!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// if no user interaction is allow throw exceptions on duplicate or missing pub keys
|
||||
if (!allowUserInteraction) {
|
||||
if (missingUserIdsCheck)
|
||||
throw new UserInteractionRequiredException(
|
||||
"Pub keys for these user ids are missing:" + missingUserIds.toString());
|
||||
if (dublicateUserIdsCheck)
|
||||
throw new UserInteractionRequiredException(
|
||||
"More than one pub key with these user ids exist:"
|
||||
+ dublicateUserIds.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (keyIdsArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return keyIdsArray;
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
|
||||
result.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIdsArray);
|
||||
return result;
|
||||
}
|
||||
|
||||
public class SelectPubKeysActivityCallback extends UserInputCallback {
|
||||
public static final String PUB_KEY_IDS = "pub_key_ids";
|
||||
private Bundle getPassphraseBundleIntent(Bundle params, long keyId) {
|
||||
// build PendingIntent for passphrase input
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0);
|
||||
|
||||
private boolean success = false;
|
||||
private long[] pubKeyIds;
|
||||
// return PendingIntent to be executed by client
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long[] getPubKeyIds() {
|
||||
return pubKeyIds;
|
||||
}
|
||||
private Bundle signImpl(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output,
|
||||
AppSettings appSettings) {
|
||||
try {
|
||||
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
@Override
|
||||
public void handleUserInput(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
success = true;
|
||||
pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS);
|
||||
// get passphrase from cache, if key has "no" passphrase, this returns an empty String
|
||||
String passphrase;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
|
||||
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
|
||||
} else {
|
||||
success = false;
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized void getKeyIdsSafe(String[] userIds, boolean allowUserInteraction,
|
||||
IOpenPgpKeyIdsCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
long[] keyIds = getKeyIdsFromEmails(userIds, allowUserInteraction);
|
||||
if (keyIds == null) {
|
||||
throw new NoUserIdsException("No user ids!");
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
|
||||
return passphraseBundle;
|
||||
}
|
||||
|
||||
callback.onSuccess(keyIds);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (NoUserIdsException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_USER_IDS, e.getMessage());
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
try {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
// sign-only
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||
builder.enableAsciiArmorOutput(asciiArmor)
|
||||
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
||||
.signatureForceV3(false)
|
||||
.signatureKeyId(appSettings.getKeyId())
|
||||
.signaturePassphrase(passphrase);
|
||||
builder.build().execute();
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void encryptAndSignSafe(OpenPgpData inputData,
|
||||
final OpenPgpData outputData, long[] keyIds, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings, boolean sign) {
|
||||
private Bundle encryptAndSignImpl(Bundle params, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings,
|
||||
boolean sign) {
|
||||
try {
|
||||
// TODO: other options of OpenPgpData!
|
||||
byte[] inputBytes = getInput(inputData);
|
||||
boolean asciiArmor = false;
|
||||
if (outputData.getType() == OpenPgpData.TYPE_STRING) {
|
||||
asciiArmor = true;
|
||||
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
long[] keyIds;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_KEY_IDS)) {
|
||||
keyIds = params.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
|
||||
} else if (params.containsKey(OpenPgpConstants.PARAMS_USER_IDS)) {
|
||||
// get key ids based on given user ids
|
||||
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
|
||||
// give params through to activity...
|
||||
Bundle result = getKeyIdsFromEmails(params, userIds);
|
||||
|
||||
if (result.getInt(OpenPgpConstants.RESULT_CODE, 0) == OpenPgpConstants.RESULT_CODE_SUCCESS) {
|
||||
keyIds = result.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
|
||||
} else {
|
||||
// if not success -> result contains a PendingIntent for user interaction
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!"));
|
||||
return result;
|
||||
}
|
||||
|
||||
// add own key for encryption
|
||||
@@ -249,351 +220,339 @@ public class OpenPgpService extends RemoteService {
|
||||
keyIds[keyIds.length - 1] = appSettings.getKeyId();
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputDt = new InputData(inputStream, inputLength);
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
try {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||
builder.enableAsciiArmorOutput(asciiArmor)
|
||||
.compressionId(appSettings.getCompression())
|
||||
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
|
||||
.encryptionKeyIds(keyIds);
|
||||
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputDt, outputStream);
|
||||
if (sign) {
|
||||
String passphrase = getCachedPassphrase(appSettings.getKeyId(),
|
||||
allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
if (sign) {
|
||||
String passphrase;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
|
||||
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
|
||||
} else {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
||||
appSettings.getKeyId());
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
|
||||
return passphraseBundle;
|
||||
}
|
||||
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
|
||||
appSettings.getHashAlgorithm(), true, passphrase);
|
||||
} else {
|
||||
operation.signAndEncrypt(asciiArmor, appSettings.getCompression(), keyIds, null,
|
||||
appSettings.getEncryptionAlgorithm(), Id.key.none,
|
||||
appSettings.getHashAlgorithm(), true, null);
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
OpenPgpData output = null;
|
||||
if (asciiArmor) {
|
||||
output = new OpenPgpData(new String(outputBytes));
|
||||
} else {
|
||||
output = new OpenPgpData(outputBytes);
|
||||
}
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, null);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: asciiArmor?!
|
||||
private void signSafe(byte[] inputBytes, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
String passphrase = getCachedPassphrase(appSettings.getKeyId(), allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
}
|
||||
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
|
||||
operation.signText(appSettings.getKeyId(), passphrase, appSettings.getHashAlgorithm(),
|
||||
Preferences.getPreferences(this).getForceV3Signatures());
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
OpenPgpData output = new OpenPgpData(new String(outputBytes));
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, null);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void decryptAndVerifySafe(byte[] inputBytes, boolean allowUserInteraction,
|
||||
IOpenPgpCallback callback, AppSettings appSettings) {
|
||||
try {
|
||||
// TODO: this is not really needed
|
||||
// checked if it is text with BEGIN and END tags
|
||||
String message = new String(inputBytes);
|
||||
Log.d(Constants.TAG, "in: " + message);
|
||||
boolean signedOnly = false;
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
message = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
message = message.replaceAll("\\xa0", " ");
|
||||
|
||||
// overwrite inputBytes
|
||||
inputBytes = message.getBytes();
|
||||
} else {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
|
||||
if (matcher.matches()) {
|
||||
signedOnly = true;
|
||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
message = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
message = message.replaceAll("\\xa0", " ");
|
||||
|
||||
// overwrite inputBytes
|
||||
inputBytes = message.getBytes();
|
||||
// sign and encrypt
|
||||
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
||||
.signatureForceV3(false)
|
||||
.signatureKeyId(appSettings.getKeyId())
|
||||
.signaturePassphrase(passphrase);
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Nothing matched! Binary?");
|
||||
}
|
||||
}
|
||||
// END TODO
|
||||
|
||||
Log.d(Constants.TAG, "in: " + new String(inputBytes));
|
||||
|
||||
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
|
||||
// app, Fix this?
|
||||
|
||||
String passphrase = null;
|
||||
if (!signedOnly) {
|
||||
// BEGIN Get key
|
||||
// TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
|
||||
// better!
|
||||
InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
|
||||
|
||||
// TODO: duplicates functions from DecryptActivity!
|
||||
long secretKeyId;
|
||||
try {
|
||||
if (inputStream2.markSupported()) {
|
||||
// should probably set this to the max size of two
|
||||
// pgpF objects, if it even needs to be anything other
|
||||
// than 0.
|
||||
inputStream2.mark(200);
|
||||
}
|
||||
secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
|
||||
if (secretKeyId == Id.key.none) {
|
||||
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
} catch (NoAsymmetricEncryptionException e) {
|
||||
if (inputStream2.markSupported()) {
|
||||
inputStream2.reset();
|
||||
}
|
||||
secretKeyId = Id.key.symmetric;
|
||||
if (!PgpOperation.hasSymmetricEncryption(this, inputStream2)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
// we do not support symmetric decryption from the API!
|
||||
throw new Exception("Symmetric decryption is not supported!");
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
|
||||
|
||||
passphrase = getCachedPassphrase(secretKeyId, allowUserInteraction);
|
||||
if (passphrase == null) {
|
||||
throw new WrongPassphraseException("No or wrong passphrase!");
|
||||
// encrypt only
|
||||
builder.signatureKeyId(Id.key.none);
|
||||
}
|
||||
// execute PGP operation!
|
||||
builder.build().execute();
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(inputBytes);
|
||||
long inputLength = inputBytes.length;
|
||||
InputData inputData = new InputData(inputStream, inputLength);
|
||||
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Bundle outputBundle;
|
||||
PgpOperation operation = new PgpOperation(getContext(), null, inputData, outputStream);
|
||||
if (signedOnly) {
|
||||
// TODO: download missing keys from keyserver?
|
||||
outputBundle = operation.verifyText(false);
|
||||
} else {
|
||||
outputBundle = operation.decryptAndVerify(passphrase, false);
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
|
||||
byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
// get signature informations from bundle
|
||||
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
|
||||
|
||||
OpenPgpSignatureResult sigResult = null;
|
||||
if (signature) {
|
||||
long signatureKeyId = outputBundle
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
String signatureUserId = outputBundle
|
||||
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
boolean signatureSuccess = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
|
||||
boolean signatureUnknown = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
|
||||
|
||||
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
|
||||
if (signatureSuccess) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_TRUSTED;
|
||||
} else if (signatureUnknown) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
|
||||
}
|
||||
|
||||
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
|
||||
signedOnly, signatureKeyId);
|
||||
}
|
||||
OpenPgpData output = new OpenPgpData(new String(outputBytes));
|
||||
|
||||
// return over handler on client side
|
||||
callback.onSuccess(output, sigResult);
|
||||
} catch (UserInteractionRequiredException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.USER_INTERACTION_REQUIRED, e.getMessage());
|
||||
} catch (WrongPassphraseException e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.NO_OR_WRONG_PASSPHRASE, e.getMessage());
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
callbackOpenPgpError(callback, OpenPgpError.GENERIC_ERROR, e.getMessage());
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle decryptAndVerifyImpl(Bundle params, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings) {
|
||||
try {
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
OpenPgpSignatureResult sigResult = null;
|
||||
try {
|
||||
|
||||
// PGPUtil.getDecoderStream(is)
|
||||
// TODOs API 2.0:
|
||||
// implement verify-only!
|
||||
// fix the mess: http://stackoverflow.com/questions/148130/how-do-i-peek-at-the-first-two-bytes-in-an-inputstream
|
||||
// should we allow to decrypt everything under every key id or only the one set?
|
||||
// TODO: instead of trying to get the passphrase before
|
||||
// pause stream when passphrase is missing and then resume
|
||||
|
||||
|
||||
// TODO: this is not really needed
|
||||
// checked if it is text with BEGIN and END tags
|
||||
// String message = new String(inputBytes);
|
||||
// Log.d(Constants.TAG, "in: " + message);
|
||||
// boolean signedOnly = false;
|
||||
// Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
|
||||
// if (matcher.matches()) {
|
||||
// Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
// message = matcher.group(1);
|
||||
// // replace non breakable spaces
|
||||
// message = message.replaceAll("\\xa0", " ");
|
||||
//
|
||||
// // overwrite inputBytes
|
||||
// inputBytes = message.getBytes();
|
||||
// } else {
|
||||
// matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
|
||||
// if (matcher.matches()) {
|
||||
// signedOnly = true;
|
||||
// Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
// message = matcher.group(1);
|
||||
// // replace non breakable spaces
|
||||
// message = message.replaceAll("\\xa0", " ");
|
||||
//
|
||||
// // overwrite inputBytes
|
||||
// inputBytes = message.getBytes();
|
||||
// } else {
|
||||
// Log.d(Constants.TAG, "Nothing matched! Binary?");
|
||||
// }
|
||||
// }
|
||||
// END TODO
|
||||
|
||||
// Log.d(Constants.TAG, "in: " + new String(inputBytes));
|
||||
|
||||
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
|
||||
// app, Fix this?
|
||||
|
||||
// String passphrase = null;
|
||||
// if (!signedOnly) {
|
||||
// // BEGIN Get key
|
||||
// // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
|
||||
// // better!
|
||||
// InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
|
||||
//
|
||||
// // TODO: duplicates functions from DecryptActivity!
|
||||
// long secretKeyId;
|
||||
// try {
|
||||
// if (inputStream2.markSupported()) {
|
||||
// // should probably set this to the max size of two
|
||||
// // pgpF objects, if it even needs to be anything other
|
||||
// // than 0.
|
||||
// inputStream2.mark(200);
|
||||
// }
|
||||
// secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
|
||||
// if (secretKeyId == Id.key.none) {
|
||||
// throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
|
||||
// }
|
||||
// } catch (NoAsymmetricEncryptionException e) {
|
||||
// if (inputStream2.markSupported()) {
|
||||
// inputStream2.reset();
|
||||
// }
|
||||
// secretKeyId = Id.key.symmetric;
|
||||
// if (!PgpDecryptVerify.hasSymmetricEncryption(this, inputStream2)) {
|
||||
// throw new PgpGeneralException(
|
||||
// getString(R.string.error_no_known_encryption_found));
|
||||
// }
|
||||
// // we do not support symmetric decryption from the API!
|
||||
// throw new Exception("Symmetric decryption is not supported!");
|
||||
// }
|
||||
//
|
||||
// Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
|
||||
|
||||
// NOTE: currently this only gets the passphrase for the key set for this client
|
||||
String passphrase;
|
||||
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
|
||||
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
|
||||
} else {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
|
||||
return passphraseBundle;
|
||||
}
|
||||
// }
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
|
||||
Bundle outputBundle;
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
|
||||
|
||||
// if (signedOnly) {
|
||||
// outputBundle = builder.build().verifyText();
|
||||
// } else {
|
||||
builder.assumeSymmetric(false)
|
||||
.passphrase(passphrase);
|
||||
|
||||
// Do we want to do this: instead of trying to get the passphrase before
|
||||
// pause stream when passphrase is missing and then resume???
|
||||
|
||||
// TODO: this also decrypts with other secret keys without passphrase!!!
|
||||
outputBundle = builder.build().execute();
|
||||
// }
|
||||
|
||||
// outputStream.close();
|
||||
|
||||
// byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||
|
||||
// get signature informations from bundle
|
||||
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false);
|
||||
|
||||
if (signature) {
|
||||
long signatureKeyId = outputBundle
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0);
|
||||
String signatureUserId = outputBundle
|
||||
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
boolean signatureSuccess = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
|
||||
boolean signatureUnknown = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, false);
|
||||
boolean signatureOnly = outputBundle
|
||||
.getBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
|
||||
|
||||
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
|
||||
if (signatureSuccess) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED;
|
||||
} else if (signatureUnknown) {
|
||||
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
|
||||
}
|
||||
|
||||
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
|
||||
signatureOnly, signatureKeyId);
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
}
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_SIGNATURE, sigResult);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle getKeyIdsImpl(Bundle params) {
|
||||
// get key ids based on given user ids
|
||||
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
|
||||
Bundle result = getKeyIdsFromEmails(params, userIds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error to IOpenPgpCallback
|
||||
*
|
||||
* @param callback
|
||||
* @param errorId
|
||||
* @param message
|
||||
* Check requirements:
|
||||
* - params != null
|
||||
* - has supported API version
|
||||
* - is allowed to call the service (access has been granted)
|
||||
*
|
||||
* @param params
|
||||
* @return null if everything is okay, or a Bundle with an error/PendingIntent
|
||||
*/
|
||||
private void callbackOpenPgpError(IOpenPgpCallback callback, int errorId, String message) {
|
||||
try {
|
||||
callback.onError(new OpenPgpError(0, message));
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG,
|
||||
"Exception while returning OpenPgpError to client via callback.onError()", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void callbackOpenPgpError(IOpenPgpKeyIdsCallback callback, int errorId, String message) {
|
||||
try {
|
||||
callback.onError(new OpenPgpError(0, message));
|
||||
} catch (Exception t) {
|
||||
Log.e(Constants.TAG,
|
||||
"Exception while returning OpenPgpError to client via callback.onError()", t);
|
||||
private Bundle checkRequirements(Bundle params) {
|
||||
// params Bundle is required!
|
||||
if (params == null) {
|
||||
Bundle result = new Bundle();
|
||||
OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// version code is required and needs to correspond to version code of service!
|
||||
if (params.getInt(OpenPgpConstants.PARAMS_API_VERSION) != OpenPgpConstants.API_VERSION) {
|
||||
Bundle result = new Bundle();
|
||||
OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if caller is allowed to access openpgp keychain
|
||||
Bundle result = isAllowed(params);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: multi-threading
|
||||
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
||||
|
||||
@Override
|
||||
public void encrypt(final OpenPgpData input, final OpenPgpData output, final long[] keyIds,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle sign(Bundle params, final ParcelFileDescriptor input, final ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, false);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return signImpl(params, input, output, appSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signAndEncrypt(final OpenPgpData input, final OpenPgpData output,
|
||||
final long[] keyIds, final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle encrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
encryptAndSignSafe(input, output, keyIds, true, callback, settings, true);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return encryptAndSignImpl(params, input, output, appSettings, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sign(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
final AppSettings settings = getAppSettings();
|
||||
public Bundle signAndEncrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
signSafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return encryptAndSignImpl(params, input, output, appSettings, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptAndVerify(final OpenPgpData input, final OpenPgpData output,
|
||||
final IOpenPgpCallback callback) throws RemoteException {
|
||||
public Bundle decryptAndVerify(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
decryptAndVerifySafe(getInput(input), true, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return decryptAndVerifyImpl(params, input, output, appSettings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getKeyIds(final String[] userIds, final boolean allowUserInteraction,
|
||||
final IOpenPgpKeyIdsCallback callback) throws RemoteException {
|
||||
public Bundle getKeyIds(Bundle params) {
|
||||
Bundle errorResult = checkRequirements(params);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
final AppSettings settings = getAppSettings();
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getKeyIdsSafe(userIds, allowUserInteraction, callback, settings);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndEnqueue(r);
|
||||
return getKeyIdsImpl(params);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static byte[] getInput(OpenPgpData data) {
|
||||
// TODO: support Uri and ParcelFileDescriptor
|
||||
|
||||
byte[] inBytes = null;
|
||||
switch (data.getType()) {
|
||||
case OpenPgpData.TYPE_STRING:
|
||||
inBytes = data.getString().getBytes();
|
||||
break;
|
||||
|
||||
case OpenPgpData.TYPE_BYTE_ARRAY:
|
||||
inBytes = data.getBytes();
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(Constants.TAG, "Uri and ParcelFileDescriptor not supported right now!");
|
||||
break;
|
||||
}
|
||||
|
||||
return inBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,17 +19,16 @@ package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpConstants;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.exception.WrongPackageSignatureException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -50,66 +49,19 @@ import android.os.Messenger;
|
||||
public abstract class RemoteService extends Service {
|
||||
Context mContext;
|
||||
|
||||
private final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100);
|
||||
// TODO: Are these parameters okay?
|
||||
private PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
|
||||
TimeUnit.SECONDS, mPoolQueue);
|
||||
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
|
||||
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
|
||||
|
||||
private final Object userInputLock = new Object();
|
||||
|
||||
/**
|
||||
* Override handleUserInput() to handle OKAY (1) and CANCEL (0). After handling the waiting
|
||||
* threads will be notified and the queue resumed
|
||||
*/
|
||||
protected class UserInputCallback extends BaseCallback {
|
||||
|
||||
public void handleUserInput(Message msg) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
handleUserInput(msg);
|
||||
|
||||
// resume
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends Handler.Callback with OKAY (1), CANCEL (0) variables
|
||||
*/
|
||||
private class BaseCallback implements Handler.Callback {
|
||||
public static final int OKAY = 1;
|
||||
public static final int CANCEL = 0;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used from Stub implementations of AIDL interfaces to enqueue a runnable for
|
||||
* execution
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
protected void checkAndEnqueue(Runnable r) {
|
||||
protected Bundle isAllowed(Bundle params) {
|
||||
try {
|
||||
if (isCallerAllowed(false)) {
|
||||
mThreadPool.execute(r);
|
||||
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
return null;
|
||||
} else {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(
|
||||
Binder.getCallingUid());
|
||||
@@ -121,32 +73,46 @@ public abstract class RemoteService extends Service {
|
||||
packageSignature = getPackageSignature(packageName);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Should not happen, returning!", e);
|
||||
return;
|
||||
// return error
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
Log.e(Constants.TAG,
|
||||
"Not allowed to use service! Starting activity for registration!");
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
extras.putByteArray(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
RegisterActivityCallback callback = new RegisterActivityCallback();
|
||||
Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!");
|
||||
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_REGISTER, callback,
|
||||
extras);
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
|
||||
if (callback.isAllowed()) {
|
||||
mThreadPool.execute(r);
|
||||
Log.d(Constants.TAG, "Enqueued runnable…");
|
||||
} else {
|
||||
Log.d(Constants.TAG, "User disallowed app!");
|
||||
}
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, intent, 0);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
Log.e(Constants.TAG, e.getMessage());
|
||||
Log.e(Constants.TAG, "wrong signature!", e);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null, extras);
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature));
|
||||
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_ERROR, intent, 0);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,41 +126,9 @@ public abstract class RemoteService extends Service {
|
||||
return packageSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks current thread and pauses execution of runnables and starts activity for user input
|
||||
*
|
||||
* @param action
|
||||
* @param messenger
|
||||
* @param extras
|
||||
*/
|
||||
protected void pauseAndStartUserInteraction(String action, BaseCallback callback, Bundle extras) {
|
||||
synchronized (userInputLock) {
|
||||
mThreadPool.pause();
|
||||
|
||||
Log.d(Constants.TAG, "starting activity...");
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(action);
|
||||
|
||||
Messenger messenger = new Messenger(new Handler(getMainLooper(), callback));
|
||||
|
||||
extras.putParcelable(RemoteServiceActivity.EXTRA_MESSENGER, messenger);
|
||||
intent.putExtras(extras);
|
||||
|
||||
startActivity(intent);
|
||||
|
||||
// lock current thread for user input
|
||||
try {
|
||||
userInputLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "CryptoService", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AppSettings from database for the application calling this remote service
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected AppSettings getAppSettings() {
|
||||
@@ -215,66 +149,11 @@ public abstract class RemoteService extends Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
class RegisterActivityCallback extends BaseCallback {
|
||||
public static final String PACKAGE_NAME = "package_name";
|
||||
|
||||
private boolean allowed = false;
|
||||
private String packageName;
|
||||
|
||||
public boolean isAllowed() {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if (msg.arg1 == OKAY) {
|
||||
allowed = true;
|
||||
packageName = msg.getData().getString(PACKAGE_NAME);
|
||||
|
||||
// resume threads
|
||||
try {
|
||||
if (isPackageAllowed(packageName)) {
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
} else {
|
||||
// Should not happen!
|
||||
Log.e(Constants.TAG, "Should not happen! Emergency shutdown!");
|
||||
mThreadPool.shutdownNow();
|
||||
}
|
||||
} catch (WrongPackageSignatureException e) {
|
||||
Log.e(Constants.TAG, e.getMessage());
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
pauseAndStartUserInteraction(RemoteServiceActivity.ACTION_ERROR_MESSAGE, null,
|
||||
extras);
|
||||
}
|
||||
} else {
|
||||
allowed = false;
|
||||
|
||||
synchronized (userInputLock) {
|
||||
userInputLock.notifyAll();
|
||||
}
|
||||
mThreadPool.resume();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if process that binds to this service (i.e. the package name corresponding to the
|
||||
* process) is in the list of allowed package names.
|
||||
*
|
||||
* @param allowOnlySelf
|
||||
* allow only Keychain app itself
|
||||
*
|
||||
* @param allowOnlySelf allow only Keychain app itself
|
||||
* @return true if process is allowed to use this service
|
||||
* @throws WrongPackageSignatureException
|
||||
*/
|
||||
@@ -308,7 +187,7 @@ public abstract class RemoteService extends Service {
|
||||
|
||||
/**
|
||||
* Checks if packageName is a registered app for the API. Does not return true for own package!
|
||||
*
|
||||
*
|
||||
* @param packageName
|
||||
* @return
|
||||
* @throws WrongPackageSignatureException
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,8 +17,15 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpConstants;
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
@@ -30,15 +37,7 @@ import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
@@ -64,17 +63,11 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
// error message
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
// register view
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
// select pub keys view
|
||||
private SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
// has the user clicked one of the buttons
|
||||
// or do we need to handle the callback in onStop()
|
||||
private boolean finishHandled;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -82,36 +75,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (!finishHandled) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
finishHandled = false;
|
||||
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
final Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
mMessenger = extras.getParcelable(EXTRA_MESSENGER);
|
||||
|
||||
/**
|
||||
* com.android.crypto actions
|
||||
*/
|
||||
if (ACTION_REGISTER.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
@@ -125,44 +94,27 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
// user needs to select a key!
|
||||
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
|
||||
Toast.makeText(RemoteServiceActivity.this,
|
||||
R.string.api_register_error_select_key, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
mSettingsFragment.setErrorOnSelectKeyFragment(
|
||||
getString(R.string.api_register_error_select_key));
|
||||
} else {
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mSettingsFragment.getAppSettings());
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.OKAY;
|
||||
Bundle data = new Bundle();
|
||||
data.putString(RemoteService.RegisterActivityCallback.PACKAGE_NAME,
|
||||
packageName);
|
||||
msg.setData(data);
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
// give params through for new service call
|
||||
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, oldParams);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
}, R.string.api_register_disallow, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = RemoteService.RegisterActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -176,8 +128,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
||||
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
|
||||
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
|
||||
|
||||
showPassphraseDialog(secretKeyId);
|
||||
showPassphraseDialog(oldParams, secretKeyId);
|
||||
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
@@ -185,8 +138,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS);
|
||||
|
||||
String text = new String();
|
||||
text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
// TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids
|
||||
String text = "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>";
|
||||
text += "<br/><br/>";
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
text += getString(R.string.api_select_pub_keys_missing_text);
|
||||
@@ -213,40 +166,22 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY;
|
||||
Bundle data = new Bundle();
|
||||
data.putLongArray(
|
||||
OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS,
|
||||
// add key ids to params Bundle for new request
|
||||
Bundle params = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
|
||||
params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS,
|
||||
mSelectFragment.getSelectedMasterKeyIds());
|
||||
msg.setData(data);
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.btn_do_not_save, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL;
|
||||
;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -279,8 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
} else if (ACTION_ERROR_MESSAGE.equals(action)) {
|
||||
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
|
||||
|
||||
String text = new String();
|
||||
text += "<font color=\"red\">" + errorMessage + "</font>";
|
||||
String text = "<font color=\"red\">" + errorMessage + "</font>";
|
||||
|
||||
// Inflate a "Done" custom action bar view
|
||||
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay,
|
||||
@@ -288,7 +222,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,7 +233,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
|
||||
textView.setHtmlFromString(text);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Wrong action!");
|
||||
Log.e(Constants.TAG, "Action does not exist!");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -308,31 +244,21 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog(long secretKeyId) {
|
||||
private void showPassphraseDialog(final Bundle params, long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
// return given params again, for calling the service method again
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
|
||||
} else {
|
||||
Message msg = Message.obtain();
|
||||
msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoServiceActivity", e);
|
||||
}
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
finishHandled = true;
|
||||
finish();
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -345,9 +271,12 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, do pgp operation directly!");
|
||||
// return given params again, for calling the service method again
|
||||
Intent finishIntent = new Intent();
|
||||
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
|
||||
setResult(RESULT_OK, finishIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.sufficientlysecure.keychain.service.exception;
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
public class WrongPackageSignatureException extends Exception {
|
||||
|
||||
@@ -56,7 +56,7 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
/**
|
||||
* Signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class SignKeyActivity extends ActionBarActivity implements
|
||||
public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
||||
private BootstrapButton mSignButton;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
@@ -72,7 +72,7 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.sign_key_activity);
|
||||
setContentView(R.layout.certify_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
@@ -164,8 +164,8 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key!");
|
||||
// send message to handler to start certification directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
@@ -196,8 +196,8 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(mMasterKeyId);
|
||||
return; // bail out; need to wait until the user has entered the passphrase
|
||||
// before trying again
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
return;
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
@@ -218,13 +218,13 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
// Send all information needed to service to sign key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING);
|
||||
intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@@ -237,14 +237,12 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(SignKeyActivity.this, R.string.key_sign_success,
|
||||
Toast.makeText(CertifyKeyActivity.this, R.string.key_sign_success,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
// upload the newly signed key to the keyserver
|
||||
uploadKey();
|
||||
} else {
|
||||
setResult(RESULT_OK);
|
||||
@@ -291,7 +289,7 @@ public class SignKeyActivity extends ActionBarActivity implements
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Toast.makeText(SignKeyActivity.this, R.string.key_send_success,
|
||||
Toast.makeText(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
setResult(RESULT_OK);
|
||||
@@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
@@ -43,7 +43,6 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.LookupUnknownKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -61,7 +60,7 @@ import android.view.animation.AnimationUtils;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
@@ -78,14 +77,20 @@ public class DecryptActivity extends DrawerActivity {
|
||||
/* EXTRA keys for input */
|
||||
public static final String EXTRA_TEXT = "text";
|
||||
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
||||
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
|
||||
// TODO: replace signed only checks with something more intelligent
|
||||
// PgpDecryptVerify should handle all automatically!!!
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private LinearLayout mSignatureLayout = null;
|
||||
private RelativeLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
@@ -100,6 +105,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private BootstrapButton mBrowse = null;
|
||||
private BootstrapButton mLookupKey = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
@@ -107,14 +113,10 @@ public class DecryptActivity extends DrawerActivity {
|
||||
private Uri mContentUri = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
private boolean mLookupUnknownKey = true;
|
||||
|
||||
private boolean mDecryptImmediately = false;
|
||||
|
||||
private BootstrapButton mDecryptButton;
|
||||
@@ -154,7 +156,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mSignatureLayout = (LinearLayout) findViewById(R.id.signature);
|
||||
mSignatureLayout = (RelativeLayout) findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
@@ -171,7 +173,15 @@ public class DecryptActivity extends DrawerActivity {
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
|
||||
Id.request.filename);
|
||||
RESULT_CODE_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
mLookupKey = (BootstrapButton) findViewById(R.id.lookup_key);
|
||||
mLookupKey.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
lookupUnknownKey(mSignatureKeyId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,7 +249,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
DecryptActivity.this, mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEY_SERVER);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId);
|
||||
startActivity(intent);
|
||||
}
|
||||
@@ -263,14 +273,14 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
if (mDecryptImmediately
|
||||
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
decryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
@@ -287,13 +297,13 @@ public class DecryptActivity extends DrawerActivity {
|
||||
* Android's Action
|
||||
*/
|
||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||
// When sending to Keychain Encrypt via share menu
|
||||
// When sending to Keychain Decrypt via share menu
|
||||
if ("text/plain".equals(type)) {
|
||||
// Plain text
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
// handle like normal text decryption, override action and extras to later
|
||||
// execute ACTION_DECRYPT in main actions
|
||||
// executeServiceMethod ACTION_DECRYPT in main actions
|
||||
extras.putString(EXTRA_TEXT, sharedText);
|
||||
action = ACTION_DECRYPT;
|
||||
}
|
||||
@@ -375,21 +385,21 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +459,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
} else { // mDecryptTarget == Id.target.message
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
@@ -527,7 +537,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
try {
|
||||
if (inStream.markSupported()) {
|
||||
inStream.mark(200); // should probably set this to the max size of two pgpF
|
||||
// objects, if it even needs to be anything other than 0.
|
||||
// objects, if it even needs to be anything other than 0.
|
||||
}
|
||||
mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream);
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
@@ -539,7 +549,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
inStream.reset();
|
||||
}
|
||||
mSecretKeyId = Id.key.symmetric;
|
||||
if (!PgpOperation.hasSymmetricEncryption(this, inStream)) {
|
||||
if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
@@ -559,7 +569,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[]{mSignatureKeyId});
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -587,28 +597,10 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) {
|
||||
// the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment
|
||||
// starts a new Intent which then returns data
|
||||
} else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) {
|
||||
// decrypt again, but don't lookup unknown keys!
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment
|
||||
.newInstance(messenger, unknownKeyId);
|
||||
|
||||
lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog");
|
||||
Intent intent = new Intent(this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
@@ -644,8 +636,6 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId);
|
||||
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_SIGNED_ONLY, mSignedOnly);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_LOOKUP_UNKNOWN_KEY, mLookupUnknownKey);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
|
||||
|
||||
@@ -662,15 +652,6 @@ public class DecryptActivity extends DrawerActivity {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
// if key is unknown show lookup dialog
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY)
|
||||
&& mLookupUnknownKey) {
|
||||
mUnknownSignatureKeyId = returnData
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
lookupUnknownKey(mUnknownSignatureKeyId);
|
||||
return;
|
||||
}
|
||||
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
|
||||
@@ -685,26 +666,26 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
@@ -727,19 +708,24 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
} else if (returnData
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
Toast.makeText(DecryptActivity.this,
|
||||
R.string.unknown_signature_key_touch_to_look_up,
|
||||
R.string.unknown_signature,
|
||||
Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
@@ -756,36 +742,37 @@ public class DecryptActivity extends DrawerActivity {
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
case RESULT_CODE_FILE: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// ImportKeysActivity and user looked uo key
|
||||
case Id.request.look_up_key_id: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
// decrypt again without lookup
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// ImportKeysActivity and user looked uo key
|
||||
case RESULT_CODE_LOOKUP_KEY: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
if (resultCode == RESULT_OK) {
|
||||
// decrypt again
|
||||
decryptStart();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class EncryptActivity extends DrawerActivity {
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
// handle like normal text encryption, override action and extras to later
|
||||
// execute ACTION_ENCRYPT in main actions
|
||||
// executeServiceMethod ACTION_ENCRYPT in main actions
|
||||
extras.putString(EXTRA_TEXT, sharedText);
|
||||
extras.putBoolean(EXTRA_ASCII_ARMOR, true);
|
||||
action = ACTION_ENCRYPT;
|
||||
|
||||
@@ -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) {
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||
|
||||
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(1, null);
|
||||
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) {
|
||||
// also exposed in AndroidManifest
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
Log.d(Constants.TAG, "onNavigationItemSelected");
|
||||
|
||||
loadNavFragment(itemPosition, null);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
String keyServer = null;
|
||||
if (getArguments().containsKey(ARG_KEY_SERVER)) {
|
||||
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, "query: " + query);
|
||||
Log.d(Constants.TAG, "keyServer: " + keyServer);
|
||||
if (getArguments().containsKey(ARG_KEY_SERVER)) {
|
||||
String keyServer = getArguments().getString(ARG_KEY_SERVER);
|
||||
int keyServerPos = mServerAdapter.getPosition(keyServer);
|
||||
mServerSpinner.setSelection(keyServerPos);
|
||||
|
||||
Log.d(Constants.TAG, "keyServer: " + keyServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_encrypt: {
|
||||
encrypt(mode, ids);
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_public_multi_delete: {
|
||||
showDeleteKeyDialog(mode, ids);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case R.id.menu_key_list_public_multi_delete: {
|
||||
showDeleteKeyDialog(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");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 java.util.ArrayList;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
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;
|
||||
public static final String EXTRA_SELECTED_TAB = "selectedTab";
|
||||
|
||||
private ListView mUserIds;
|
||||
private ListView mKeys;
|
||||
ViewPager mViewPager;
|
||||
TabsAdapter mTabsAdapter;
|
||||
|
||||
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 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);
|
||||
mViewPager = (ViewPager) findViewById(R.id.pager);
|
||||
|
||||
loadData(getIntent());
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 -------------- */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user