Merge branch 'development' into linked-identities
Conflicts: Graphics/update-drawables.sh OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
This commit is contained in:
@@ -13,27 +13,27 @@ dependencies {
|
||||
|
||||
// JCenter etc.
|
||||
compile 'com.eftimoff:android-patternview:1.0.1@aar'
|
||||
compile 'com.journeyapps:zxing-android-embedded:2.1.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-integration:2.1.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
|
||||
compile 'com.google.zxing:core:3.2.0'
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||
compile 'it.neokree:MaterialNavigationDrawer:1.3.2'
|
||||
compile 'com.getbase:floatingactionbutton:1.9.0'
|
||||
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
||||
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
||||
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
|
||||
compile 'se.emilsjolander:stickylistheaders:2.6.0'
|
||||
compile 'org.sufficientlysecure:html-textview:1.1'
|
||||
|
||||
// libs as submodules
|
||||
compile project(':extern:openpgp-api-lib')
|
||||
compile project(':extern:openkeychain-api-lib')
|
||||
compile project(':extern:html-textview')
|
||||
compile project(':extern:StickyListHeaders:library')
|
||||
compile project(':extern:spongycastle:core')
|
||||
compile project(':extern:spongycastle:pg')
|
||||
compile project(':extern:spongycastle:pkix')
|
||||
compile project(':extern:spongycastle:prov')
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
compile project(':extern:TokenAutoComplete:library')
|
||||
compile project(':extern:safeslinger-exchange')
|
||||
compile project(':extern:snackbar:lib')
|
||||
}
|
||||
@@ -47,24 +47,24 @@ dependencyVerification {
|
||||
'com.android.support:recyclerview-v7:859ed80e3761f8fc3126901260b208505120b5678bcf36ad2cfe9c453958b9c7',
|
||||
'com.android.support:cardview-v7:4c03f2acce9925aa4f8845cb8cb37b3772c712b2438ff15f76c9e3d3bc63ead7',
|
||||
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
|
||||
'com.journeyapps:zxing-android-embedded:57fdf8a262135976201fd89f1bd8016ed16510be92e7ea721b999daeeeab8f7e',
|
||||
'com.journeyapps:zxing-android-integration:12caeb2608f11b6df77d27edc505ac8580abfc97a09a814b638cb9df0ba06906',
|
||||
'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
|
||||
'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
|
||||
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
'it.neokree:MaterialNavigationDrawer:a1221a410c5f71bf078c5c4768fdf06b402d6006c74f8e7b61199e4edc2aea57',
|
||||
'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca',
|
||||
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
|
||||
'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb',
|
||||
'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4',
|
||||
'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988',
|
||||
// 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
|
||||
// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
|
||||
// 'OpenKeychain.extern:html-textview:536822e8fdcd3e4628d0a1cd6c252285ba5f8e5bfb20d71ff80fdbdb6cc8be8c',
|
||||
// 'OpenKeychain.extern.StickyListHeaders:library:d9937cf9d9992863e32cee1f18ffec12df7b97dd83939bb75ee6cf747c54bed1',
|
||||
// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
|
||||
// 'com.madgag.spongycastle:pg:160b345b10a2c92dc731453eec87037377f66a8e14a0648d404d7b193c4e380d',
|
||||
// 'com.madgag.spongycastle:pkix:0b4f3301ea12dd9f25d71770e6ea9f75e0611bf53062543e47be5bc15340a7e4',
|
||||
// 'com.madgag.spongycastle:prov:7325942e0b39f5fb35d6380818eed4b826e7dfc7570ad35b696d778049d8c36a',
|
||||
// 'OpenKeychain.extern:minidns:77b1786d29469e3b21f9404827cab811edc857cd68bc732cd57f11307c332eae',
|
||||
// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
|
||||
// 'OpenKeychain.extern.TokenAutoComplete:library:9333f1c269996812baa18c0494e42f931309ab00a3cdb65a6e4d70f82d4c7107',
|
||||
// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
|
||||
// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
|
||||
'com.android.support:support-annotations:ab6b131ab0e1edd165d21fb4c3edadeacbee9539aa166f7f7cbae05b60dc207a',
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
android:name=".ui.CreateKeyActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:label="@string/title_manage_my_keys"
|
||||
android:launchMode="singleTop"
|
||||
android:allowTaskReparenting="true"
|
||||
android:parentActivityName=".ui.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@@ -673,7 +675,7 @@
|
||||
taskAffinity and allowTaskReparenting somehow prevents this from happening!
|
||||
-->
|
||||
<activity
|
||||
android:name=".ui.NfcActivity"
|
||||
android:name=".ui.NfcOperationActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=":Nfc"
|
||||
android:allowTaskReparenting="true" />
|
||||
@@ -698,6 +700,10 @@
|
||||
android:name=".service.PassphraseCacheService"
|
||||
android:exported="false"
|
||||
android:process=":passphrase_cache" />
|
||||
<service
|
||||
android:name=".remote.CryptoInputParcelCacheService"
|
||||
android:exported="false"
|
||||
android:process=":remote_api" />
|
||||
<service
|
||||
android:name=".service.KeychainIntentService"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -14,8 +14,12 @@ import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class is based on JcaPGPContentSignerBuilder.
|
||||
@@ -31,31 +35,27 @@ public class NfcSyncPGPContentSignerBuilder
|
||||
private int keyAlgorithm;
|
||||
private long keyID;
|
||||
|
||||
private byte[] signedHash;
|
||||
private Date creationTimestamp;
|
||||
private Map signedHashes;
|
||||
|
||||
public static class NfcInteractionNeeded extends RuntimeException
|
||||
{
|
||||
public byte[] hashToSign;
|
||||
public Date creationTimestamp;
|
||||
public int hashAlgo;
|
||||
|
||||
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp)
|
||||
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo)
|
||||
{
|
||||
super("NFC interaction required!");
|
||||
this.hashToSign = hashToSign;
|
||||
this.hashAlgo = hashAlgo;
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, byte[] signedHash, Date creationTimestamp)
|
||||
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, Map signedHashes)
|
||||
{
|
||||
this.keyAlgorithm = keyAlgorithm;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.keyID = keyID;
|
||||
this.signedHash = signedHash;
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
this.signedHashes = signedHashes;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
|
||||
@@ -125,14 +125,14 @@ public class NfcSyncPGPContentSignerBuilder
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
if (signedHash != null) {
|
||||
// we already have the signed hash from a previous execution, return this!
|
||||
return signedHash;
|
||||
} else {
|
||||
// catch this when signatureGenerator.generate() is executed and divert digest to card,
|
||||
// when doing the operation again reuse creationTimestamp (this will be hashed)
|
||||
throw new NfcInteractionNeeded(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp);
|
||||
byte[] digest = digestCalculator.getDigest();
|
||||
ByteBuffer buf = ByteBuffer.wrap(digest);
|
||||
if (signedHashes.containsKey(buf)) {
|
||||
return (byte[]) signedHashes.get(buf);
|
||||
}
|
||||
// catch this when signatureGenerator.generate() is executed and divert digest to card,
|
||||
// when doing the operation again reuse creationTimestamp (this will be hashed)
|
||||
throw new NfcInteractionNeeded(digest, getHashAlgorithm());
|
||||
}
|
||||
|
||||
public byte[] getDigest()
|
||||
|
||||
@@ -15,7 +15,10 @@ import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.operator.PGPDataDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class is based on JcePublicKeyDataDecryptorFactoryBuilder
|
||||
@@ -88,7 +91,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public PublicKeyDataDecryptorFactory build(final byte[] nfcDecrypted) {
|
||||
public PublicKeyDataDecryptorFactory build(final Map<ByteBuffer,byte[]> nfcDecryptedMap) {
|
||||
return new PublicKeyDataDecryptorFactory()
|
||||
{
|
||||
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
|
||||
@@ -99,7 +102,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
throw new PGPException("ECDH not supported!");
|
||||
}
|
||||
|
||||
return decryptSessionData(keyAlgorithm, secKeyData, nfcDecrypted);
|
||||
return decryptSessionData(keyAlgorithm, secKeyData, nfcDecryptedMap);
|
||||
}
|
||||
|
||||
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
|
||||
@@ -197,8 +200,9 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
// }
|
||||
// }
|
||||
|
||||
private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData, byte[] nfcDecrypted)
|
||||
throws PGPException
|
||||
private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData,
|
||||
Map<ByteBuffer,byte[]> nfcDecryptedMap)
|
||||
throws PGPException
|
||||
{
|
||||
// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
|
||||
//
|
||||
@@ -214,15 +218,14 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
|
||||
|| keyAlgorithm == PGPPublicKey.RSA_GENERAL)
|
||||
{
|
||||
byte[] bi = secKeyData[0]; // encoded MPI
|
||||
ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
|
||||
|
||||
if (nfcDecrypted != null) {
|
||||
// we already have the decrypted bytes from a previous execution, return this!
|
||||
return nfcDecrypted;
|
||||
if (nfcDecryptedMap.containsKey(bi)) {
|
||||
return nfcDecryptedMap.get(bi);
|
||||
} else {
|
||||
// catch this when decryptSessionData() is executed and divert digest to card,
|
||||
// when doing the operation again reuse nfcDecrypted
|
||||
throw new NfcInteractionNeeded(bi);
|
||||
throw new NfcInteractionNeeded(bi.array());
|
||||
}
|
||||
|
||||
// c1.update(bi, 2, bi.length - 2);
|
||||
|
||||
@@ -28,8 +28,9 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
@@ -38,6 +39,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@@ -60,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public CertifyResult certify(CertifyActionsParcel parcel, String keyServerUri) {
|
||||
public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_CRT, 0);
|
||||
@@ -74,13 +78,14 @@ public class CertifyOperation extends BaseOperation {
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
|
||||
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
||||
certificationKey = secretKeyRing.getSecretKey();
|
||||
if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
||||
log.add(LogType.MSG_CRT_ERROR_DIVERT, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
|
||||
if (!cryptoInput.hasPassphrase()) {
|
||||
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
|
||||
}
|
||||
|
||||
// certification is always with the master key id, so use that one
|
||||
Passphrase passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId);
|
||||
Passphrase passphrase = cryptoInput.getPassphrase();
|
||||
|
||||
if (!certificationKey.unlock(passphrase)) {
|
||||
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
||||
@@ -92,9 +97,6 @@ public class CertifyOperation extends BaseOperation {
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
} catch (NoSecretKeyException e) {
|
||||
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>();
|
||||
@@ -103,6 +105,10 @@ public class CertifyOperation extends BaseOperation {
|
||||
|
||||
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
|
||||
|
||||
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
|
||||
cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
|
||||
certificationKey.getKeyId());
|
||||
|
||||
// Work through all requested certifications
|
||||
for (CertifyAction action : parcel.mCertifyActions) {
|
||||
|
||||
@@ -123,28 +129,21 @@ public class CertifyOperation extends BaseOperation {
|
||||
CanonicalizedPublicKeyRing publicRing =
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId);
|
||||
|
||||
UncachedKeyRing certifiedKey = null;
|
||||
if (action.mUserIds != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
PgpCertifyOperation op = new PgpCertifyOperation();
|
||||
PgpCertifyResult result = op.certify(certificationKey, publicRing,
|
||||
log, 2, action, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
|
||||
|
||||
certifiedKey = certificationKey.certifyUserIds(
|
||||
publicRing, action.mUserIds, null, null);
|
||||
}
|
||||
|
||||
if (action.mUserAttributes != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
certifiedKey = certificationKey.certifyUserAttributes(
|
||||
publicRing, action.mUserAttributes, null, null);
|
||||
}
|
||||
|
||||
if (certifiedKey == null) {
|
||||
if (!result.success()) {
|
||||
certifyError += 1;
|
||||
log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3);
|
||||
continue;
|
||||
}
|
||||
certifiedKeys.add(certifiedKey);
|
||||
if (result.nfcInputRequired()) {
|
||||
RequiredInputParcel requiredInput = result.getRequiredInput();
|
||||
allRequiredInput.addAll(requiredInput);
|
||||
continue;
|
||||
}
|
||||
|
||||
certifiedKeys.add(result.getCertifiedRing());
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
certifyError += 1;
|
||||
@@ -153,6 +152,11 @@ public class CertifyOperation extends BaseOperation {
|
||||
|
||||
}
|
||||
|
||||
if ( ! allRequiredInput.isEmpty()) {
|
||||
log.add(LogType.MSG_CRT_NFC_RETURN, 1);
|
||||
return new CertifyResult(log, allRequiredInput.build());
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_CRT_SAVING, 1);
|
||||
|
||||
// Check if we were cancelled
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@@ -56,7 +58,7 @@ public class EditKeyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public EditKeyResult execute(SaveKeyringParcel saveParcel, Passphrase passphrase) {
|
||||
public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_ED, 0);
|
||||
@@ -81,7 +83,10 @@ public class EditKeyOperation extends BaseOperation {
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
|
||||
if (modifyResult.isPending()) {
|
||||
return modifyResult;
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2);
|
||||
|
||||
@@ -230,7 +230,7 @@ public class ImportExportOperation extends BaseOperation {
|
||||
}
|
||||
} catch (Keyserver.QueryFailedException e) {
|
||||
Log.e(Constants.TAG, "query failed", e);
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class PromoteKeyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public PromoteKeyResult execute(long masterKeyId) {
|
||||
public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_PR, 0);
|
||||
@@ -58,27 +58,16 @@ public class PromoteKeyOperation extends BaseOperation {
|
||||
// Perform actual type change
|
||||
UncachedKeyRing promotedRing;
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
// This operation is only allowed for pure public keys
|
||||
// TODO delete secret keys if they are stripped, or have been moved to the card?
|
||||
if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) {
|
||||
log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_PR_FETCHING, 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
|
||||
CanonicalizedPublicKeyRing pubRing =
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
// create divert-to-card secret key from public key
|
||||
promotedRing = pubRing.createDummySecretRing();
|
||||
promotedRing = pubRing.createDivertSecretRing(cardAid);
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
|
||||
@@ -20,14 +20,21 @@ package org.sufficientlysecure.keychain.operations;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@@ -55,7 +62,7 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public SignEncryptResult execute(SignEncryptParcel input) {
|
||||
public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_SE, 0);
|
||||
@@ -68,6 +75,21 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
|
||||
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
|
||||
|
||||
NfcSignOperationsBuilder pendingInputBuilder = null;
|
||||
|
||||
// if signing subkey has not explicitly been set, get first usable subkey capable of signing
|
||||
if (input.getSignatureMasterKeyId() != Constants.key.none
|
||||
&& input.getSignatureSubKeyId() == null) {
|
||||
try {
|
||||
long signKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
input.getSignatureMasterKeyId()).getSecretSignId();
|
||||
input.setSignatureSubKeyId(signKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
if (checkCancelled()) {
|
||||
@@ -123,15 +145,22 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper,
|
||||
new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled);
|
||||
PgpSignEncryptResult result = op.execute(input, inputData, outStream);
|
||||
PgpSignEncryptResult result = op.execute(input, cryptoInput, inputData, outStream);
|
||||
results.add(result);
|
||||
log.add(result, 2);
|
||||
|
||||
if (result.isPending()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results);
|
||||
}
|
||||
|
||||
if (!result.success()) {
|
||||
RequiredInputParcel requiredInput = result.getRequiredInputParcel();
|
||||
// Passphrase returns immediately, nfc are aggregated
|
||||
if (requiredInput.mType == RequiredInputType.PASSPHRASE) {
|
||||
return new SignEncryptResult(log, requiredInput, results);
|
||||
}
|
||||
if (pendingInputBuilder == null) {
|
||||
pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
|
||||
input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
|
||||
}
|
||||
pendingInputBuilder.addAll(requiredInput);
|
||||
} else if (!result.success()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
|
||||
@@ -141,9 +170,12 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
|
||||
} while (!inputUris.isEmpty());
|
||||
|
||||
if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) {
|
||||
return new SignEncryptResult(log, pendingInputBuilder.build(), results);
|
||||
}
|
||||
|
||||
if (!outputUris.isEmpty()) {
|
||||
// Any output URIs left are indicative of a programming error
|
||||
log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1);
|
||||
throw new AssertionError("Got outputs left but no inputs. This is a programming error, please report!");
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_SE_SUCCESS, 1);
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
@@ -30,16 +31,19 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
public class CertifyResult extends OperationResult {
|
||||
|
||||
public class CertifyResult extends InputPendingResult {
|
||||
int mCertifyOk, mCertifyError, mUploadOk, mUploadError;
|
||||
|
||||
public CertifyResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
|
||||
this(result, log);
|
||||
super(result, log);
|
||||
mCertifyOk = certifyOk;
|
||||
mCertifyError = certifyError;
|
||||
mUploadOk = uploadOk;
|
||||
|
||||
@@ -22,23 +22,10 @@ import android.os.Parcel;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class DecryptVerifyResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_ASYM_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_SYM_PASSPHRASE = RESULT_PENDING + 32;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 64;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcSubKeyId;
|
||||
byte[] mNfcSessionKey;
|
||||
Passphrase mNfcPassphrase;
|
||||
public class DecryptVerifyResult extends InputPendingResult {
|
||||
|
||||
OpenPgpSignatureResult mSignatureResult;
|
||||
OpenPgpMetadata mDecryptMetadata;
|
||||
@@ -46,32 +33,6 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
// https://tools.ietf.org/html/rfc4880#page56
|
||||
String mCharset;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcState(long subKeyId, byte[] sessionKey, Passphrase passphrase) {
|
||||
mNfcSubKeyId = subKeyId;
|
||||
mNfcSessionKey = sessionKey;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public long getNfcSubKeyId() {
|
||||
return mNfcSubKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcEncryptedSessionKey() {
|
||||
return mNfcSessionKey;
|
||||
}
|
||||
|
||||
public Passphrase getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult getSignatureResult() {
|
||||
return mSignatureResult;
|
||||
}
|
||||
@@ -104,13 +65,14 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public DecryptVerifyResult(Parcel source) {
|
||||
super(source);
|
||||
mKeyIdPassphraseNeeded = source.readLong();
|
||||
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
||||
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
||||
mNfcSessionKey = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
@@ -119,16 +81,8 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mKeyIdPassphraseNeeded);
|
||||
dest.writeParcelable(mSignatureResult, 0);
|
||||
dest.writeParcelable(mDecryptMetadata, 0);
|
||||
if (mNfcSessionKey != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcSessionKey);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeParcelable(mNfcPassphrase, flags);
|
||||
}
|
||||
|
||||
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
|
||||
|
||||
@@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult {
|
||||
|
||||
public EditKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mMasterKeyId = source.readLong();
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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.operations.results;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
public class InputPendingResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
final RequiredInputParcel mRequiredInput;
|
||||
|
||||
public InputPendingResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
mRequiredInput = null;
|
||||
}
|
||||
|
||||
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(RESULT_PENDING, log);
|
||||
mRequiredInput = requiredInput;
|
||||
}
|
||||
|
||||
public InputPendingResult(Parcel source) {
|
||||
super(source);
|
||||
mRequiredInput = source.readParcelable(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeParcelable(mRequiredInput, 0);
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public RequiredInputParcel getRequiredInputParcel() {
|
||||
return mRequiredInput;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,75 +33,33 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableCache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** Represent the result of an operation.
|
||||
/**
|
||||
* Represent the result of an operation.
|
||||
*
|
||||
* This class holds a result and the log of an operation. It can be subclassed
|
||||
* to include typed additional information specific to the operation. To keep
|
||||
* the class structure (somewhat) simple, this class contains an exhaustive
|
||||
* list (ie, enum) of all possible log types, which should in all cases be tied
|
||||
* to string resource ids.
|
||||
*
|
||||
*/
|
||||
public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public static final String EXTRA_RESULT = "operation_result";
|
||||
public static final UUID NULL_UUID = new UUID(0,0);
|
||||
|
||||
/**
|
||||
* A HashMap of UUID:OperationLog which contains logs that we don't need
|
||||
* to care about. This is used such that when we become parceled, we are
|
||||
* well below the 1Mbit boundary that is specified.
|
||||
* Instead of parceling the logs, they are cached to overcome the 1 MB boundary of
|
||||
* Android's Binder. See ParcelableCache
|
||||
*/
|
||||
private static ConcurrentHashMap<UUID, OperationLog> dehydratedLogs;
|
||||
private static ParcelableCache<OperationLog> logCache;
|
||||
static {
|
||||
// Static initializer for ConcurrentHashMap
|
||||
dehydratedLogs = new ConcurrentHashMap<UUID,OperationLog>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dehydrate a log (such that it is available after deparcelization)
|
||||
*
|
||||
* Returns the NULL uuid (0) if you hand it null.
|
||||
* @param log An OperationLog to dehydrate
|
||||
* @return a UUID, the ticket for your dehydrated log
|
||||
*
|
||||
*/
|
||||
private static UUID dehydrateLog(OperationLog log) {
|
||||
if(log == null) {
|
||||
return NULL_UUID;
|
||||
}
|
||||
else {
|
||||
UUID ticket = UUID.randomUUID();
|
||||
dehydratedLogs.put(ticket, log);
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Rehydrate a log after going through parcelization, invalidating its place in the
|
||||
* dehydration pool.
|
||||
* This is used such that when parcelized, the parcel is no larger than 1mbit.
|
||||
* @param ticket A UUID ticket that identifies the log in question.
|
||||
* @return An OperationLog.
|
||||
*/
|
||||
private static OperationLog rehydrateLog(UUID ticket) {
|
||||
// UUID.equals isn't well documented; we use compareTo instead.
|
||||
if( NULL_UUID.compareTo(ticket) == 0 ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
OperationLog log = dehydratedLogs.get(ticket);
|
||||
dehydratedLogs.remove(ticket);
|
||||
return log;
|
||||
}
|
||||
logCache = new ParcelableCache<>();
|
||||
}
|
||||
|
||||
/** Holds the overall result, the number specifying varying degrees of success:
|
||||
@@ -126,11 +84,8 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public OperationResult(Parcel source) {
|
||||
mResult = source.readInt();
|
||||
long mostSig = source.readLong();
|
||||
long leastSig = source.readLong();
|
||||
UUID mTicket = new UUID(mostSig, leastSig);
|
||||
// fetch the dehydrated log out of storage (this removes it from the dehydration pool)
|
||||
mLog = rehydrateLog(mTicket);
|
||||
// get log out of cache based on UUID from source
|
||||
mLog = logCache.readFromParcelAndGetFromCache(source);
|
||||
}
|
||||
|
||||
public int getResult() {
|
||||
@@ -250,12 +205,20 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast());
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType);
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId());
|
||||
|
||||
// Take the last message as string
|
||||
int msgId = mLog.getLast().mType.getMsgId();
|
||||
String logText;
|
||||
|
||||
LogEntryParcel entryParcel = mLog.getLast();
|
||||
// special case: first parameter may be a quantity
|
||||
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
||||
&& entryParcel.mParameters[0] instanceof Integer) {
|
||||
logText = activity.getResources().getQuantityString(entryParcel.mType.getMsgId(),
|
||||
(Integer) entryParcel.mParameters[0],
|
||||
entryParcel.mParameters);
|
||||
} else {
|
||||
logText = activity.getString(entryParcel.mType.getMsgId(),
|
||||
entryParcel.mParameters);
|
||||
}
|
||||
|
||||
Style style;
|
||||
|
||||
@@ -273,19 +236,19 @@ public abstract class OperationResult implements Parcelable {
|
||||
}
|
||||
|
||||
if (getLog() == null || getLog().isEmpty()) {
|
||||
return Notify.create(activity, msgId, Notify.LENGTH_LONG, style);
|
||||
return Notify.create(activity, logText, Notify.LENGTH_LONG, style);
|
||||
}
|
||||
|
||||
return Notify.create(activity, msgId, Notify.LENGTH_LONG, style,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
return Notify.create(activity, logText, Notify.LENGTH_LONG, style,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
@@ -512,6 +475,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (LogLevel.START, R.string.msg_mr),
|
||||
MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
|
||||
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
|
||||
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
|
||||
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
|
||||
@@ -521,6 +485,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_MF_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_NOEXIST_REVOKE (LogLevel.ERROR, R.string.msg_mf_error_noexist_revoke),
|
||||
MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop),
|
||||
MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry),
|
||||
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
|
||||
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
|
||||
@@ -538,6 +503,9 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (LogLevel.DEBUG, R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (LogLevel.DEBUG, R.string.msg_mf_primary_new),
|
||||
MSG_MF_RESTRICTED_MODE (LogLevel.INFO, R.string.msg_mf_restricted_mode),
|
||||
MSG_MF_REQUIRE_DIVERT (LogLevel.OK, R.string.msg_mf_require_divert),
|
||||
MSG_MF_REQUIRE_PASSPHRASE (LogLevel.OK, R.string.msg_mf_require_passphrase),
|
||||
MSG_MF_SUBKEY_CHANGE (LogLevel.INFO, R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_NEW_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id),
|
||||
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
|
||||
@@ -590,13 +558,11 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// promote key
|
||||
MSG_PR (LogLevel.START, R.string.msg_pr),
|
||||
MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret),
|
||||
MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
|
||||
MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
|
||||
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
|
||||
|
||||
// messages used in UI code
|
||||
MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert),
|
||||
MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
|
||||
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
|
||||
|
||||
@@ -660,7 +626,6 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_SE_ERROR_INPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_input_uri_not_found),
|
||||
MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_output_uri_not_found),
|
||||
MSG_SE_ERROR_TOO_MANY_INPUTS (LogLevel.ERROR, R.string.msg_se_error_too_many_inputs),
|
||||
MSG_SE_WARN_OUTPUT_LEFT (LogLevel.WARN, R.string.msg_se_warn_output_left),
|
||||
MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success),
|
||||
|
||||
// pgpsignencrypt
|
||||
@@ -697,9 +662,9 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found),
|
||||
MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing),
|
||||
MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock),
|
||||
MSG_CRT_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_crt_error_divert),
|
||||
MSG_CRT (LogLevel.START, R.string.msg_crt),
|
||||
MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch),
|
||||
MSG_CRT_NFC_RETURN (LogLevel.OK, R.string.msg_crt_nfc_return),
|
||||
MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save),
|
||||
MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving),
|
||||
MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success),
|
||||
@@ -823,11 +788,8 @@ public abstract class OperationResult implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mResult);
|
||||
// Get a ticket for our log.
|
||||
UUID mTicket = dehydrateLog(mLog);
|
||||
// And write out the UUID most and least significant bits.
|
||||
dest.writeLong(mTicket.getMostSignificantBits());
|
||||
dest.writeLong(mTicket.getLeastSignificantBits());
|
||||
// cache log and write UUID to dest
|
||||
logCache.cacheAndWriteToParcel(mLog, dest);
|
||||
}
|
||||
|
||||
public static class OperationLog implements Iterable<LogEntryParcel> {
|
||||
|
||||
@@ -22,8 +22,10 @@ import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
public class PgpEditKeyResult extends OperationResult {
|
||||
|
||||
public class PgpEditKeyResult extends InputPendingResult {
|
||||
|
||||
private transient UncachedKeyRing mRing;
|
||||
public final long mRingMasterKeyId;
|
||||
@@ -35,6 +37,11 @@ public class PgpEditKeyResult extends OperationResult {
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
|
||||
}
|
||||
|
||||
public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
mRingMasterKeyId = Constants.key.none;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getRing() {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
@@ -19,85 +19,31 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class PgpSignEncryptResult extends OperationResult {
|
||||
public class PgpSignEncryptResult extends InputPendingResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcKeyId;
|
||||
byte[] mNfcHash;
|
||||
int mNfcAlgo;
|
||||
Date mNfcTimestamp;
|
||||
Passphrase mNfcPassphrase;
|
||||
byte[] mDetachedSignature;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, Passphrase passphrase) {
|
||||
mNfcKeyId = nfcKeyId;
|
||||
mNfcHash = nfcHash;
|
||||
mNfcAlgo = nfcAlgo;
|
||||
mNfcTimestamp = nfcTimestamp;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public void setDetachedSignature(byte[] detachedSignature) {
|
||||
mDetachedSignature = detachedSignature;
|
||||
}
|
||||
|
||||
public long getNfcKeyId() {
|
||||
return mNfcKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcHash() {
|
||||
return mNfcHash;
|
||||
}
|
||||
|
||||
public int getNfcAlgo() {
|
||||
return mNfcAlgo;
|
||||
}
|
||||
|
||||
public Date getNfcTimestamp() {
|
||||
return mNfcTimestamp;
|
||||
}
|
||||
|
||||
public Passphrase getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public byte[] getDetachedSignature() {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(Parcel source) {
|
||||
super(source);
|
||||
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcAlgo = source.readInt();
|
||||
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
}
|
||||
|
||||
@@ -107,19 +53,6 @@ public class PgpSignEncryptResult extends OperationResult {
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
if (mNfcHash != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcHash);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mNfcAlgo);
|
||||
if (mNfcTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mDetachedSignature != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mDetachedSignature);
|
||||
@@ -138,4 +71,4 @@ public class PgpSignEncryptResult extends OperationResult {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult {
|
||||
|
||||
public PromoteKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mMasterKeyId = source.readLong();
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {
|
||||
|
||||
@@ -21,20 +21,17 @@ import android.os.Parcel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SignEncryptResult extends OperationResult {
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
|
||||
public class SignEncryptResult extends InputPendingResult {
|
||||
|
||||
ArrayList<PgpSignEncryptResult> mResults;
|
||||
byte[] mResultBytes;
|
||||
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
public PgpSignEncryptResult getPending() {
|
||||
for (PgpSignEncryptResult sub : mResults) {
|
||||
if (sub.isPending()) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) {
|
||||
super(log, requiredInput);
|
||||
mResults = results;
|
||||
}
|
||||
|
||||
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results) {
|
||||
|
||||
@@ -98,11 +98,14 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDummySecretRing () {
|
||||
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
|
||||
S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
|
||||
return new UncachedKeyRing(secRing);
|
||||
}
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDivertSecretRing (byte[] cardAid) {
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
|
||||
return new UncachedKeyRing(secRing);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,13 +40,17 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for a PGPSecretKey.
|
||||
@@ -184,13 +188,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
return PgpConstants.sPreferredHashAlgorithms;
|
||||
}
|
||||
|
||||
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash,
|
||||
Date nfcCreationTimestamp) {
|
||||
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
|
||||
Map<ByteBuffer,byte[]> signedHashes) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||
// use synchronous "NFC based" SignerBuilder
|
||||
return new NfcSyncPGPContentSignerBuilder(
|
||||
mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
|
||||
mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp)
|
||||
mSecretKey.getKeyID(), signedHashes)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
} else {
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
@@ -200,29 +204,43 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
|
||||
byte[] nfcSignedHash, Date nfcCreationTimestamp)
|
||||
throws PgpGeneralException {
|
||||
public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
|
||||
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (nfcSignedHash != null && nfcCreationTimestamp == null) {
|
||||
throw new PgpGeneralException("Got nfc hash without timestamp!!");
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
return signatureGenerator;
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public PGPSignatureGenerator getDataSignatureGenerator(int hashAlgo, boolean cleartext,
|
||||
Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp)
|
||||
throws PgpGeneralException {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
|
||||
// We explicitly create a signature creation timestamp in this place.
|
||||
// That way, we can inject an artificial one from outside, ie the one
|
||||
// used in previous runs of this function.
|
||||
if (nfcCreationTimestamp == null) {
|
||||
if (creationTimestamp == null) {
|
||||
// to sign using nfc PgpSignEncrypt is executed two times.
|
||||
// the first time it stops to return the PendingIntent for nfc connection and signing the hash
|
||||
// the second time the signed hash is used.
|
||||
// to get the same hash we cache the timestamp for the second round!
|
||||
nfcCreationTimestamp = new Date();
|
||||
creationTimestamp = new Date();
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo,
|
||||
nfcSignedHash, nfcCreationTimestamp);
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes);
|
||||
|
||||
int signatureType;
|
||||
if (cleartext) {
|
||||
@@ -238,7 +256,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
spGen.setSignatureCreationTime(false, creationTimestamp);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
return signatureGenerator;
|
||||
} catch (PgpKeyNotFoundException | PGPException e) {
|
||||
@@ -247,145 +265,24 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
public PublicKeyDataDecryptorFactory getDecryptorFactory(byte[] nfcDecryptedSessionKey) {
|
||||
public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||
return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(nfcDecryptedSessionKey);
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getCryptoData()
|
||||
);
|
||||
} else {
|
||||
return new JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Certify the given pubkeyid with the given masterkeyid.
|
||||
*
|
||||
* @param publicKeyRing Keyring to add certification to.
|
||||
* @param userIds User IDs to certify
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds,
|
||||
byte[] nfcSignedHash, Date nfcCreationTimestamp) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (!isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator;
|
||||
{
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (nfcCreationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
try {
|
||||
for (String userId : userIds) {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
|
||||
|
||||
return new UncachedKeyRing(ring);
|
||||
}
|
||||
|
||||
/**
|
||||
* Certify the given user attributes with the given masterkeyid.
|
||||
*
|
||||
* @param publicKeyRing Keyring to add certification to.
|
||||
* @param userAttributes User IDs to certify, or all if null
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing,
|
||||
List<WrappedUserAttribute> userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (!isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator;
|
||||
{
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (nfcCreationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
try {
|
||||
for (WrappedUserAttribute userAttribute : userAttributes) {
|
||||
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
|
||||
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
|
||||
|
||||
return new UncachedKeyRing(ring);
|
||||
public byte[] getIv() {
|
||||
return mSecretKey.getIV();
|
||||
}
|
||||
|
||||
static class PrivateKeyNotUnlockedException extends RuntimeException {
|
||||
@@ -402,4 +299,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
return mPrivateKey;
|
||||
}
|
||||
|
||||
// HACK, for TESTING ONLY!!
|
||||
PGPSecretKey getSecretKey() {
|
||||
return mSecretKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
public class PgpCertifyOperation {
|
||||
|
||||
public PgpCertifyResult certify(
|
||||
CanonicalizedSecretKey secretKey,
|
||||
CanonicalizedPublicKeyRing publicRing,
|
||||
OperationLog log,
|
||||
int indent,
|
||||
CertifyAction action,
|
||||
Map<ByteBuffer,byte[]> signedHashes,
|
||||
Date creationTimestamp) {
|
||||
|
||||
if (!secretKey.isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicRing.getMasterKeyId() == secretKey.getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator = secretKey.getCertSignatureGenerator(signedHashes);
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (creationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, creationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
|
||||
|
||||
NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp,
|
||||
publicKey.getKeyID(), publicKey.getKeyID());
|
||||
|
||||
try {
|
||||
if (action.mUserIds != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (String userId : action.mUserIds) {
|
||||
try {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
requiredInput.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (action.mUserAttributes != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (WrappedUserAttribute userAttribute : action.mUserAttributes) {
|
||||
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
|
||||
try {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
requiredInput.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return new PgpCertifyResult();
|
||||
}
|
||||
|
||||
if (!requiredInput.isEmpty()) {
|
||||
return new PgpCertifyResult(requiredInput.build());
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicRing.getRing(), publicKey);
|
||||
return new PgpCertifyResult(new UncachedKeyRing(ring));
|
||||
|
||||
}
|
||||
|
||||
public static class PgpCertifyResult {
|
||||
|
||||
final RequiredInputParcel mRequiredInput;
|
||||
final UncachedKeyRing mCertifiedRing;
|
||||
|
||||
PgpCertifyResult() {
|
||||
mRequiredInput = null;
|
||||
mCertifiedRing = null;
|
||||
}
|
||||
|
||||
PgpCertifyResult(RequiredInputParcel requiredInput) {
|
||||
mRequiredInput = requiredInput;
|
||||
mCertifiedRing = null;
|
||||
}
|
||||
|
||||
PgpCertifyResult(UncachedKeyRing certifiedRing) {
|
||||
mRequiredInput = null;
|
||||
mCertifiedRing = certifiedRing;
|
||||
}
|
||||
|
||||
public boolean success() {
|
||||
return mCertifiedRing != null || mRequiredInput != null;
|
||||
}
|
||||
|
||||
public boolean nfcInputRequired() {
|
||||
return mRequiredInput != null;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getCertifiedRing() {
|
||||
return mCertifiedRing;
|
||||
}
|
||||
|
||||
public RequiredInputParcel getRequiredInput() {
|
||||
return mRequiredInput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,16 +47,15 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -84,10 +83,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private OutputStream mOutStream;
|
||||
|
||||
private boolean mAllowSymmetricDecryption;
|
||||
private Passphrase mPassphrase;
|
||||
private Set<Long> mAllowedKeyIds;
|
||||
private boolean mDecryptMetadataOnly;
|
||||
private byte[] mDecryptedSessionKey;
|
||||
private byte[] mDetachedSignature;
|
||||
private String mRequiredSignerFingerprint;
|
||||
private boolean mSignedLiteralData;
|
||||
@@ -100,10 +97,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
this.mOutStream = builder.mOutStream;
|
||||
|
||||
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
|
||||
this.mPassphrase = builder.mPassphrase;
|
||||
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
||||
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
||||
this.mDetachedSignature = builder.mDetachedSignature;
|
||||
this.mSignedLiteralData = builder.mSignedLiteralData;
|
||||
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
||||
@@ -119,10 +114,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private OutputStream mOutStream = null;
|
||||
private Progressable mProgressable = null;
|
||||
private boolean mAllowSymmetricDecryption = true;
|
||||
private Passphrase mPassphrase = null;
|
||||
private Set<Long> mAllowedKeyIds = null;
|
||||
private boolean mDecryptMetadataOnly = false;
|
||||
private byte[] mDecryptedSessionKey = null;
|
||||
private byte[] mDetachedSignature = null;
|
||||
private String mRequiredSignerFingerprint = null;
|
||||
private boolean mSignedLiteralData = false;
|
||||
@@ -160,11 +153,6 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow these key ids alone for decryption.
|
||||
* This means only ciphertexts encrypted for one of these private key can be decrypted.
|
||||
@@ -183,11 +171,6 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNfcState(byte[] decryptedSessionKey) {
|
||||
mDecryptedSessionKey = decryptedSessionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If detachedSignature != null, it will be used exclusively to verify the signature
|
||||
*/
|
||||
@@ -204,7 +187,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Decrypts and/or verifies data based on parameters of class
|
||||
*/
|
||||
public DecryptVerifyResult execute() {
|
||||
public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
|
||||
try {
|
||||
if (mDetachedSignature != null) {
|
||||
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
||||
@@ -226,10 +209,10 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
} else {
|
||||
// else: ascii armored encryption! go on...
|
||||
return decryptVerify(in, 0);
|
||||
return decryptVerify(cryptoInput, in, 0);
|
||||
}
|
||||
} else {
|
||||
return decryptVerify(in, 0);
|
||||
return decryptVerify(cryptoInput, in, 0);
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
@@ -248,7 +231,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Verify Keybase.io style signed literal data
|
||||
*/
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
|
||||
throws IOException, PGPException {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_VL, indent);
|
||||
|
||||
@@ -378,7 +362,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Decrypt and/or verifies binary or ascii armored pgp
|
||||
*/
|
||||
private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException {
|
||||
private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
|
||||
InputStream in, int indent) throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
@@ -433,6 +418,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
|
||||
// go through all objects and find one we can decrypt
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
@@ -492,11 +479,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
encryptedDataAsymmetric = encData;
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (mPassphrase == null) {
|
||||
if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
||||
passphrase = null;
|
||||
} else if (cryptoInput.hasPassphrase()) {
|
||||
passphrase = cryptoInput.getPassphrase();
|
||||
} else {
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
mPassphrase = getCachedPassphrase(subKeyId);
|
||||
passphrase = getCachedPassphrase(subKeyId);
|
||||
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
@@ -504,12 +495,11 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
if (passphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(subKeyId);
|
||||
return result;
|
||||
return new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredDecryptPassphrase(
|
||||
secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,11 +526,14 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
// if no passphrase is given, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
if (!cryptoInput.hasPassphrase()) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log);
|
||||
return new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredSymmetricPassphrase());
|
||||
}
|
||||
|
||||
passphrase = cryptoInput.getPassphrase();
|
||||
|
||||
// break out of while, only decrypt the first packet
|
||||
break;
|
||||
}
|
||||
@@ -573,7 +566,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
|
||||
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
|
||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
mPassphrase.getCharArray());
|
||||
passphrase.getCharArray());
|
||||
|
||||
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||
encryptedData = encryptedDataSymmetric;
|
||||
@@ -585,7 +578,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||
if (!secretEncryptionKey.unlock(mPassphrase)) {
|
||||
if (!secretEncryptionKey.unlock(passphrase)) {
|
||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
@@ -599,16 +592,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
try {
|
||||
PublicKeyDataDecryptorFactory decryptorFactory
|
||||
= secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey);
|
||||
= secretEncryptionKey.getDecryptorFactory(cryptoInput);
|
||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||
|
||||
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
|
||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log);
|
||||
result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase);
|
||||
return result;
|
||||
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
||||
e.encryptedSessionKey, secretEncryptionKey.getKeyId()
|
||||
));
|
||||
}
|
||||
encryptedData = encryptedDataAsymmetric;
|
||||
} else {
|
||||
@@ -878,8 +870,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
* The method is heavily based on
|
||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||
*/
|
||||
private DecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn, int indent)
|
||||
throws IOException, PGPException {
|
||||
private DecryptVerifyResult verifyCleartextSignature(
|
||||
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.bcpg.sig.Features;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
||||
@@ -43,6 +43,8 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBu
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
@@ -54,6 +56,9 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -86,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* This indicator may be null.
|
||||
*/
|
||||
public class PgpKeyOperation {
|
||||
|
||||
private Stack<Progressable> mProgress;
|
||||
private AtomicBoolean mCancelled;
|
||||
|
||||
@@ -317,7 +323,8 @@ public class PgpKeyOperation {
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
subProgressPush(50, 100);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, new Passphrase(), log);
|
||||
CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase(""));
|
||||
return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
@@ -348,8 +355,9 @@ public class PgpKeyOperation {
|
||||
* namely stripping of subkeys and changing the protection mode of dummy keys.
|
||||
*
|
||||
*/
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
Passphrase passphrase) {
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
@@ -387,11 +395,24 @@ public class PgpKeyOperation {
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// If we have no passphrase, only allow restricted operation
|
||||
if (passphrase == null) {
|
||||
if (saveParcel.isEmpty()) {
|
||||
log.add(LogType.MSG_MF_ERROR_NOOP, indent);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
|
||||
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
|
||||
return internalRestricted(sKR, saveParcel, log);
|
||||
}
|
||||
|
||||
// Do we require a passphrase? If so, pass it along
|
||||
if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) {
|
||||
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
||||
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
|
||||
cryptoInput.getSignatureTime()));
|
||||
}
|
||||
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
@@ -399,33 +420,45 @@ public class PgpKeyOperation {
|
||||
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
|
||||
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log);
|
||||
|
||||
}
|
||||
|
||||
private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags, long masterKeyExpiry,
|
||||
SaveKeyringParcel saveParcel, Passphrase passphrase,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel,
|
||||
OperationLog log) {
|
||||
|
||||
int indent = 1;
|
||||
|
||||
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
|
||||
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
||||
masterSecretKey.getKeyID());
|
||||
|
||||
progress(R.string.progress_modify, 0);
|
||||
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogType.MSG_MF_UNLOCK, indent);
|
||||
PGPPrivateKey masterPrivateKey;
|
||||
{
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
|
||||
if (isDivertToCard(masterSecretKey)) {
|
||||
masterPrivateKey = null;
|
||||
log.add(LogType.MSG_MF_DIVERT, indent);
|
||||
} else {
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogType.MSG_MF_UNLOCK, indent);
|
||||
{
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(cryptoInput.getPassphrase().getCharArray());
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +482,7 @@ public class PgpKeyOperation {
|
||||
String userId = saveParcel.mAddUserIds.get(i);
|
||||
log.add(LogType.MSG_MF_UID_ADD, indent, userId);
|
||||
|
||||
if (userId.equals("")) {
|
||||
if ("".equals(userId)) {
|
||||
log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
@@ -480,9 +513,16 @@ public class PgpKeyOperation {
|
||||
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.mChangePrimaryUserId);
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
try {
|
||||
PGPSignature cert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId,
|
||||
isPrimary, masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@@ -509,9 +549,15 @@ public class PgpKeyOperation {
|
||||
PGPUserAttributeSubpacketVector vector = attribute.getVector();
|
||||
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
|
||||
masterPublicKey, vector);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
|
||||
try {
|
||||
PGPSignature cert = generateUserAttributeSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, vector);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@@ -539,9 +585,15 @@ public class PgpKeyOperation {
|
||||
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
try {
|
||||
PGPSignature cert = generateRevocationSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@@ -611,11 +663,18 @@ public class PgpKeyOperation {
|
||||
log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, false,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, false,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -627,11 +686,17 @@ public class PgpKeyOperation {
|
||||
log.add(LogType.MSG_MF_PRIMARY_NEW, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, true,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, true,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
ok = true;
|
||||
}
|
||||
|
||||
@@ -718,8 +783,9 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
PGPPublicKey pKey =
|
||||
updateMasterCertificates(masterPrivateKey, masterPublicKey,
|
||||
flags, expiry, indent, log);
|
||||
updateMasterCertificates(
|
||||
masterSecretKey, masterPrivateKey, masterPublicKey,
|
||||
flags, expiry, cryptoInput, nfcSignOps, indent, log);
|
||||
if (pKey == null) {
|
||||
// error log entry has already been added by updateMasterCertificates itself
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
@@ -756,9 +822,16 @@ public class PgpKeyOperation {
|
||||
pKey = PGPPublicKey.removeCertification(pKey, sig);
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getPassphrase().getCharArray());
|
||||
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
|
||||
PGPSignature sig = generateSubkeyBindingSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry);
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
|
||||
sKey, pKey, flags, expiry, passphrase);
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
@@ -782,10 +855,17 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
|
||||
try {
|
||||
PGPSignature sig = generateRevocationSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, pKey);
|
||||
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@@ -828,10 +908,16 @@ public class PgpKeyOperation {
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
try {
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
@@ -840,7 +926,8 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getPassphrase().getCharArray());
|
||||
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
|
||||
@@ -868,7 +955,7 @@ public class PgpKeyOperation {
|
||||
indent += 1;
|
||||
|
||||
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
|
||||
passphrase, saveParcel.mNewUnlock, log, indent);
|
||||
cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
|
||||
if (sKR == null) {
|
||||
// The error has been logged above, just return a bad state
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
@@ -892,6 +979,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
progress(R.string.progress_done, 100);
|
||||
|
||||
if (!nfcSignOps.isEmpty()) {
|
||||
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
|
||||
return new PgpEditKeyResult(log, nfcSignOps.build());
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_MF_SUCCESS, indent);
|
||||
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||
|
||||
@@ -1064,8 +1157,7 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
newPassphrase.getCharArray());
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
|
||||
|
||||
// noinspection unchecked
|
||||
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
|
||||
@@ -1116,9 +1208,13 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
/** Update all (non-revoked) uid signatures with new flags and expiry time. */
|
||||
private static PGPPublicKey updateMasterCertificates(
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey,
|
||||
int flags, long expiry, int indent, OperationLog log)
|
||||
private PGPPublicKey updateMasterCertificates(
|
||||
PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPublicKey masterPublicKey,
|
||||
int flags, long expiry,
|
||||
CryptoInputParcel cryptoInput,
|
||||
NfcSignOperationsBuilder nfcSignOps,
|
||||
int indent, OperationLog log)
|
||||
throws PGPException, IOException, SignatureException {
|
||||
|
||||
// keep track if we actually changed one
|
||||
@@ -1173,10 +1269,16 @@ public class PgpKeyOperation {
|
||||
currentCert.getHashedSubPackets().isPrimaryUserID();
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
ok = true;
|
||||
|
||||
}
|
||||
@@ -1191,15 +1293,37 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserIdSignature(
|
||||
static PGPSignatureGenerator getSignatureGenerator(
|
||||
PGPSecretKey secretKey, CryptoInputParcel cryptoInput) {
|
||||
|
||||
PGPContentSignerBuilder builder;
|
||||
|
||||
S2K s2k = secretKey.getS2K();
|
||||
if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
|
||||
// use synchronous "NFC based" SignerBuilder
|
||||
builder = new NfcSyncPGPContentSignerBuilder(
|
||||
secretKey.getPublicKey().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
|
||||
secretKey.getKeyID(), cryptoInput.getCryptoData())
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
} else {
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
builder = new JcaPGPContentSignerBuilder(
|
||||
secretKey.getPublicKey().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
}
|
||||
|
||||
return new PGPSignatureGenerator(builder);
|
||||
|
||||
}
|
||||
|
||||
private PGPSignature generateUserIdSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
|
||||
int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
{
|
||||
@@ -1223,7 +1347,7 @@ public class PgpKeyOperation {
|
||||
hashedPacketsGen.setPrimaryUserID(false, primary);
|
||||
|
||||
/* critical subpackets: we consider those important for a modern pgp implementation */
|
||||
hashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
hashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
// Request that senders add the MDC to the message (authenticate unsigned messages)
|
||||
hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
|
||||
hashedPacketsGen.setKeyFlags(true, flags);
|
||||
@@ -1239,19 +1363,15 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserAttributeSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
|
||||
PGPUserAttributeSubpacketVector vector)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
{
|
||||
/* critical subpackets: we consider those important for a modern pgp implementation */
|
||||
hashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
hashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
}
|
||||
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
@@ -1260,29 +1380,24 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateRevocationSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
|
||||
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
|
||||
return sGen.generateCertification(userId, pKey);
|
||||
}
|
||||
|
||||
private static PGPSignature generateRevocationSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
// Generate key revocation or subkey revocation, depending on master/subkey-ness
|
||||
if (masterPublicKey.getKeyID() == pKey.getKeyID()) {
|
||||
@@ -1294,26 +1409,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, Passphrase passphrase)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passphrase.getCharArray());
|
||||
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
|
||||
return generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, subPrivateKey,
|
||||
pKey, flags, expiry);
|
||||
}
|
||||
|
||||
static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
|
||||
// date for signing
|
||||
Date creationTime = new Date();
|
||||
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
// If this key can sign, we need a primary key binding signature
|
||||
@@ -1324,10 +1425,10 @@ public class PgpKeyOperation {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey);
|
||||
PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
|
||||
subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
|
||||
unhashedPacketsGen.setEmbeddedSignature(true, certification);
|
||||
}
|
||||
|
||||
@@ -1342,10 +1443,6 @@ public class PgpKeyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
|
||||
@@ -1372,4 +1469,16 @@ public class PgpKeyOperation {
|
||||
return flags;
|
||||
}
|
||||
|
||||
private static boolean isDummy(PGPSecretKey secretKey) {
|
||||
S2K s2k = secretKey.getS2K();
|
||||
return s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
|
||||
}
|
||||
|
||||
private static boolean isDivertToCard(PGPSecretKey secretKey) {
|
||||
S2K s2k = secretKey.getS2K();
|
||||
return s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,18 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public class PgpSignEncryptInput {
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
|
||||
public class PgpSignEncryptInputParcel implements Parcelable {
|
||||
|
||||
protected String mVersionHeader = null;
|
||||
protected boolean mEnableAsciiArmorOutput = false;
|
||||
@@ -35,16 +42,68 @@ public class PgpSignEncryptInput {
|
||||
protected long mSignatureMasterKeyId = Constants.key.none;
|
||||
protected Long mSignatureSubKeyId = null;
|
||||
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
|
||||
protected Passphrase mSignaturePassphrase = null;
|
||||
protected long mAdditionalEncryptId = Constants.key.none;
|
||||
protected byte[] mNfcSignedHash = null;
|
||||
protected Date mNfcCreationTimestamp = null;
|
||||
protected boolean mFailOnMissingEncryptionKeyIds = false;
|
||||
protected String mCharset;
|
||||
protected boolean mCleartextSignature;
|
||||
protected boolean mDetachedSignature = false;
|
||||
protected boolean mHiddenRecipients = false;
|
||||
|
||||
public PgpSignEncryptInputParcel() {
|
||||
|
||||
}
|
||||
|
||||
PgpSignEncryptInputParcel(Parcel source) {
|
||||
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
|
||||
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||
mVersionHeader = source.readString();
|
||||
mEnableAsciiArmorOutput = source.readInt() == 1;
|
||||
mCompressionId = source.readInt();
|
||||
mEncryptionMasterKeyIds = source.createLongArray();
|
||||
mSymmetricPassphrase = source.readParcelable(loader);
|
||||
mSymmetricEncryptionAlgorithm = source.readInt();
|
||||
mSignatureMasterKeyId = source.readLong();
|
||||
mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
|
||||
mSignatureHashAlgorithm = source.readInt();
|
||||
mAdditionalEncryptId = source.readLong();
|
||||
mFailOnMissingEncryptionKeyIds = source.readInt() == 1;
|
||||
mCharset = source.readString();
|
||||
mCleartextSignature = source.readInt() == 1;
|
||||
mDetachedSignature = source.readInt() == 1;
|
||||
mHiddenRecipients = source.readInt() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mVersionHeader);
|
||||
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
|
||||
dest.writeInt(mCompressionId);
|
||||
dest.writeLongArray(mEncryptionMasterKeyIds);
|
||||
dest.writeParcelable(mSymmetricPassphrase, 0);
|
||||
dest.writeInt(mSymmetricEncryptionAlgorithm);
|
||||
dest.writeLong(mSignatureMasterKeyId);
|
||||
if (mSignatureSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mSignatureHashAlgorithm);
|
||||
dest.writeLong(mAdditionalEncryptId);
|
||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
||||
dest.writeString(mCharset);
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
dest.writeInt(mHiddenRecipients ? 1 : 0);
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return mCharset;
|
||||
}
|
||||
@@ -57,37 +116,20 @@ public class PgpSignEncryptInput {
|
||||
return mFailOnMissingEncryptionKeyIds;
|
||||
}
|
||||
|
||||
public Date getNfcCreationTimestamp() {
|
||||
return mNfcCreationTimestamp;
|
||||
}
|
||||
|
||||
public byte[] getNfcSignedHash() {
|
||||
return mNfcSignedHash;
|
||||
}
|
||||
|
||||
public long getAdditionalEncryptId() {
|
||||
return mAdditionalEncryptId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) {
|
||||
public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) {
|
||||
mAdditionalEncryptId = additionalEncryptId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Passphrase getSignaturePassphrase() {
|
||||
return mSignaturePassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignaturePassphrase(Passphrase signaturePassphrase) {
|
||||
mSignaturePassphrase = signaturePassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSignatureHashAlgorithm() {
|
||||
return mSignatureHashAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
mSignatureHashAlgorithm = signatureHashAlgorithm;
|
||||
return this;
|
||||
}
|
||||
@@ -96,7 +138,7 @@ public class PgpSignEncryptInput {
|
||||
return mSignatureSubKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
mSignatureSubKeyId = signatureSubKeyId;
|
||||
return this;
|
||||
}
|
||||
@@ -105,7 +147,7 @@ public class PgpSignEncryptInput {
|
||||
return mSignatureMasterKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
return this;
|
||||
}
|
||||
@@ -114,7 +156,7 @@ public class PgpSignEncryptInput {
|
||||
return mSymmetricEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
|
||||
return this;
|
||||
}
|
||||
@@ -123,7 +165,7 @@ public class PgpSignEncryptInput {
|
||||
return mSymmetricPassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricPassphrase(Passphrase symmetricPassphrase) {
|
||||
public PgpSignEncryptInputParcel setSymmetricPassphrase(Passphrase symmetricPassphrase) {
|
||||
mSymmetricPassphrase = symmetricPassphrase;
|
||||
return this;
|
||||
}
|
||||
@@ -132,7 +174,7 @@ public class PgpSignEncryptInput {
|
||||
return mEncryptionMasterKeyIds;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
|
||||
return this;
|
||||
}
|
||||
@@ -141,7 +183,7 @@ public class PgpSignEncryptInput {
|
||||
return mCompressionId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCompressionId(int compressionId) {
|
||||
public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
|
||||
mCompressionId = compressionId;
|
||||
return this;
|
||||
}
|
||||
@@ -154,28 +196,22 @@ public class PgpSignEncryptInput {
|
||||
return mVersionHeader;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setVersionHeader(String versionHeader) {
|
||||
public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) {
|
||||
mVersionHeader = versionHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) {
|
||||
mNfcSignedHash = signedHash;
|
||||
mNfcCreationTimestamp = creationTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
|
||||
public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
|
||||
this.mCleartextSignature = cleartextSignature;
|
||||
return this;
|
||||
}
|
||||
@@ -184,7 +220,7 @@ public class PgpSignEncryptInput {
|
||||
return mCleartextSignature;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) {
|
||||
public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) {
|
||||
this.mDetachedSignature = detachedSignature;
|
||||
return this;
|
||||
}
|
||||
@@ -193,7 +229,7 @@ public class PgpSignEncryptInput {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) {
|
||||
public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) {
|
||||
this.mHiddenRecipients = hiddenRecipients;
|
||||
return this;
|
||||
}
|
||||
@@ -201,5 +237,16 @@ public class PgpSignEncryptInput {
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
public static final Creator<PgpSignEncryptInputParcel> CREATOR = new Creator<PgpSignEncryptInputParcel>() {
|
||||
public PgpSignEncryptInputParcel createFromParcel(final Parcel source) {
|
||||
return new PgpSignEncryptInputParcel(source);
|
||||
}
|
||||
|
||||
public PgpSignEncryptInputParcel[] newArray(final int size) {
|
||||
return new PgpSignEncryptInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
@@ -44,11 +43,15 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -72,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* <p/>
|
||||
* For a high-level operation based on URIs, see SignEncryptOperation.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
|
||||
* @see PgpSignEncryptInputParcel
|
||||
* @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
|
||||
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
|
||||
*/
|
||||
@@ -99,8 +102,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
/**
|
||||
* Signs and/or encrypts data based on parameters of class
|
||||
*/
|
||||
public PgpSignEncryptResult execute(PgpSignEncryptInput input,
|
||||
InputData inputData, OutputStream outputStream) {
|
||||
public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
|
||||
InputData inputData, OutputStream outputStream) {
|
||||
|
||||
int indent = 0;
|
||||
OperationLog log = new OperationLog();
|
||||
@@ -128,7 +131,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
ArmoredOutputStream armorOut = null;
|
||||
OutputStream out;
|
||||
if (input.isEnableAsciiArmorOutput()) {
|
||||
armorOut = new ArmoredOutputStream(outputStream);
|
||||
armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
|
||||
if (input.getVersionHeader() != null) {
|
||||
armorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
@@ -145,62 +148,62 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
CanonicalizedSecretKey signingKey = null;
|
||||
if (enableSignature) {
|
||||
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
try {
|
||||
// fetch the indicated master key id (the one whose name we sign in)
|
||||
CanonicalizedSecretKeyRing signingKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
|
||||
|
||||
long signKeyId;
|
||||
// use specified signing subkey, or find the one to use
|
||||
if (input.getSignatureSubKeyId() == null) {
|
||||
signKeyId = signingKeyRing.getSecretSignId();
|
||||
} else {
|
||||
signKeyId = input.getSignatureSubKeyId();
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signingKey = signingKeyRing.getSecretKey(signKeyId);
|
||||
switch (signingKey.getSecretKeyType()) {
|
||||
case DIVERT_TO_CARD:
|
||||
case PASSPHRASE_EMPTY: {
|
||||
if (!signingKey.unlock(new Passphrase())) {
|
||||
throw new AssertionError(
|
||||
"PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase."
|
||||
+ " This is a programming error!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException | PgpGeneralException e) {
|
||||
case PIN:
|
||||
case PATTERN:
|
||||
case PASSPHRASE: {
|
||||
if (cryptoInput.getPassphrase() == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
|
||||
cryptoInput.getSignatureTime()));
|
||||
}
|
||||
if (!signingKey.unlock(cryptoInput.getPassphrase())) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GNU_DUMMY: {
|
||||
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Unhandled SecretKeyType! (should not happen)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId()));
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(signingKey.getKeyId());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
try {
|
||||
if (!signingKey.unlock(input.getSignaturePassphrase())) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
@@ -281,8 +284,9 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
|
||||
try {
|
||||
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
|
||||
signatureGenerator = signingKey.getSignatureGenerator(
|
||||
input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
|
||||
signatureGenerator = signingKey.getDataSignatureGenerator(
|
||||
input.getSignatureHashAlgorithm(), cleartext,
|
||||
cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_NFC, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
@@ -405,7 +409,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
detachedByteOut = new ByteArrayOutputStream();
|
||||
OutputStream detachedOut = detachedByteOut;
|
||||
if (input.isEnableAsciiArmorOutput()) {
|
||||
detachedArmorOut = new ArmoredOutputStream(detachedOut);
|
||||
detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
|
||||
if (input.getVersionHeader() != null) {
|
||||
detachedArmorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
@@ -485,19 +489,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
|
||||
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
|
||||
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
|
||||
PgpSignEncryptResult result =
|
||||
new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
|
||||
|
||||
// SignatureSubKeyId can be null.
|
||||
if (input.getSignatureSubKeyId() == null) {
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// Note that the checked key here is the master key, not the signing key
|
||||
// (although these are always the same on Yubikeys)
|
||||
result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
|
||||
Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
|
||||
return result;
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
|
||||
e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/** This parcel stores the input of one or more PgpSignEncrypt operations.
|
||||
@@ -42,7 +38,7 @@ import java.util.List;
|
||||
* left, which will be returned in a byte array as part of the result parcel.
|
||||
*
|
||||
*/
|
||||
public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable {
|
||||
public class SignEncryptParcel extends PgpSignEncryptInputParcel {
|
||||
|
||||
public ArrayList<Uri> mInputUris = new ArrayList<>();
|
||||
public ArrayList<Uri> mOutputUris = new ArrayList<>();
|
||||
@@ -53,26 +49,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
|
||||
}
|
||||
|
||||
public SignEncryptParcel(Parcel src) {
|
||||
|
||||
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||
mVersionHeader = src.readString();
|
||||
mEnableAsciiArmorOutput = src.readInt() == 1;
|
||||
mCompressionId = src.readInt();
|
||||
mEncryptionMasterKeyIds = src.createLongArray();
|
||||
mSymmetricPassphrase = src.readParcelable(Passphrase.class.getClassLoader());
|
||||
mSymmetricEncryptionAlgorithm = src.readInt();
|
||||
mSignatureMasterKeyId = src.readLong();
|
||||
mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null;
|
||||
mSignatureHashAlgorithm = src.readInt();
|
||||
mSignaturePassphrase = src.readParcelable(Passphrase.class.getClassLoader());
|
||||
mAdditionalEncryptId = src.readLong();
|
||||
mNfcSignedHash = src.createByteArray();
|
||||
mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null;
|
||||
mFailOnMissingEncryptionKeyIds = src.readInt() == 1;
|
||||
mCharset = src.readString();
|
||||
mCleartextSignature = src.readInt() == 1;
|
||||
mDetachedSignature = src.readInt() == 1;
|
||||
mHiddenRecipients = src.readInt() == 1;
|
||||
super(src);
|
||||
|
||||
mInputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
mOutputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
@@ -110,34 +87,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mVersionHeader);
|
||||
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
|
||||
dest.writeInt(mCompressionId);
|
||||
dest.writeLongArray(mEncryptionMasterKeyIds);
|
||||
dest.writeParcelable(mSymmetricPassphrase, flags);
|
||||
dest.writeInt(mSymmetricEncryptionAlgorithm);
|
||||
dest.writeLong(mSignatureMasterKeyId);
|
||||
if (mSignatureSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mSignatureHashAlgorithm);
|
||||
dest.writeParcelable(mSignaturePassphrase, flags);
|
||||
dest.writeLong(mAdditionalEncryptId);
|
||||
dest.writeByteArray(mNfcSignedHash);
|
||||
if (mNfcCreationTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcCreationTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
||||
dest.writeString(mCharset);
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
dest.writeInt(mHiddenRecipients ? 1 : 0);
|
||||
super.writeToParcel(dest, flags);
|
||||
|
||||
dest.writeTypedList(mInputUris);
|
||||
dest.writeTypedList(mOutputUris);
|
||||
|
||||
@@ -1165,7 +1165,7 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
// If anything changed, save the updated (sub)key
|
||||
// If anything change, save the updated (sub)key
|
||||
if (modified != resultKey) {
|
||||
result = replacePublicKey(result, modified);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.remote;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class CryptoInputParcelCacheService extends Service {
|
||||
|
||||
public static final String ACTION_ADD = Constants.INTENT_PREFIX + "ADD";
|
||||
public static final String ACTION_GET = Constants.INTENT_PREFIX + "GET";
|
||||
|
||||
public static final String EXTRA_CRYPTO_INPUT_PARCEL = "crypto_input_parcel";
|
||||
public static final String EXTRA_UUID1 = "uuid1";
|
||||
public static final String EXTRA_UUID2 = "uuid2";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
|
||||
private static final int MSG_GET_OKAY = 1;
|
||||
private static final int MSG_GET_NOT_FOUND = 2;
|
||||
|
||||
Context mContext;
|
||||
|
||||
private static final UUID NULL_UUID = new UUID(0, 0);
|
||||
|
||||
private ConcurrentHashMap<UUID, CryptoInputParcel> mCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static class InputParcelNotFound extends Exception {
|
||||
public InputParcelNotFound() {
|
||||
}
|
||||
|
||||
public InputParcelNotFound(String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCryptoInputParcel(Context context, Intent data, CryptoInputParcel inputParcel) {
|
||||
UUID mTicket = addCryptoInputParcel(context, inputParcel);
|
||||
// And write out the UUID most and least significant bits.
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID1, mTicket.getMostSignificantBits());
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID2, mTicket.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static CryptoInputParcel getCryptoInputParcel(Context context, Intent data) {
|
||||
if (!data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID1)
|
||||
|| !data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID2)) {
|
||||
return null;
|
||||
}
|
||||
long mostSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID1, 0);
|
||||
long leastSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID2, 0);
|
||||
UUID uuid = new UUID(mostSig, leastSig);
|
||||
try {
|
||||
return getCryptoInputParcel(context, uuid);
|
||||
} catch (InputParcelNotFound inputParcelNotFound) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID addCryptoInputParcel(Context context, CryptoInputParcel inputParcel) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
|
||||
intent.setAction(ACTION_ADD);
|
||||
intent.putExtra(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
|
||||
intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
|
||||
intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
|
||||
context.startService(intent);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private static CryptoInputParcel getCryptoInputParcel(Context context, UUID uuid) throws InputParcelNotFound {
|
||||
Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
|
||||
intent.setAction(ACTION_GET);
|
||||
|
||||
final Object mutex = new Object();
|
||||
final Message returnMessage = Message.obtain();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("getParcelableThread");
|
||||
handlerThread.start();
|
||||
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// copy over result to handle after mutex.wait
|
||||
returnMessage.what = message.what;
|
||||
returnMessage.copyFrom(message);
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
// quit handlerThread
|
||||
getLooper().quit();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
|
||||
intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
|
||||
intent.putExtra(EXTRA_MESSENGER, messenger);
|
||||
// send intent to this service
|
||||
context.startService(intent);
|
||||
|
||||
// Wait on mutex until parcelable is returned to handlerThread. Note that this local
|
||||
// variable is used in the handler closure above, so it does make sense here!
|
||||
// noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (mutex) {
|
||||
try {
|
||||
mutex.wait(3000);
|
||||
} catch (InterruptedException e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
switch (returnMessage.what) {
|
||||
case MSG_GET_OKAY:
|
||||
Bundle returnData = returnMessage.getData();
|
||||
returnData.setClassLoader(context.getClassLoader());
|
||||
return returnData.getParcelable(EXTRA_CRYPTO_INPUT_PARCEL);
|
||||
case MSG_GET_NOT_FOUND:
|
||||
throw new InputParcelNotFound();
|
||||
default:
|
||||
Log.e(Constants.TAG, "timeout!");
|
||||
throw new InputParcelNotFound("should not happen!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when service is started by intent
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_ADD: {
|
||||
long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
|
||||
long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
|
||||
UUID uuid = new UUID(uuid1, uuid2);
|
||||
CryptoInputParcel inputParcel = intent.getParcelableExtra(EXTRA_CRYPTO_INPUT_PARCEL);
|
||||
mCache.put(uuid, inputParcel);
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_GET: {
|
||||
long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
|
||||
long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
|
||||
UUID uuid = new UUID(uuid1, uuid2);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
|
||||
Message msg = Message.obtain();
|
||||
// UUID.equals isn't well documented; we use compareTo instead.
|
||||
if (NULL_UUID.compareTo(uuid) == 0) {
|
||||
msg.what = MSG_GET_NOT_FOUND;
|
||||
} else {
|
||||
CryptoInputParcel inputParcel = mCache.get(uuid);
|
||||
mCache.remove(uuid);
|
||||
msg.what = MSG_GET_OKAY;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
|
||||
msg.setData(bundle);
|
||||
}
|
||||
|
||||
try {
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoInputParcelCacheService: Sending message failed", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "CryptoInputParcelCacheService: Intent or Intent Action not supported!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCache.size() <= 0) {
|
||||
// stop whole service if cache is empty
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService: No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService, onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService, onDestroy()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
public class CryptoInputParcelCacheServiceBinder extends Binder {
|
||||
public CryptoInputParcelCacheService getService() {
|
||||
return CryptoInputParcelCacheService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new CryptoInputParcelCacheServiceBinder();
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -31,16 +32,15 @@ import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@@ -48,8 +48,10 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||
import org.sufficientlysecure.keychain.ui.NfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
@@ -60,7 +62,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
@@ -78,9 +79,6 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
* @param encryptionUserIds
|
||||
* @return
|
||||
*/
|
||||
private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
|
||||
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
|
||||
@@ -163,52 +161,35 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
}
|
||||
|
||||
private Intent returnPassphraseIntent(Intent data, long keyId) {
|
||||
// build PendingIntent for passphrase input
|
||||
Intent intent = new Intent(getBaseContext(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, keyId);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_DATA, data);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
private static PendingIntent getRequiredInputPendingIntent(Context context,
|
||||
Intent data, RequiredInputParcel requiredInput) {
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
switch (requiredInput.mType) {
|
||||
case NFC_DECRYPT:
|
||||
case NFC_SIGN: {
|
||||
// build PendingIntent for YubiKey NFC operations
|
||||
Intent intent = new Intent(context, NfcOperationActivity.class);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data);
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getNfcSignPendingIntent(Intent data, long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getBaseContext(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_SIGN_HASH);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, data);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
|
||||
case PASSPHRASE: {
|
||||
// build PendingIntent for Passphrase request
|
||||
Intent intent = new Intent(context, PassphraseDialogActivity.class);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SERVICE_INTENT, data);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
|
||||
return PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
default:
|
||||
throw new AssertionError("Unhandled required input type!");
|
||||
}
|
||||
|
||||
private PendingIntent getNfcDecryptPendingIntent(Intent data, long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getBaseContext(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, data);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
|
||||
return PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getKeyserverPendingIntent(Intent data, long masterKeyId) {
|
||||
@@ -240,17 +221,13 @@ public class OpenPgpService extends RemoteService {
|
||||
try {
|
||||
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
if (nfcSignedHash != null) {
|
||||
Log.d(Constants.TAG, "nfcSignedHash:" + Hex.toHexString(nfcSignedHash));
|
||||
} else {
|
||||
Log.d(Constants.TAG, "nfcSignedHash: null");
|
||||
}
|
||||
// sign-only
|
||||
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel()
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setCleartextSignature(cleartextSign)
|
||||
.setDetachedSignature(!cleartextSign)
|
||||
.setVersionHeader(null)
|
||||
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
||||
// NOTE: Fallback to return account settings (Old API)
|
||||
@@ -258,17 +235,21 @@ public class OpenPgpService extends RemoteService {
|
||||
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
||||
return signKeyIdIntent;
|
||||
}
|
||||
|
||||
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||
if (signKeyId == Constants.key.none) {
|
||||
Log.e(Constants.TAG, "No signing key given!");
|
||||
}
|
||||
throw new Exception("No signing key given");
|
||||
} else {
|
||||
pseInput.setSignatureMasterKeyId(signKeyId);
|
||||
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
// get first usable subkey capable of signing
|
||||
try {
|
||||
long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
pseInput.getSignatureMasterKeyId()).getSecretSignId();
|
||||
pseInput.setSignatureSubKeyId(signSubKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
throw new Exception("signing subkey not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
@@ -281,42 +262,31 @@ public class OpenPgpService extends RemoteService {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
// sign-only
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput()
|
||||
.setSignaturePassphrase(passphrase)
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setCleartextSignature(cleartextSign)
|
||||
.setDetachedSignature(!cleartextSign)
|
||||
.setVersionHeader(null)
|
||||
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
|
||||
.setSignatureMasterKeyId(signKeyId)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputData, os);
|
||||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
if (pgpResult.getDetachedSignature() != null && !cleartextSign) {
|
||||
@@ -372,11 +342,6 @@ public class OpenPgpService extends RemoteService {
|
||||
compressionId = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
// first try to get key ids from non-ambiguous key id extra
|
||||
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
if (keyIds == null) {
|
||||
@@ -401,9 +366,8 @@ public class OpenPgpService extends RemoteService {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength, originalFilename);
|
||||
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput();
|
||||
pseInput.setSignaturePassphrase(passphrase)
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
|
||||
pseInput.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setVersionHeader(null)
|
||||
.setCompressionId(compressionId)
|
||||
.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED)
|
||||
@@ -420,49 +384,49 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||
if (signKeyId == Constants.key.none) {
|
||||
Log.e(Constants.TAG, "No signing key given!");
|
||||
}
|
||||
throw new Exception("No signing key given");
|
||||
} else {
|
||||
pseInput.setSignatureMasterKeyId(signKeyId);
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
// get first usable subkey capable of signing
|
||||
try {
|
||||
long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
pseInput.getSignatureMasterKeyId()).getSecretSignId();
|
||||
pseInput.setSignatureSubKeyId(signSubKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
throw new Exception("signing subkey not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// sign and encrypt
|
||||
pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
|
||||
.setSignatureMasterKeyId(signKeyId)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate)
|
||||
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
|
||||
}
|
||||
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os);
|
||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
@@ -511,11 +475,6 @@ public class OpenPgpService extends RemoteService {
|
||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds;
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||
@@ -533,42 +492,37 @@ public class OpenPgpService extends RemoteService {
|
||||
this, new ProviderHelper(getContext()), null, inputData, os
|
||||
);
|
||||
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||
|
||||
// allow only private keys associated with accounts of this app
|
||||
// no support for symmetric encryption
|
||||
builder.setPassphrase(passphrase)
|
||||
.setAllowSymmetricDecryption(false)
|
||||
builder.setAllowSymmetricDecryption(false)
|
||||
.setAllowedKeyIds(allowedKeyIds)
|
||||
.setDecryptMetadataOnly(decryptMetadataOnly)
|
||||
.setNfcState(nfcDecryptedSessionKey)
|
||||
.setDetachedSignature(detachedSignature);
|
||||
|
||||
DecryptVerifyResult pgpResult = builder.build().execute();
|
||||
DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
throw new PgpGeneralException(
|
||||
"Decryption of symmetric content not supported by API!");
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
// prepare and return PendingIntent to be executed by client
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcDecryptPendingIntent(data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
|
||||
@@ -754,7 +708,6 @@ public class OpenPgpService extends RemoteService {
|
||||
* - has supported API version
|
||||
* - is allowed to call the service (access has been granted)
|
||||
*
|
||||
* @param data
|
||||
* @return null if everything is okay, or a Bundle with an error/PendingIntent
|
||||
*/
|
||||
private Intent checkRequirements(Intent data) {
|
||||
@@ -794,9 +747,7 @@ public class OpenPgpService extends RemoteService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: multi-threading
|
||||
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
||||
|
||||
@Override
|
||||
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
try {
|
||||
@@ -806,30 +757,42 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
String action = data.getAction();
|
||||
if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||
return signImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
return decryptAndVerifyImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) {
|
||||
return decryptAndVerifyImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(action)) {
|
||||
return getSignKeyIdImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
||||
return getKeyIdsImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||
return getKeyImpl(data);
|
||||
} else {
|
||||
return null;
|
||||
switch (action) {
|
||||
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
||||
return signImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_SIGN: {
|
||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||
return signImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DETACHED_SIGN: {
|
||||
return signImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_ENCRYPT: {
|
||||
return encryptAndSignImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
|
||||
return encryptAndSignImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
||||
return decryptAndVerifyImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
|
||||
return decryptAndVerifyImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
|
||||
return getSignKeyIdImpl(data);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_KEY_IDS: {
|
||||
return getKeyIdsImpl(data);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_KEY: {
|
||||
return getKeyImpl(data);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// always close input and output file descriptors even in error cases
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AccountSettingsActivity extends BaseActivity {
|
||||
|
||||
@@ -40,9 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -22,10 +22,14 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
@@ -68,6 +67,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
@@ -159,8 +159,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_PASSPHRASE = "passphrase";
|
||||
public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
|
||||
|
||||
// keybase proof
|
||||
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
||||
@@ -169,6 +167,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
// save keyring
|
||||
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
||||
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||
|
||||
// delete keyring(s)
|
||||
public static final String DELETE_KEY_LIST = "delete_list";
|
||||
@@ -193,7 +192,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// promote key
|
||||
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
||||
public static final String PROMOTE_TYPE = "promote_type";
|
||||
public static final String PROMOTE_CARD_AID = "promote_card_aid";
|
||||
|
||||
// consolidate
|
||||
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
||||
@@ -260,11 +259,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
||||
|
||||
// Operation
|
||||
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
|
||||
CertifyResult result = op.certify(parcel, keyServerUri);
|
||||
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@@ -289,27 +289,20 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_DECRYPT_METADATA: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
/* Input */
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this, inputData, null
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setDecryptMetadataOnly(true)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
.setDecryptMetadataOnly(true);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
||||
|
||||
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
|
||||
} catch (Exception e) {
|
||||
@@ -384,7 +377,8 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
);
|
||||
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(
|
||||
new CryptoInputParcel());
|
||||
outStream.close();
|
||||
|
||||
if (!decryptVerifyResult.success()) {
|
||||
@@ -419,15 +413,13 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_DECRYPT_VERIFY: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
/* Input */
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
/* Operation */
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
@@ -436,24 +428,22 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
this, new ProviderHelper(this), this,
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
builder.setAllowSymmetricDecryption(true);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
||||
|
||||
outStream.close();
|
||||
|
||||
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
|
||||
|
||||
/* Output */
|
||||
|
||||
/* Output */
|
||||
finalizeDecryptOutputStream(data, resultData, outStream);
|
||||
|
||||
Log.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
|
||||
} catch (IOException | PgpGeneralException e) {
|
||||
// TODO get rid of this!
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
@@ -478,11 +468,11 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
||||
Passphrase passphrase = data.getParcelable(EDIT_KEYRING_PASSPHRASE);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
// Operation
|
||||
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
EditKeyResult result = op.execute(saveParcel, passphrase);
|
||||
OperationResult result = op.execute(saveParcel, cryptoInput);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@@ -492,11 +482,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_PROMOTE_KEYRING: {
|
||||
|
||||
// Input
|
||||
long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
|
||||
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
|
||||
|
||||
// Operation
|
||||
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
PromoteKeyResult result = op.execute(keyRingId);
|
||||
PromoteKeyResult result = op.execute(keyRingId, cardAid);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@@ -553,11 +544,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
// Operation
|
||||
SignEncryptOperation op = new SignEncryptOperation(
|
||||
this, new ProviderHelper(this), this, mActionCanceled);
|
||||
SignEncryptResult result = op.execute(inputParcel);
|
||||
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
|
||||
@@ -60,18 +60,18 @@ import java.util.Date;
|
||||
*
|
||||
* Caching behavior for subkeys depends on the cacheSubs preference:
|
||||
*
|
||||
* - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
|
||||
* checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
|
||||
* from the same master key will use the same passphrase. This can lead to bad passphrase
|
||||
* errors if two subkeys are encrypted differently. This is the default behavior.
|
||||
* - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
|
||||
* checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
|
||||
* from the same master key will use the same passphrase. This can lead to bad passphrase
|
||||
* errors if two subkeys are encrypted differently. This is the default behavior.
|
||||
*
|
||||
* - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
|
||||
* has two subkeys for different purposes, passphrases will be cached independently and the
|
||||
* user will be asked for a passphrase once per subkey even if it is the same one. This mode
|
||||
* of operation is more precise, since we can assume that all passphrases returned from cache
|
||||
* will be correct without fail. Since keyrings with differently encrypted subkeys are a very
|
||||
* rare occurrence, and caching by keyring is what the user expects in the vast majority of
|
||||
* cases, this is not the default behavior.
|
||||
* - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
|
||||
* has two subkeys for different purposes, passphrases will be cached independently and the
|
||||
* user will be asked for a passphrase once per subkey even if it is the same one. This mode
|
||||
* of operation is more precise, since we can assume that all passphrases returned from cache
|
||||
* will be correct without fail. Since keyrings with differently encrypted subkeys are a very
|
||||
* rare occurrence, and caching by keyring is what the user expects in the vast majority of
|
||||
* cases, this is not the default behavior.
|
||||
*
|
||||
*/
|
||||
public class PassphraseCacheService extends Service {
|
||||
@@ -123,7 +123,7 @@ public class PassphraseCacheService extends Service {
|
||||
public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId,
|
||||
Passphrase passphrase,
|
||||
String primaryUserId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + masterKeyId);
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.addCachedPassphrase() for " + masterKeyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
@@ -137,10 +137,23 @@ public class PassphraseCacheService extends Service {
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
public static void clearCachedPassphrase(Context context, long masterKeyId, long subKeyId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase() for " + masterKeyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
intent.putExtra(EXTRA_KEY_ID, masterKeyId);
|
||||
intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||
* designed to wait until the service returns the passphrase.
|
||||
|
||||
*
|
||||
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||
*/
|
||||
public static Passphrase getCachedPassphrase(Context context, long masterKeyId, long subKeyId) throws KeyNotFoundException {
|
||||
@@ -218,7 +231,7 @@ public class PassphraseCacheService extends Service {
|
||||
}
|
||||
|
||||
// on "none" key, just do nothing
|
||||
if(masterKeyId == Constants.key.none) {
|
||||
if (masterKeyId == Constants.key.none) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -232,11 +245,11 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
switch (keyType) {
|
||||
case DIVERT_TO_CARD:
|
||||
if (Preferences.getPreferences(this).useDefaultYubikeyPin()) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Using default Yubikey PIN: 123456");
|
||||
return new Passphrase("123456"); // default Yubikey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
|
||||
if (Preferences.getPreferences(this).useDefaultYubiKeyPin()) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Using default YubiKey PIN: 123456");
|
||||
return new Passphrase("123456"); // default YubiKey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
|
||||
} else {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: NOT using default Yubikey PIN");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: NOT using default YubiKey PIN");
|
||||
break;
|
||||
}
|
||||
case PASSPHRASE_EMPTY:
|
||||
@@ -310,11 +323,11 @@ public class PassphraseCacheService extends Service {
|
||||
/**
|
||||
* Build pending intent that is executed by alarm manager to time out a specific passphrase
|
||||
*/
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
private static PendingIntent buildIntent(Context context, long referenceKeyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_KEY_ID, referenceKeyId);
|
||||
// request code should be unique for each PendingIntent, thus keyId is used
|
||||
return PendingIntent.getBroadcast(context, (int) keyId, intent,
|
||||
return PendingIntent.getBroadcast(context, (int) referenceKeyId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
@@ -325,11 +338,17 @@ public class PassphraseCacheService extends Service {
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");
|
||||
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
updateService();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
// register broadcastreceiver
|
||||
registerReceiver();
|
||||
|
||||
if (intent != null && intent.getAction() != null) {
|
||||
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_PASSPHRASE_CACHE_ADD: {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1);
|
||||
@@ -343,28 +362,19 @@ public class PassphraseCacheService extends Service {
|
||||
);
|
||||
|
||||
// if we don't cache by specific subkey id, or the requested subkey is the master key,
|
||||
// just add master key id to the cache
|
||||
if (subKeyId == masterKeyId || !Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
||||
mPassphraseCache.put(masterKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, masterKeyId));
|
||||
}
|
||||
} else {
|
||||
// otherwise, add this specific subkey to the cache
|
||||
mPassphraseCache.put(subKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, subKeyId));
|
||||
}
|
||||
// just add master key id to the cache, otherwise, add this specific subkey to the cache
|
||||
long referenceKeyId =
|
||||
Preferences.getPreferences(mContext).getPassphraseCacheSubs() ? subKeyId : masterKeyId;
|
||||
mPassphraseCache.put(referenceKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId));
|
||||
}
|
||||
|
||||
updateService();
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
break;
|
||||
}
|
||||
case ACTION_PASSPHRASE_CACHE_GET: {
|
||||
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric);
|
||||
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
@@ -392,22 +402,42 @@ public class PassphraseCacheService extends Service {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
|
||||
break;
|
||||
}
|
||||
case ACTION_PASSPHRASE_CACHE_CLEAR: {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
// Stop all ttl alarms
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) {
|
||||
|
||||
long referenceKeyId;
|
||||
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
||||
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
|
||||
} else {
|
||||
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
|
||||
}
|
||||
// Stop specific ttl alarm and
|
||||
am.cancel(buildIntent(this, referenceKeyId));
|
||||
mPassphraseCache.delete(referenceKeyId);
|
||||
|
||||
} else {
|
||||
|
||||
// Stop all ttl alarms
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
}
|
||||
mPassphraseCache.clear();
|
||||
|
||||
}
|
||||
|
||||
mPassphraseCache.clear();
|
||||
|
||||
updateService();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateService();
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,10 +82,14 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mRevokeSubKeys = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return isRestrictedOnly() && mChangeSubKeys.isEmpty();
|
||||
}
|
||||
|
||||
/** Returns true iff this parcel does not contain any operations which require a passphrase. */
|
||||
public boolean isRestrictedOnly() {
|
||||
if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
|
||||
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty()
|
||||
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty()
|
||||
|| !mRevokeSubKeys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@@ -26,6 +27,7 @@ import android.support.v4.app.FragmentManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.input;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
/**
|
||||
* This is a base class for the input of crypto operations.
|
||||
*/
|
||||
public class CryptoInputParcel implements Parcelable {
|
||||
|
||||
final Date mSignatureTime;
|
||||
final Passphrase mPassphrase;
|
||||
|
||||
// this map contains both decrypted session keys and signed hashes to be
|
||||
// used in the crypto operation described by this parcel.
|
||||
private HashMap<ByteBuffer, byte[]> mCryptoData = new HashMap<>();
|
||||
|
||||
public CryptoInputParcel() {
|
||||
mSignatureTime = new Date();
|
||||
mPassphrase = null;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Date signatureTime, Passphrase passphrase) {
|
||||
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Passphrase passphrase) {
|
||||
mSignatureTime = new Date();
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Date signatureTime) {
|
||||
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
|
||||
mPassphrase = null;
|
||||
}
|
||||
|
||||
protected CryptoInputParcel(Parcel source) {
|
||||
mSignatureTime = new Date(source.readLong());
|
||||
mPassphrase = source.readParcelable(getClass().getClassLoader());
|
||||
|
||||
{
|
||||
int count = source.readInt();
|
||||
mCryptoData = new HashMap<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
byte[] key = source.createByteArray();
|
||||
byte[] value = source.createByteArray();
|
||||
mCryptoData.put(ByteBuffer.wrap(key), value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(mSignatureTime.getTime());
|
||||
dest.writeParcelable(mPassphrase, 0);
|
||||
|
||||
dest.writeInt(mCryptoData.size());
|
||||
for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) {
|
||||
dest.writeByteArray(entry.getKey().array());
|
||||
dest.writeByteArray(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void addCryptoData(byte[] hash, byte[] signedHash) {
|
||||
mCryptoData.put(ByteBuffer.wrap(hash), signedHash);
|
||||
}
|
||||
|
||||
public Map<ByteBuffer, byte[]> getCryptoData() {
|
||||
return Collections.unmodifiableMap(mCryptoData);
|
||||
}
|
||||
|
||||
public Date getSignatureTime() {
|
||||
return mSignatureTime;
|
||||
}
|
||||
|
||||
public boolean hasPassphrase() {
|
||||
return mPassphrase != null;
|
||||
}
|
||||
|
||||
public Passphrase getPassphrase() {
|
||||
return mPassphrase;
|
||||
}
|
||||
|
||||
public static final Creator<CryptoInputParcel> CREATOR = new Creator<CryptoInputParcel>() {
|
||||
public CryptoInputParcel createFromParcel(final Parcel source) {
|
||||
return new CryptoInputParcel(source);
|
||||
}
|
||||
|
||||
public CryptoInputParcel[] newArray(final int size) {
|
||||
return new CryptoInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("CryptoInput: { ");
|
||||
b.append(mSignatureTime).append(" ");
|
||||
if (mPassphrase != null) {
|
||||
b.append("passphrase");
|
||||
}
|
||||
if (mCryptoData != null) {
|
||||
b.append(mCryptoData.size());
|
||||
b.append(" hashes ");
|
||||
}
|
||||
b.append("}");
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package org.sufficientlysecure.keychain.service.input;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants.key;
|
||||
|
||||
|
||||
public class RequiredInputParcel implements Parcelable {
|
||||
|
||||
public enum RequiredInputType {
|
||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
|
||||
}
|
||||
|
||||
public Date mSignatureTime;
|
||||
|
||||
public final RequiredInputType mType;
|
||||
|
||||
public final byte[][] mInputHashes;
|
||||
public final int[] mSignAlgos;
|
||||
|
||||
private Long mMasterKeyId;
|
||||
private Long mSubKeyId;
|
||||
|
||||
private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes,
|
||||
int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) {
|
||||
mType = type;
|
||||
mInputHashes = inputHashes;
|
||||
mSignAlgos = signAlgos;
|
||||
mSignatureTime = signatureTime;
|
||||
mMasterKeyId = masterKeyId;
|
||||
mSubKeyId = subKeyId;
|
||||
}
|
||||
|
||||
public RequiredInputParcel(Parcel source) {
|
||||
mType = RequiredInputType.values()[source.readInt()];
|
||||
|
||||
// 0 = none, 1 = both, 2 = only hashes (decrypt)
|
||||
int hashTypes = source.readInt();
|
||||
if (hashTypes != 0) {
|
||||
int count = source.readInt();
|
||||
mInputHashes = new byte[count][];
|
||||
if (hashTypes == 1) {
|
||||
mSignAlgos = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
mInputHashes[i] = source.createByteArray();
|
||||
mSignAlgos[i] = source.readInt();
|
||||
}
|
||||
} else {
|
||||
mSignAlgos = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
mInputHashes[i] = source.createByteArray();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mInputHashes = null;
|
||||
mSignAlgos = null;
|
||||
}
|
||||
|
||||
mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
mSubKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
|
||||
}
|
||||
|
||||
public Long getMasterKeyId() {
|
||||
return mMasterKeyId;
|
||||
}
|
||||
|
||||
public Long getSubKeyId() {
|
||||
return mSubKeyId;
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createNfcSignOperation(
|
||||
byte[] inputHash, int signAlgo, Date signatureTime) {
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
||||
new byte[][] { inputHash }, new int[] { signAlgo },
|
||||
signatureTime, null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash, long subKeyId) {
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
|
||||
new byte[][] { inputHash }, null, null, null, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredSignPassphrase(
|
||||
long masterKeyId, long subKeyId, Date signatureTime) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, signatureTime, masterKeyId, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredDecryptPassphrase(
|
||||
long masterKeyId, long subKeyId) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, null, masterKeyId, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredSymmetricPassphrase() {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC,
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredPassphrase(
|
||||
RequiredInputParcel req) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mType.ordinal());
|
||||
if (mInputHashes != null) {
|
||||
dest.writeInt(mSignAlgos != null ? 1 : 2);
|
||||
dest.writeInt(mInputHashes.length);
|
||||
for (int i = 0; i < mInputHashes.length; i++) {
|
||||
dest.writeByteArray(mInputHashes[i]);
|
||||
if (mSignAlgos != null) {
|
||||
dest.writeInt(mSignAlgos[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mSignatureTime != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureTime.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final Creator<RequiredInputParcel> CREATOR = new Creator<RequiredInputParcel>() {
|
||||
public RequiredInputParcel createFromParcel(final Parcel source) {
|
||||
return new RequiredInputParcel(source);
|
||||
}
|
||||
|
||||
public RequiredInputParcel[] newArray(final int size) {
|
||||
return new RequiredInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static class NfcSignOperationsBuilder {
|
||||
Date mSignatureTime;
|
||||
ArrayList<Integer> mSignAlgos = new ArrayList<>();
|
||||
ArrayList<byte[]> mInputHashes = new ArrayList<>();
|
||||
Long mMasterKeyId;
|
||||
Long mSubKeyId;
|
||||
|
||||
public NfcSignOperationsBuilder(Date signatureTime, Long masterKeyId, Long subKeyId) {
|
||||
mSignatureTime = signatureTime;
|
||||
mMasterKeyId = masterKeyId;
|
||||
mSubKeyId = subKeyId;
|
||||
}
|
||||
|
||||
public RequiredInputParcel build() {
|
||||
byte[][] inputHashes = new byte[mInputHashes.size()][];
|
||||
mInputHashes.toArray(inputHashes);
|
||||
int[] signAlgos = new int[mSignAlgos.size()];
|
||||
for (int i = 0; i < mSignAlgos.size(); i++) {
|
||||
signAlgos[i] = mSignAlgos.get(i);
|
||||
}
|
||||
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
||||
inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
|
||||
}
|
||||
|
||||
public void addHash(byte[] hash, int algo) {
|
||||
mInputHashes.add(hash);
|
||||
mSignAlgos.add(algo);
|
||||
}
|
||||
|
||||
public void addAll(RequiredInputParcel input) {
|
||||
if (!mSignatureTime.equals(input.mSignatureTime)) {
|
||||
throw new AssertionError("input times must match, this is a programming error!");
|
||||
}
|
||||
if (input.mType != RequiredInputType.NFC_SIGN) {
|
||||
throw new AssertionError("operation types must match, this is a progrmming error!");
|
||||
}
|
||||
|
||||
Collections.addAll(mInputHashes, input.mInputHashes);
|
||||
for (int signAlgo : input.mSignAlgos) {
|
||||
mSignAlgos.add(signAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mInputHashes.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class CertifyFingerprintActivity extends BaseActivity {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
/**
|
||||
* Signs the specified public key with the specified secret master key
|
||||
|
||||
@@ -25,8 +25,6 @@ import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
@@ -56,23 +54,21 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CertifyKeyFragment extends LoaderFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public class CertifyKeyFragment extends CryptoOperationFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
ListView mUserIds;
|
||||
@@ -102,9 +98,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setContentShown(false);
|
||||
|
||||
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
|
||||
if (mPubMasterKeyIds == null) {
|
||||
Log.e(Constants.TAG, "List of key ids to certify missing!");
|
||||
@@ -114,6 +107,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
|
||||
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
|
||||
KeychainIntentService.EXTRA_MESSENGER);
|
||||
mPassthroughMessenger = null; // TODO remove, development hack
|
||||
|
||||
// preselect certify key id if given
|
||||
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
||||
@@ -143,9 +137,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
|
||||
View view = inflater.inflate(R.layout.certify_key_fragment, getContainer());
|
||||
View view = inflater.inflate(R.layout.certify_key_fragment, null);
|
||||
|
||||
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
|
||||
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
|
||||
@@ -173,7 +165,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
|
||||
Notify.Style.ERROR).show();
|
||||
} else {
|
||||
initiateCertifying();
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -183,7 +175,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
mUploadKeyCheckbox.setChecked(false);
|
||||
}
|
||||
|
||||
return root;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,17 +214,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
}) {
|
||||
@Override
|
||||
public byte[] getBlob(int column) {
|
||||
// For some reason, getBlob was not implemented before ICS
|
||||
if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
try {
|
||||
// haha, yes there is int.class
|
||||
Method m = MatrixCursor.class.getDeclaredMethod("get", new Class[]{int.class});
|
||||
m.setAccessible(true);
|
||||
return (byte[]) m.invoke(this, 1);
|
||||
} catch (Exception e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
return super.getBlob(column);
|
||||
}
|
||||
};
|
||||
@@ -307,7 +288,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
}
|
||||
|
||||
mUserIdsAdapter.swapCursor(matrix);
|
||||
setContentShown(true, isResumed());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -315,49 +295,8 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateCertifying() {
|
||||
// get the user's passphrase for this key (if required)
|
||||
Passphrase passphrase;
|
||||
try {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), mSignMasterKeyId, mSignMasterKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Key not found!", e);
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
if (passphrase == null) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSignMasterKeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
} else {
|
||||
startCertifying();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
startCertifying();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startCertifying() {
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Bail out if there is not at least one user id selected
|
||||
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
|
||||
if (certifyActions.isEmpty()) {
|
||||
@@ -372,6 +311,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
|
||||
parcel.mCertifyActions.addAll(certifyActions);
|
||||
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
@@ -396,11 +336,17 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
// handle messages by KeychainIntentCryptoServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
|
||||
|
||||
Intent intent = new Intent();
|
||||
|
||||
@@ -17,17 +17,25 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CreateKeyActivity extends BaseActivity {
|
||||
public class CreateKeyActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
@@ -35,6 +43,10 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
|
||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
||||
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
|
||||
|
||||
public static final String FRAGMENT_TAG = "currentFragment";
|
||||
|
||||
String mName;
|
||||
@@ -60,14 +72,29 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
|
||||
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
|
||||
} else {
|
||||
// Initialize members with default values for a new instance
|
||||
mName = getIntent().getStringExtra(EXTRA_NAME);
|
||||
mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
|
||||
mFirstTime = getIntent().getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||
|
||||
// Start with first fragment of wizard
|
||||
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
|
||||
loadFragment(frag, FragAction.START);
|
||||
Intent intent = getIntent();
|
||||
// Initialize members with default values for a new instance
|
||||
mName = intent.getStringExtra(EXTRA_NAME);
|
||||
mEmail = intent.getStringExtra(EXTRA_EMAIL);
|
||||
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||
|
||||
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
|
||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
||||
|
||||
Fragment frag2 = CreateKeyYubiImportFragment.createInstance(
|
||||
nfcFingerprints, nfcAid, nfcUserId);
|
||||
loadFragment(frag2, FragAction.START);
|
||||
|
||||
setTitle(R.string.title_import_keys);
|
||||
return;
|
||||
} else {
|
||||
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
|
||||
loadFragment(frag, FragAction.START);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mFirstTime) {
|
||||
@@ -79,6 +106,38 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
if (mCurrentFragment instanceof NfcListenerFragment) {
|
||||
((NfcListenerFragment) mCurrentFragment).onNfcPerform();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] scannedFingerprints = nfcGetFingerprints();
|
||||
byte[] nfcAid = nfcGetAid();
|
||||
String userId = nfcGetUserId();
|
||||
|
||||
try {
|
||||
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
|
||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Fragment frag = CreateKeyYubiImportFragment.createInstance(
|
||||
scannedFingerprints, nfcAid, userId);
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -125,8 +184,14 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
|
||||
}
|
||||
|
||||
interface NfcListenerFragment {
|
||||
public void onNfcPerform() throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
if (mAdditionalEmailModels == null) {
|
||||
mAdditionalEmailModels = new ArrayList<>();
|
||||
if (mCreateKeyActivity.mAdditionalEmails != null) {
|
||||
setAdditionalEmails(mCreateKeyActivity.mAdditionalEmails);
|
||||
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,12 +209,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
return emails;
|
||||
}
|
||||
|
||||
private void setAdditionalEmails(ArrayList<String> emails) {
|
||||
for (String email : emails) {
|
||||
mAdditionalEmailModels.add(new EmailAdapter.ViewModel(email));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -244,8 +238,7 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
// each data item is just a string in this case
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public TextView mTextView;
|
||||
public ImageButton mDeleteButton;
|
||||
|
||||
@@ -289,7 +282,10 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||
if (holder instanceof ViewHolder) {
|
||||
if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
} else if (holder instanceof ViewHolder) {
|
||||
ViewHolder thisHolder = (ViewHolder) holder;
|
||||
// - get element from your dataset at this position
|
||||
// - replace the contents of the view with that element
|
||||
@@ -302,9 +298,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
remove(model);
|
||||
}
|
||||
});
|
||||
} else if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +325,12 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
notifyItemInserted(mDataset.size() - 1);
|
||||
}
|
||||
|
||||
private void addAll(ArrayList<String> emails) {
|
||||
for (String email : emails) {
|
||||
mDataset.add(new EmailAdapter.ViewModel(email));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ViewModel model) {
|
||||
int position = mDataset.indexOf(model);
|
||||
mDataset.remove(position);
|
||||
|
||||
@@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
|
||||
mCreateKey = view.findViewById(R.id.create_key_create_key_button);
|
||||
mImportKey = view.findViewById(R.id.create_key_import_button);
|
||||
// mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
|
||||
mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
|
||||
mCancel = (TextView) view.findViewById(R.id.create_key_cancel);
|
||||
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
@@ -95,6 +95,14 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
mYubiKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment();
|
||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
mImportKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment {
|
||||
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_AID = "aid";
|
||||
public static final String ARG_USER_ID = "user_ids";
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
private byte[] mNfcFingerprints;
|
||||
private long mNfcMasterKeyId;
|
||||
private byte[] mNfcAid;
|
||||
private String mNfcUserId;
|
||||
private String mNfcFingerprint;
|
||||
private ImportKeysListFragment mListFragment;
|
||||
private TextView vSerNo;
|
||||
private TextView vUserId;
|
||||
|
||||
public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
|
||||
|
||||
CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
|
||||
args.putByteArray(ARG_AID, nfcAid);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
|
||||
mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT);
|
||||
mNfcAid = args.getByteArray(ARG_AID);
|
||||
mNfcUserId = args.getString(ARG_USER_ID);
|
||||
|
||||
mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
|
||||
mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false);
|
||||
|
||||
vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
|
||||
vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
|
||||
|
||||
{
|
||||
View mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
View mNextButton = view.findViewById(R.id.create_key_next_button);
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
importKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true);
|
||||
|
||||
view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
refreshSearch();
|
||||
}
|
||||
});
|
||||
|
||||
setData();
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import")
|
||||
.commit();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle args) {
|
||||
super.onSaveInstanceState(args);
|
||||
|
||||
args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints);
|
||||
args.putByteArray(ARG_AID, mNfcAid);
|
||||
args.putString(ARG_USER_ID, mNfcUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
public void setData() {
|
||||
String serno = Hex.toHexString(mNfcAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.yubikey_serno, serno));
|
||||
|
||||
if (!mNfcUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder_unset));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshSearch() {
|
||||
mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint,
|
||||
Preferences.getPreferences(getActivity()).getCloudSearchPrefs()));
|
||||
}
|
||||
|
||||
public void importKey() {
|
||||
|
||||
// Message is received after decrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
|
||||
) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
ImportKeyResult result =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getActivity(), ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(mNfcMasterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
|
||||
startActivity(intent);
|
||||
getActivity().finish();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
|
||||
String hexFp = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||
keyList.add(new ParcelableKeyRing(hexFp, null, null));
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
|
||||
|
||||
{
|
||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||
Preferences.CloudSearchPrefs cloudPrefs =
|
||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||
}
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNfcPerform() throws IOException {
|
||||
|
||||
mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints();
|
||||
mNfcAid = mCreateKeyActivity.nfcGetAid();
|
||||
mNfcUserId = mCreateKeyActivity.nfcGetUserId();
|
||||
|
||||
mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
|
||||
mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
|
||||
setData();
|
||||
refreshSearch();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
|
||||
|
||||
public class CreateKeyYubiWaitFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
View mBackButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false);
|
||||
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,12 +21,12 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class DecryptFilesActivity extends BaseActivity {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
@@ -32,13 +33,13 @@ import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
@@ -63,6 +64,8 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
private Uri mInputUri = null;
|
||||
private Uri mOutputUri = null;
|
||||
|
||||
private String mCurrentCryptoOperation;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
@@ -90,9 +93,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
|
||||
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// reset state
|
||||
mPassphrase = null;
|
||||
mNfcDecryptedSessionKey = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
@@ -144,7 +144,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
return;
|
||||
}
|
||||
|
||||
decryptOriginalFilename();
|
||||
startDecryptFilenames();
|
||||
}
|
||||
|
||||
private String removeEncryptedAppend(String name) {
|
||||
@@ -157,110 +157,45 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
}
|
||||
|
||||
private void askForOutputFilename(String originalFilename) {
|
||||
String targetName;
|
||||
if (!TextUtils.isEmpty(originalFilename)) {
|
||||
targetName = originalFilename;
|
||||
} else {
|
||||
targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
|
||||
if (TextUtils.isEmpty(originalFilename)) {
|
||||
originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(mInputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, targetName);
|
||||
File targetFile = new File(parentDir, originalFilename);
|
||||
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
||||
} else {
|
||||
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
|
||||
FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptOriginalFilename() {
|
||||
Log.d(Constants.TAG, "decryptOriginalFilename");
|
||||
private void startDecrypt() {
|
||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA);
|
||||
|
||||
// data
|
||||
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
|
||||
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after decrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_decrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
// go on...
|
||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
private void startDecryptFilenames() {
|
||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
// use current operation, either decrypt metadata or decrypt payload
|
||||
intent.setAction(mCurrentCryptoOperation);
|
||||
|
||||
// data
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
|
||||
@@ -269,8 +204,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
|
||||
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@@ -280,10 +214,16 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
getString(R.string.progress_decrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
@@ -291,39 +231,39 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
if (pgpResult.success()) {
|
||||
|
||||
// display signature result in activity
|
||||
onResult(pgpResult);
|
||||
switch (mCurrentCryptoOperation) {
|
||||
case KeychainIntentService.ACTION_DECRYPT_METADATA: {
|
||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
||||
break;
|
||||
}
|
||||
case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
|
||||
// display signature result in activity
|
||||
onResult(pgpResult);
|
||||
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
setInputUri(null);
|
||||
}
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
setInputUri(null);
|
||||
}
|
||||
|
||||
/*
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setInputData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
/*
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setInputData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "Bug: not supported operation!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
}
|
||||
@@ -346,22 +286,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
decryptOriginalFilename();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC_DECRYPT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
decryptOriginalFilename();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_INPUT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
setInputUri(data.getData());
|
||||
@@ -373,7 +297,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
// This happens after output file was selected, so start our operation
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mOutputUri = data.getData();
|
||||
decryptStart();
|
||||
startDecrypt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -383,4 +307,5 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -30,16 +29,13 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public abstract class DecryptFragment extends Fragment {
|
||||
public abstract class DecryptFragment extends CryptoOperationFragment {
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC_DECRYPT = 0x00008002;
|
||||
|
||||
protected long mSignatureKeyId = 0;
|
||||
|
||||
protected LinearLayout mResultLayout;
|
||||
@@ -56,11 +52,6 @@ public abstract class DecryptFragment extends Fragment {
|
||||
protected TextView mSignatureEmail;
|
||||
protected TextView mSignatureAction;
|
||||
|
||||
|
||||
// State
|
||||
protected Passphrase mPassphrase;
|
||||
protected byte[] mNfcDecryptedSessionKey;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@@ -95,25 +86,6 @@ public abstract class DecryptFragment extends Fragment {
|
||||
startActivity(viewKeyIntent);
|
||||
}
|
||||
|
||||
protected void startPassphraseDialog(long subkeyId) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
}
|
||||
|
||||
protected void startNfcDecrypt(long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getActivity(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return returns false if signature is invalid, key is revoked or expired.
|
||||
@@ -253,9 +225,4 @@ public abstract class DecryptFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden by MessageFragment and FileFragment to start actual decryption
|
||||
*/
|
||||
protected abstract void decryptStart();
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -30,7 +29,6 @@ import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@@ -38,6 +36,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -51,10 +50,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
// view
|
||||
private LinearLayout mValidLayout;
|
||||
private LinearLayout mInvalidLayout;
|
||||
private Button mInvalidButton;
|
||||
private TextView mText;
|
||||
private View mShareButton;
|
||||
private View mCopyButton;
|
||||
|
||||
// model
|
||||
private String mCiphertext;
|
||||
@@ -81,23 +77,26 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
|
||||
mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid);
|
||||
mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid);
|
||||
mInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
|
||||
mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
|
||||
mShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
|
||||
mCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
|
||||
mShareButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
View vShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
|
||||
vShareButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(sendWithChooserExcludingEncrypt(mText.getText().toString()));
|
||||
}
|
||||
});
|
||||
mCopyButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
View vCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
|
||||
vCopyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
copyToClipboard(mText.getText().toString());
|
||||
}
|
||||
});
|
||||
mInvalidButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
|
||||
vInvalidButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mInvalidLayout.setVisibility(View.GONE);
|
||||
@@ -143,14 +142,12 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
|
||||
if (ciphertext != null) {
|
||||
mCiphertext = ciphertext;
|
||||
decryptStart();
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
@@ -160,10 +157,10 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// data
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@@ -177,6 +174,11 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
@@ -184,20 +186,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
if (pgpResult.success()) {
|
||||
|
||||
byte[] decryptedMessage = returnData
|
||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
||||
@@ -245,34 +234,4 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
decryptStart();
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC_DECRYPT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
decryptStart();
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EditKeyActivity extends BaseActivity {
|
||||
|
||||
@@ -51,27 +51,27 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.*;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
public class EditKeyFragment extends CryptoOperationFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
|
||||
private ListView mUserIdsList;
|
||||
private ListView mSubkeysList;
|
||||
private ListView mUserIdsAddedList;
|
||||
@@ -96,7 +96,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
private String mPrimaryUserId;
|
||||
private Passphrase mCurrentPassphrase;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@@ -125,8 +124,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
|
||||
View view = inflater.inflate(R.layout.edit_key_fragment, null);
|
||||
|
||||
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
|
||||
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
|
||||
@@ -136,7 +134,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id);
|
||||
mAddSubkey = view.findViewById(R.id.edit_key_action_add_key);
|
||||
|
||||
return root;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,7 +149,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
if (mDataUri == null) {
|
||||
returnKeyringParcel();
|
||||
} else {
|
||||
saveInDatabase(mCurrentPassphrase);
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
}, new OnClickListener() {
|
||||
@@ -181,18 +179,12 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) {
|
||||
mSaveKeyringParcel = saveKeyringParcel;
|
||||
mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId;
|
||||
if (saveKeyringParcel.mNewUnlock != null) {
|
||||
mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase;
|
||||
}
|
||||
|
||||
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true);
|
||||
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true);
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
|
||||
// show directly
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
@@ -212,9 +204,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
case GNU_DUMMY:
|
||||
finishWithError(LogType.MSG_EK_ERROR_DUMMY);
|
||||
return;
|
||||
case DIVERT_TO_CARD:
|
||||
finishWithError(LogType.MSG_EK_ERROR_DIVERT);
|
||||
break;
|
||||
}
|
||||
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
|
||||
@@ -225,24 +214,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentPassphrase == null) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSaveKeyringParcel.mMasterKeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
} else {
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
}
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel);
|
||||
mUserIdsList.setAdapter(mUserIdsAdapter);
|
||||
@@ -258,28 +233,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mCurrentPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mChangePassphrase.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -318,7 +271,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
setContentShown(false);
|
||||
|
||||
switch (id) {
|
||||
case LOADER_ID_USER_IDS: {
|
||||
@@ -351,7 +303,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
break;
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,7 +344,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, mCurrentPassphrase, R.string.title_change_passphrase);
|
||||
messenger, R.string.title_change_passphrase);
|
||||
|
||||
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
@@ -589,8 +540,11 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
private void saveInDatabase(Passphrase passphrase) {
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
|
||||
Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
|
||||
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
@@ -602,6 +556,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
|
||||
// get returned data bundle
|
||||
@@ -637,7 +595,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.View;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class EncryptActivity extends BaseActivity {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||
|
||||
// For NFC data
|
||||
protected Passphrase mSigningKeyPassphrase = null;
|
||||
protected Date mNfcTimestamp = null;
|
||||
protected byte[] mNfcHash = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected void startPassphraseDialog(long subkeyId) {
|
||||
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
}
|
||||
|
||||
protected void startNfcSign(long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(this, NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_SIGN_HASH);
|
||||
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
mSigningKeyPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
startEncrypt();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
mNfcHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
startEncrypt();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startEncrypt() {
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle());
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
this,
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
|
||||
PgpSignEncryptResult pgpResult = result.getPending();
|
||||
|
||||
if (pgpResult != null && pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
|
||||
mNfcTimestamp = pgpResult.getNfcTimestamp();
|
||||
startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(),
|
||||
pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(EncryptActivity.this).show();
|
||||
}
|
||||
|
||||
// no matter the result, reset parameters
|
||||
mSigningKeyPassphrase = null;
|
||||
mNfcHash = null;
|
||||
mNfcTimestamp = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
protected abstract boolean inputIsValid();
|
||||
|
||||
protected abstract void onEncryptSuccess(SignEncryptResult result);
|
||||
|
||||
protected abstract SignEncryptParcel createEncryptBundle();
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface EncryptActivityInterface {
|
||||
|
||||
public interface UpdateListener {
|
||||
void onNotifyUpdate();
|
||||
}
|
||||
|
||||
public boolean isUseArmor();
|
||||
public boolean isUseCompression();
|
||||
public boolean isEncryptFilenames();
|
||||
public boolean isHiddenRecipients();
|
||||
|
||||
public long getSignatureKey();
|
||||
public long[] getEncryptionKeys();
|
||||
public String[] getEncryptionUsers();
|
||||
public void setSignatureKey(long signatureKey);
|
||||
public void setEncryptionKeys(long[] encryptionKeys);
|
||||
public void setEncryptionUsers(String[] encryptionUsers);
|
||||
|
||||
public void setPassphrase(Passphrase passphrase);
|
||||
|
||||
// ArrayList on purpose as only those are parcelable
|
||||
public ArrayList<Uri> getInputUris();
|
||||
public ArrayList<Uri> getOutputUris();
|
||||
public void setInputUris(ArrayList<Uri> uris);
|
||||
public void setOutputUris(ArrayList<Uri> uris);
|
||||
|
||||
public String getMessage();
|
||||
public void setMessage(String message);
|
||||
|
||||
/**
|
||||
* Call this to notify the UI for changes done on the array lists or arrays,
|
||||
* automatically called if setter is used
|
||||
*/
|
||||
public void notifyUpdate();
|
||||
|
||||
public void startEncrypt(boolean share);
|
||||
}
|
||||
@@ -37,10 +37,6 @@ import java.util.regex.Matcher;
|
||||
|
||||
public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
|
||||
View mEncryptFile;
|
||||
View mEncryptText;
|
||||
View mDecryptFile;
|
||||
View mDecryptFromClipboard;
|
||||
View mClipboardIcon;
|
||||
|
||||
@Override
|
||||
@@ -53,10 +49,10 @@ public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
|
||||
|
||||
mEncryptFile = view.findViewById(R.id.encrypt_files);
|
||||
mEncryptText = view.findViewById(R.id.encrypt_text);
|
||||
mDecryptFile = view.findViewById(R.id.decrypt_files);
|
||||
mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
|
||||
View mEncryptFile = view.findViewById(R.id.encrypt_files);
|
||||
View mEncryptText = view.findViewById(R.id.encrypt_text);
|
||||
View mDecryptFile = view.findViewById(R.id.decrypt_files);
|
||||
View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
|
||||
mClipboardIcon = view.findViewById(R.id.clipboard_icon);
|
||||
|
||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,32 +18,25 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptFilesActivity extends EncryptActivity implements EncryptActivityInterface {
|
||||
public class EncryptFilesActivity extends BaseActivity implements
|
||||
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
|
||||
EncryptFilesFragment.IMode {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA;
|
||||
@@ -55,302 +48,22 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
|
||||
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
|
||||
|
||||
// view
|
||||
private int mCurrentMode = MODE_ASYMMETRIC;
|
||||
|
||||
// tabs
|
||||
private static final int MODE_ASYMMETRIC = 0;
|
||||
private static final int MODE_SYMMETRIC = 1;
|
||||
|
||||
// model used by fragments
|
||||
private boolean mUseArmor = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mDeleteAfterEncrypt = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mEncryptFilenames = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mInputUris;
|
||||
private ArrayList<Uri> mOutputUris;
|
||||
private String mMessage = "";
|
||||
|
||||
public boolean isModeSymmetric() {
|
||||
return MODE_SYMMETRIC == mCurrentMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseArmor() {
|
||||
return mUseArmor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseCompression() {
|
||||
return mUseCompression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptFilenames() {
|
||||
return mEncryptFilenames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSignatureKey() {
|
||||
return mSigningKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getEncryptionKeys() {
|
||||
return mEncryptionKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEncryptionUsers() {
|
||||
return mEncryptionUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignatureKey(long signatureKey) {
|
||||
mSigningKeyId = signatureKey;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionKeys(long[] encryptionKeys) {
|
||||
mEncryptionKeyIds = encryptionKeys;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionUsers(String[] encryptionUsers) {
|
||||
mEncryptionUserIds = encryptionUsers;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputUris(ArrayList<Uri> uris) {
|
||||
mInputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputUris(ArrayList<Uri> uris) {
|
||||
mOutputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (fragment instanceof EncryptActivityInterface.UpdateListener) {
|
||||
((UpdateListener) fragment).onNotifyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
startEncrypt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptSuccess(final SignEncryptResult result) {
|
||||
if (mDeleteAfterEncrypt) {
|
||||
final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]);
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris);
|
||||
deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
|
||||
|
||||
@Override
|
||||
public void onDeleted() {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(EncryptFilesActivity.this).show();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
|
||||
mInputUris.clear();
|
||||
notifyUpdate();
|
||||
} else {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(EncryptFilesActivity.this).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.addInputUris(mInputUris);
|
||||
data.addOutputUris(mOutputUris);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setEnableAsciiArmorOutput(mUseArmor);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
|
||||
} else {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
// file checks
|
||||
|
||||
if (mInputUris.isEmpty()) {
|
||||
Notify.create(this, R.string.no_file_selected, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
} else if (mInputUris.size() > 1 && !mShareAfterEncrypt) {
|
||||
// This should be impossible...
|
||||
return false;
|
||||
} else if (mInputUris.size() != mOutputUris.size()) {
|
||||
// This as well
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
// Files must be encrypted, only text can be signed-only right now
|
||||
if (!gotEncryptionKeys) {
|
||||
Notify.create(this, R.string.select_encryption_key, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Fragment mModeFragment;
|
||||
EncryptFilesFragment mEncryptFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
updateModeFragment();
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -358,73 +71,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
setContentView(R.layout.encrypt_files_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_file_activity, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
|
||||
updateModeFragment();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_armor: {
|
||||
mUseArmor = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_delete_after_encrypt: {
|
||||
mDeleteAfterEncrypt = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_encrypt_filenames: {
|
||||
mEncryptFilenames = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateModeFragment() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_pager_mode,
|
||||
mCurrentMode == MODE_SYMMETRIC
|
||||
? new EncryptSymmetricFragment()
|
||||
: new EncryptAsymmetricFragment()
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
private void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
@@ -453,14 +103,56 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
}
|
||||
|
||||
mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
|
||||
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
|
||||
|
||||
// preselect keys given by intent
|
||||
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
if (savedInstanceState == null) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
// Save uris
|
||||
mInputUris = uris;
|
||||
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
|
||||
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
|
||||
|
||||
mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
|
||||
transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeChanged(boolean symmetric) {
|
||||
// switch fragments
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_mode_container,
|
||||
symmetric
|
||||
? EncryptModeSymmetricFragment.newInstance()
|
||||
: EncryptModeAsymmetricFragment.newInstance(0, null)
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId) {
|
||||
mEncryptFragment.setSigningKeyId(signatureKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
|
||||
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
|
||||
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseChanged(Passphrase passphrase) {
|
||||
mEncryptFragment.setPassphrase(passphrase);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 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,57 +17,130 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.widget.DefaultItemAnimator;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptFilesFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptFilesFragment extends CryptoOperationFragment {
|
||||
|
||||
public interface IMode {
|
||||
public void onModeChanged(boolean symmetric);
|
||||
}
|
||||
|
||||
public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||
public static final String ARG_URIS = "uris";
|
||||
|
||||
private static final int REQUEST_CODE_INPUT = 0x00007003;
|
||||
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
private IMode mModeInterface;
|
||||
|
||||
// view
|
||||
private View mAddView;
|
||||
private ListView mSelectedFiles;
|
||||
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
|
||||
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>();
|
||||
private boolean mSymmetricMode = false;
|
||||
private boolean mUseArmor = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mDeleteAfterEncrypt = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mEncryptFilenames = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mOutputUris = new ArrayList<>();
|
||||
|
||||
private RecyclerView mSelectedFiles;
|
||||
|
||||
ArrayList<FilesAdapter.ViewModel> mFilesModels;
|
||||
FilesAdapter mFilesAdapter;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
|
||||
EncryptFilesFragment frag = new EncryptFilesFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
|
||||
args.putParcelableArrayList(ARG_URIS, uris);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
}
|
||||
|
||||
public void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptionUserIds = encryptionUserIds;
|
||||
}
|
||||
|
||||
public void setSigningKeyId(long signingKeyId) {
|
||||
mSigningKeyId = signingKeyId;
|
||||
}
|
||||
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mModeInterface = (IMode) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity + " must be IMode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,17 +150,28 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false);
|
||||
mSelectedFiles = (RecyclerView) view.findViewById(R.id.selected_files_list);
|
||||
|
||||
mAddView = inflater.inflate(R.layout.file_list_entry_add, null);
|
||||
mAddView.setOnClickListener(new View.OnClickListener() {
|
||||
mSelectedFiles.addItemDecoration(new SpacesItemDecoration(
|
||||
FormattingUtils.dpToPx(getActivity(), 4)));
|
||||
mSelectedFiles.setHasFixedSize(true);
|
||||
mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
mSelectedFiles.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
mFilesModels = new ArrayList<>();
|
||||
mFilesAdapter = new FilesAdapter(getActivity(), mFilesModels, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addInputUri();
|
||||
}
|
||||
});
|
||||
mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
|
||||
mSelectedFiles.addFooterView(mAddView);
|
||||
mSelectedFiles.setAdapter(mAdapter);
|
||||
|
||||
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_URIS);
|
||||
if (inputUris != null) {
|
||||
mFilesAdapter.addAll(inputUris);
|
||||
}
|
||||
mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR);
|
||||
mSelectedFiles.setAdapter(mFilesAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -95,7 +179,6 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@@ -103,8 +186,8 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFilesFragment.this, mEncryptInterface.getInputUris().isEmpty() ?
|
||||
null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1),
|
||||
FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ?
|
||||
null : mFilesModels.get(mFilesModels.size() - 1).inputUri,
|
||||
"*/*", REQUEST_CODE_INPUT);
|
||||
}
|
||||
}
|
||||
@@ -114,34 +197,27 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getInputUris().contains(inputUri)) {
|
||||
try {
|
||||
mFilesAdapter.add(inputUri);
|
||||
} catch (IOException e) {
|
||||
Notify.create(getActivity(),
|
||||
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
|
||||
Notify.Style.ERROR).show(this);
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
mEncryptInterface.getInputUris().add(inputUri);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
}
|
||||
|
||||
private void delInputUri(int position) {
|
||||
mEncryptInterface.getInputUris().remove(position);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
}
|
||||
|
||||
private void showOutputFileDialog() {
|
||||
if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) {
|
||||
if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(0);
|
||||
FilesAdapter.ViewModel model = mFilesModels.get(0);
|
||||
String targetName =
|
||||
(mEncryptInterface.isEncryptFilenames() ? "1" : FileHelper.getFilename(getActivity(), inputUri))
|
||||
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(inputUri.getPath());
|
||||
File file = new File(model.inputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, targetName);
|
||||
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
|
||||
@@ -152,44 +228,61 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
}
|
||||
|
||||
private void encryptClicked(boolean share) {
|
||||
if (mEncryptInterface.getInputUris().isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this);
|
||||
if (mFilesModels.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.error_no_file_selected,
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
if (share) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
mOutputUris.clear();
|
||||
int filenameCounter = 1;
|
||||
for (Uri uri : mEncryptInterface.getInputUris()) {
|
||||
for (FilesAdapter.ViewModel model : mFilesModels) {
|
||||
String targetName =
|
||||
(mEncryptInterface.isEncryptFilenames() ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri))
|
||||
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||
filenameCounter++;
|
||||
}
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
startEncrypt(true);
|
||||
} else {
|
||||
if (mEncryptInterface.getInputUris().size() > 1) {
|
||||
Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this);
|
||||
if (mFilesModels.size() > 1) {
|
||||
Notify.create(getActivity(), R.string.error_multi_not_supported,
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
showOutputFileDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public boolean handleClipData(Intent data) {
|
||||
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
|
||||
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
|
||||
Uri uri = data.getClipData().getItemAt(i).getUri();
|
||||
if (uri != null) addInputUri(uri);
|
||||
public void addFile(Intent data) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
addInputUri(data.getData());
|
||||
} else {
|
||||
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
|
||||
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
|
||||
Uri uri = data.getClipData().getItemAt(i).getUri();
|
||||
if (uri != null) {
|
||||
addInputUri(uri);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// fallback, try old method to get single uri
|
||||
addInputUri(data.getData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.encrypt_file_fragment, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.encrypt_save: {
|
||||
encryptClicked(false);
|
||||
@@ -199,6 +292,32 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
encryptClicked(true);
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_symmetric: {
|
||||
mSymmetricMode = item.isChecked();
|
||||
mModeInterface.onModeChanged(mSymmetricMode);
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_armor: {
|
||||
mUseArmor = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_delete_after_encrypt: {
|
||||
mDeleteAfterEncrypt = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_encrypt_filenames: {
|
||||
mEncryptFilenames = item.isChecked();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -206,24 +325,234 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
// file checks
|
||||
|
||||
if (mFilesModels.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
|
||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
|
||||
// This should be impossible...
|
||||
return false;
|
||||
} else if (mFilesModels.size() != mOutputUris.size()) {
|
||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
|
||||
// This as well
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSymmetricMode) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
// Files must be encrypted, only text can be signed-only right now
|
||||
if (!gotEncryptionKeys) {
|
||||
Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onEncryptSuccess(final SignEncryptResult result) {
|
||||
if (mDeleteAfterEncrypt) {
|
||||
DeleteFileDialogFragment deleteFileDialog =
|
||||
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
|
||||
deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
|
||||
|
||||
@Override
|
||||
public void onDeleted() {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
} else {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.addInputUris(mFilesAdapter.getAsArrayList());
|
||||
data.addOutputUris(mOutputUris);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setEnableAsciiArmorOutput(mUseArmor);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
if (mSymmetricMode) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
|
||||
} else {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
|
||||
|
||||
if (!mSymmetricMode && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
Log.d(Constants.TAG, "Input not valid!");
|
||||
return;
|
||||
}
|
||||
Log.d(Constants.TAG, "Input valid!");
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
final SignEncryptParcel input = createEncryptBundle();
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INPUT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) {
|
||||
addInputUri(data.getData());
|
||||
}
|
||||
addFile(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case REQUEST_CODE_OUTPUT: {
|
||||
// This happens after output file was selected, so start our operation
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
mEncryptInterface.getOutputUris().add(data.getData());
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
mOutputUris.clear();
|
||||
mOutputUris.add(data.getData());
|
||||
startEncrypt(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -236,67 +565,190 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
// Clear cache if needed
|
||||
for (Uri uri : new HashSet<>(thumbnailCache.keySet())) {
|
||||
if (!mEncryptInterface.getInputUris().contains(uri)) {
|
||||
thumbnailCache.remove(uri);
|
||||
public static class FilesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private Activity mActivity;
|
||||
private List<ViewModel> mDataset;
|
||||
private View.OnClickListener mFooterOnClickListener;
|
||||
private static final int TYPE_FOOTER = 0;
|
||||
private static final int TYPE_ITEM = 1;
|
||||
|
||||
public static class ViewModel {
|
||||
Uri inputUri;
|
||||
Bitmap thumbnail;
|
||||
String filename;
|
||||
long fileSize;
|
||||
|
||||
ViewModel(Context context, Uri inputUri) {
|
||||
this.inputUri = inputUri;
|
||||
int px = FormattingUtils.dpToPx(context, 48);
|
||||
this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px));
|
||||
this.filename = FileHelper.getFilename(context, inputUri);
|
||||
this.fileSize = FileHelper.getFileSize(context, inputUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on inputUri only
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ViewModel viewModel = (ViewModel) o;
|
||||
return !(inputUri != null ? !inputUri.equals(viewModel.inputUri)
|
||||
: viewModel.inputUri != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on inputUri only
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return inputUri != null ? inputUri.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return inputUri.toString();
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public TextView filename;
|
||||
public TextView fileSize;
|
||||
public View removeButton;
|
||||
public ImageView thumbnail;
|
||||
|
||||
private class SelectedFilesAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mEncryptInterface.getInputUris().size();
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
filename = (TextView) itemView.findViewById(R.id.filename);
|
||||
fileSize = (TextView) itemView.findViewById(R.id.filesize);
|
||||
removeButton = itemView.findViewById(R.id.action_remove_file_from_list);
|
||||
thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mEncryptInterface.getInputUris().get(position);
|
||||
class FooterHolder extends RecyclerView.ViewHolder {
|
||||
public Button mAddButton;
|
||||
|
||||
public FooterHolder(View itemView) {
|
||||
super(itemView);
|
||||
mAddButton = (Button) itemView.findViewById(R.id.file_list_entry_add);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).hashCode();
|
||||
// Provide a suitable constructor (depends on the kind of dataset)
|
||||
public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
|
||||
mActivity = activity;
|
||||
mDataset = myDataset;
|
||||
mFooterOnClickListener = onFooterClickListener;
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(position);
|
||||
View view;
|
||||
if (convertView == null) {
|
||||
view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null);
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == TYPE_FOOTER) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.file_list_entry_add, parent, false);
|
||||
return new FooterHolder(v);
|
||||
} else {
|
||||
view = convertView;
|
||||
//inflate your layout and pass it to view holder
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.file_list_entry, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
((TextView) view.findViewById(R.id.filename)).setText(FileHelper.getFilename(getActivity(), inputUri));
|
||||
long size = FileHelper.getFileSize(getActivity(), inputUri);
|
||||
if (size == -1) {
|
||||
((TextView) view.findViewById(R.id.filesize)).setText("");
|
||||
} else {
|
||||
((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size));
|
||||
}
|
||||
view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
delInputUri(position);
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||
if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
} else if (holder instanceof ViewHolder) {
|
||||
ViewHolder thisHolder = (ViewHolder) holder;
|
||||
// - get element from your dataset at this position
|
||||
// - replace the contents of the view with that element
|
||||
final ViewModel model = mDataset.get(position);
|
||||
|
||||
thisHolder.filename.setText(model.filename);
|
||||
if (model.fileSize == -1) {
|
||||
thisHolder.fileSize.setText("");
|
||||
} else {
|
||||
thisHolder.fileSize.setText(FileHelper.readableFileSize(model.fileSize));
|
||||
}
|
||||
thisHolder.removeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
remove(model);
|
||||
}
|
||||
});
|
||||
if (model.thumbnail != null) {
|
||||
thisHolder.thumbnail.setImageBitmap(model.thumbnail);
|
||||
} else {
|
||||
thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am);
|
||||
}
|
||||
});
|
||||
int px = FormattingUtils.dpToPx(getActivity(), 48);
|
||||
if (!thumbnailCache.containsKey(inputUri)) {
|
||||
thumbnailCache.put(inputUri, FileHelper.getThumbnail(getActivity(), inputUri, new Point(px, px)));
|
||||
}
|
||||
Bitmap bitmap = thumbnailCache.get(inputUri);
|
||||
if (bitmap != null) {
|
||||
((ImageView) view.findViewById(R.id.thumbnail)).setImageBitmap(bitmap);
|
||||
} else {
|
||||
((ImageView) view.findViewById(R.id.thumbnail)).setImageResource(R.drawable.ic_doc_generic_am);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
// Return the size of your dataset (invoked by the layout manager)
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mDataset.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (isPositionFooter(position)) {
|
||||
return TYPE_FOOTER;
|
||||
} else {
|
||||
return TYPE_ITEM;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPositionFooter(int position) {
|
||||
return position == mDataset.size();
|
||||
}
|
||||
|
||||
public void add(Uri inputUri) throws IOException {
|
||||
ViewModel newModel = new ViewModel(mActivity, inputUri);
|
||||
if (mDataset.contains(newModel)) {
|
||||
throw new IOException("Already added!");
|
||||
}
|
||||
mDataset.add(newModel);
|
||||
notifyItemInserted(mDataset.size() - 1);
|
||||
}
|
||||
|
||||
public void addAll(ArrayList<Uri> inputUris) {
|
||||
if (inputUris != null) {
|
||||
int startIndex = mDataset.size();
|
||||
for (Uri inputUri : inputUris) {
|
||||
ViewModel newModel = new ViewModel(mActivity, inputUri);
|
||||
if (mDataset.contains(newModel)) {
|
||||
Log.e(Constants.TAG, "Skipped duplicate " + inputUri.toString());
|
||||
} else {
|
||||
mDataset.add(newModel);
|
||||
}
|
||||
}
|
||||
notifyItemRangeInserted(startIndex, mDataset.size() - startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ViewModel model) {
|
||||
int position = mDataset.indexOf(model);
|
||||
mDataset.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public ArrayList<Uri> getAsArrayList() {
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
for (ViewModel model : mDataset) {
|
||||
uris.add(model.inputUri);
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 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
|
||||
@@ -44,7 +44,17 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptModeAsymmetricFragment extends Fragment {
|
||||
|
||||
public interface IAsymmetric {
|
||||
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId);
|
||||
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds);
|
||||
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds);
|
||||
}
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
// view
|
||||
@@ -52,37 +62,43 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
private EncryptKeyCompletionView mEncryptKeyView;
|
||||
|
||||
// model
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
private IAsymmetric mEncryptInterface;
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
if (mSign != null) {
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
}
|
||||
// @Override
|
||||
// public void updateUi() {
|
||||
// if (mSign != null) {
|
||||
// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
// }
|
||||
// }
|
||||
|
||||
public static final String ARG_SINGATURE_KEY_ID = "signature_key_id";
|
||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) {
|
||||
EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_SINGATURE_KEY_ID, signatureKey);
|
||||
args.putLongArray(ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mEncryptInterface = (IAsymmetric) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity + " must implement IAsymmetric");
|
||||
}
|
||||
}
|
||||
|
||||
private void setSignatureKeyId(long signatureKeyId) {
|
||||
mEncryptInterface.setSignatureKey(signatureKeyId);
|
||||
}
|
||||
|
||||
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptInterface.setEncryptionKeys(encryptionKeyIds);
|
||||
}
|
||||
|
||||
private void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptInterface.setEncryptionUsers(encryptionUserIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@@ -94,7 +110,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
|
||||
@Override
|
||||
public void onKeyChanged(long masterKeyId) {
|
||||
setSignatureKeyId(masterKeyId);
|
||||
mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
|
||||
}
|
||||
});
|
||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||
@@ -109,7 +125,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
mProviderHelper = new ProviderHelper(getActivity());
|
||||
|
||||
// preselect keys given
|
||||
preselectKeys();
|
||||
long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
|
||||
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
|
||||
preselectKeys(signatureKeyId, encryptionKeyIds);
|
||||
|
||||
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
|
||||
@Override
|
||||
@@ -131,24 +149,20 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
/**
|
||||
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
|
||||
*/
|
||||
private void preselectKeys() {
|
||||
// TODO all of this works under the assumption that the first suitable subkey is always used!
|
||||
// not sure if we need to distinguish between different subkeys here?
|
||||
long signatureKey = mEncryptInterface.getSignatureKey();
|
||||
if (signatureKey != Constants.key.none) {
|
||||
private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
|
||||
if (signatureKeyId != Constants.key.none) {
|
||||
try {
|
||||
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(signatureKey));
|
||||
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
|
||||
if (keyring.hasAnySecret()) {
|
||||
setSignatureKeyId(keyring.getMasterKeyId());
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
|
||||
mSign.setSelectedKeyId(signatureKeyId);
|
||||
}
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys();
|
||||
if (encryptionKeyIds != null) {
|
||||
for (long preselectedId : encryptionKeyIds) {
|
||||
try {
|
||||
@@ -181,7 +195,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
for (int i = 0; i < keyIds.size(); i++) {
|
||||
keyIdsArr[i] = iterator.next();
|
||||
}
|
||||
setEncryptionKeyIds(keyIdsArr);
|
||||
setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
|
||||
mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
|
||||
mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()]));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 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
|
||||
@@ -30,20 +30,37 @@ import android.widget.EditText;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptModeSymmetricFragment extends Fragment {
|
||||
|
||||
EncryptActivityInterface mEncryptInterface;
|
||||
public interface ISymmetric {
|
||||
|
||||
public void onPassphraseChanged(Passphrase passphrase);
|
||||
}
|
||||
|
||||
private ISymmetric mEncryptInterface;
|
||||
|
||||
private EditText mPassphrase;
|
||||
private EditText mPassphraseAgain;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptModeSymmetricFragment newInstance() {
|
||||
EncryptModeSymmetricFragment frag = new EncryptModeSymmetricFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mEncryptInterface = (ISymmetric) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity.toString() + " must implement ISymmetric");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +91,9 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
|
||||
p1.removeFromMemory();
|
||||
p2.removeFromMemory();
|
||||
if (passesEquals) {
|
||||
mEncryptInterface.setPassphrase(new Passphrase(mPassphrase.getText()));
|
||||
mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
|
||||
} else {
|
||||
mEncryptInterface.setPassphrase(null);
|
||||
mEncryptInterface.onPassphraseChanged(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -86,8 +103,4 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,31 +19,21 @@
|
||||
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.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface {
|
||||
public class EncryptTextActivity extends BaseActivity implements
|
||||
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
|
||||
EncryptTextFragment.IMode {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT;
|
||||
@@ -55,285 +45,22 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
|
||||
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
|
||||
|
||||
// view
|
||||
private int mCurrentMode = MODE_ASYMMETRIC;
|
||||
|
||||
// tabs
|
||||
private static final int MODE_ASYMMETRIC = 0;
|
||||
private static final int MODE_SYMMETRIC = 1;
|
||||
|
||||
// model used by fragments
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
// TODO Constants.key.none? What's wrong with a null value?
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mInputUris;
|
||||
private ArrayList<Uri> mOutputUris;
|
||||
private String mMessage = "";
|
||||
|
||||
public boolean isModeSymmetric() {
|
||||
return MODE_SYMMETRIC == mCurrentMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseArmor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptFilenames() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseCompression() {
|
||||
return mUseCompression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSignatureKey() {
|
||||
return mSigningKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getEncryptionKeys() {
|
||||
return mEncryptionKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEncryptionUsers() {
|
||||
return mEncryptionUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignatureKey(long signatureKey) {
|
||||
mSigningKeyId = signatureKey;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionKeys(long[] encryptionKeys) {
|
||||
mEncryptionKeyIds = encryptionKeys;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionUsers(String[] encryptionUsers) {
|
||||
mEncryptionUserIds = encryptionUsers;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
if (mPassphrase != null) {
|
||||
mPassphrase.removeFromMemory();
|
||||
}
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputUris(ArrayList<Uri> uris) {
|
||||
mInputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputUris(ArrayList<Uri> uris) {
|
||||
mOutputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (fragment instanceof UpdateListener) {
|
||||
((UpdateListener) fragment).onNotifyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
startEncrypt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(result.getResultBytes());
|
||||
result.createNotify(EncryptTextActivity.this).show();
|
||||
// Notify.create(EncryptTextActivity.this,
|
||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
||||
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.setBytes(mMessage.getBytes());
|
||||
data.setCleartextSignature(true);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
// Always use armor for messages
|
||||
data.setEnableAsciiArmorOutput(true);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(byte[] resultBytes) {
|
||||
ClipboardReflection.copyToClipboard(this, new String(resultBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
|
||||
Intent prototype = createSendIntent(resultBytes);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
// pass trough email addresses as extra for email applications
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
if (mMessage == null) {
|
||||
Notify.create(this, R.string.error_message, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mSigningKeyId == 0) {
|
||||
Notify.create(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Fragment mModeFragment;
|
||||
EncryptTextFragment mEncryptFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// if called with an intent action, do not init drawer navigation
|
||||
if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) {
|
||||
// lock drawer
|
||||
// deactivateDrawerNavigation();
|
||||
// TODO: back button to key?
|
||||
} else {
|
||||
// activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
updateModeFragment();
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -341,58 +68,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
setContentView(R.layout.encrypt_text_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_text_activity, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void updateModeFragment() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_pager_mode,
|
||||
mCurrentMode == MODE_SYMMETRIC
|
||||
? new EncryptSymmetricFragment()
|
||||
: new EncryptAsymmetricFragment()
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
|
||||
updateModeFragment();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
private void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
@@ -423,19 +105,61 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
}
|
||||
|
||||
String textData = extras.getString(EXTRA_TEXT);
|
||||
if (ACTION_ENCRYPT_TEXT.equals(action) && textData == null) {
|
||||
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
|
||||
return;
|
||||
}
|
||||
|
||||
// preselect keys given by intent
|
||||
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
|
||||
/**
|
||||
* Main Actions
|
||||
*/
|
||||
if (ACTION_ENCRYPT_TEXT.equals(action) && textData != null) {
|
||||
mMessage = textData;
|
||||
} else if (ACTION_ENCRYPT_TEXT.equals(action)) {
|
||||
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
|
||||
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
|
||||
|
||||
mEncryptFragment = EncryptTextFragment.newInstance(textData);
|
||||
transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeChanged(boolean symmetric) {
|
||||
// switch fragments
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_mode_container,
|
||||
symmetric
|
||||
? EncryptModeSymmetricFragment.newInstance()
|
||||
: EncryptModeAsymmetricFragment.newInstance(0, null)
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId) {
|
||||
mEncryptFragment.setSigningKeyId(signatureKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
|
||||
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
|
||||
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseChanged(Passphrase passphrase) {
|
||||
mEncryptFragment.setSymmetricPassphrase(passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 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
|
||||
@@ -18,32 +18,102 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptTextFragment extends CryptoOperationFragment {
|
||||
|
||||
public interface IMode {
|
||||
public void onModeChanged(boolean symmetric);
|
||||
}
|
||||
|
||||
public class EncryptTextFragment extends Fragment {
|
||||
public static final String ARG_TEXT = "text";
|
||||
|
||||
private IMode mModeInterface;
|
||||
|
||||
private boolean mSymmetricMode = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
// TODO Constants.key.none? What's wrong with a null value?
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mSymmetricPassphrase = new Passphrase();
|
||||
private String mMessage = "";
|
||||
|
||||
private TextView mText;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
}
|
||||
|
||||
public void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptionUserIds = encryptionUserIds;
|
||||
}
|
||||
|
||||
public void setSigningKeyId(long signingKeyId) {
|
||||
mSigningKeyId = signingKeyId;
|
||||
}
|
||||
|
||||
public void setSymmetricPassphrase(Passphrase passphrase) {
|
||||
mSymmetricPassphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptTextFragment newInstance(String text) {
|
||||
EncryptTextFragment frag = new EncryptTextFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_TEXT, text);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mModeInterface = (IMode) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity.toString() + " must implement IMode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,39 +138,58 @@ public class EncryptTextFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
mEncryptInterface.setMessage(s.toString());
|
||||
mMessage = s.toString();
|
||||
}
|
||||
});
|
||||
|
||||
// set initial text
|
||||
if (mMessage != null) {
|
||||
mText.setText(mMessage);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mMessage = getArguments().getString(ARG_TEXT);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String text = mEncryptInterface.getMessage();
|
||||
if (text != null) {
|
||||
mText.setText(text);
|
||||
}
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.encrypt_text_fragment, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mSymmetricMode = item.isChecked();
|
||||
mModeInterface.onModeChanged(mSymmetricMode);
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
case R.id.encrypt_copy: {
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
startEncrypt(false);
|
||||
break;
|
||||
}
|
||||
case R.id.encrypt_share: {
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
startEncrypt(true);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -109,4 +198,189 @@ public class EncryptTextFragment extends Fragment {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(result.getResultBytes());
|
||||
result.createNotify(getActivity()).show();
|
||||
// Notify.create(EncryptTextActivity.this,
|
||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
||||
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
}
|
||||
}
|
||||
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.setBytes(mMessage.getBytes());
|
||||
data.setCleartextSignature(true);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
// Always use armor for messages
|
||||
data.setEnableAsciiArmorOutput(true);
|
||||
|
||||
if (mSymmetricMode) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mSymmetricPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(byte[] resultBytes) {
|
||||
ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
|
||||
Intent prototype = createSendIntent(resultBytes);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
|
||||
|
||||
if (!mSymmetricMode && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
// pass trough email addresses as extra for email applications
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
if (mMessage == null) {
|
||||
Notify.create(getActivity(), R.string.error_message, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSymmetricMode) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mSymmetricPassphrase == null) {
|
||||
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
if (mSymmetricPassphrase.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mSigningKeyId == 0) {
|
||||
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
final SignEncryptParcel input = createEncryptBundle();
|
||||
final Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import com.astuetz.PagerSlidingTabStrip;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
public class HelpActivity extends BaseActivity {
|
||||
public static final String EXTRA_SELECTED_TAB = "selected_tab";
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.service.CloudImportService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
@@ -47,7 +48,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ImportKeysActivity extends BaseActivity {
|
||||
public class ImportKeysActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
|
||||
|
||||
@@ -53,9 +53,11 @@ import java.util.List;
|
||||
|
||||
public class ImportKeysListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_BYTES = "bytes";
|
||||
private static final String ARG_SERVER_QUERY = "query";
|
||||
public static final String ARG_SERVER_QUERY = "query";
|
||||
public static final String ARG_NON_INTERACTIVE = "non_interactive";
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysAdapter mAdapter;
|
||||
@@ -66,6 +68,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
private static final int LOADER_ID_CLOUD = 1;
|
||||
|
||||
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
|
||||
private boolean mNonInteractive;
|
||||
|
||||
public LoaderState getLoaderState() {
|
||||
return mLoaderState;
|
||||
@@ -118,16 +121,19 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) {
|
||||
return newInstance(bytes, dataUri, serverQuery, false);
|
||||
}
|
||||
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri,
|
||||
String serverQuery, boolean nonInteractive) {
|
||||
ImportKeysListFragment frag = new ImportKeysListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_BYTES, bytes);
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putString(ARG_SERVER_QUERY, serverQuery);
|
||||
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -173,9 +179,11 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
mAdapter = new ImportKeysAdapter(mActivity);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
byte[] bytes = getArguments().getByteArray(ARG_BYTES);
|
||||
String query = getArguments().getString(ARG_SERVER_QUERY);
|
||||
Bundle args = getArguments();
|
||||
Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null;
|
||||
byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null;
|
||||
String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null;
|
||||
mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false;
|
||||
|
||||
if (dataUri != null || bytes != null) {
|
||||
mLoaderState = new BytesLoaderState(bytes, dataUri);
|
||||
@@ -203,6 +211,10 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
if (mNonInteractive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select checkbox!
|
||||
// Update underlying data and notify adapter of change. The adapter will
|
||||
// update the view automatically.
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
@@ -83,25 +84,22 @@ public class ImportKeysProxyActivity extends FragmentActivity {
|
||||
|
||||
returnResult = false;
|
||||
processScannedContent(dataUri);
|
||||
} else if (ACTION_SCAN_IMPORT.equals(action)) {
|
||||
} else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
|
||||
returnResult = false;
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
.setResultDisplayDuration(0);
|
||||
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
integrator.initiateScan();
|
||||
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
|
||||
returnResult = true;
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
} else if (ACTION_QR_CODE_API.equals(action)) {
|
||||
// scan using xzing's Barcode Scanner from outside OpenKeychain
|
||||
|
||||
returnResult = false;
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
.setResultDisplayDuration(0);
|
||||
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
integrator.initiateScan();
|
||||
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
||||
// Check to see if the Activity started due to an Android Beam
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
public class LogDisplayActivity extends BaseActivity {
|
||||
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
*
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcIntentActivity extends BaseActivity {
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
private Intent mServiceIntent;
|
||||
|
||||
private static final int TIMEOUT = 100000;
|
||||
|
||||
private NfcAdapter mNfcAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(Constants.TAG, action);
|
||||
Log.d(Constants.TAG, intent.getDataString());
|
||||
|
||||
// TODO check fingerprint
|
||||
// mFingerprint = data.getByteArray(EXTRA_FINGERPRINT);
|
||||
|
||||
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
finish();
|
||||
}
|
||||
|
||||
try {
|
||||
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
|
||||
// Connect to the detected tag, setting a couple of settings
|
||||
IsoDep isoDep = IsoDep.get(detectedTag);
|
||||
isoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
|
||||
isoDep.connect();
|
||||
|
||||
nfcGreet(isoDep);
|
||||
// nfcPin(isoDep, "yoloswag");
|
||||
nfcGetFingerprint(isoDep);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException!", e);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity,
|
||||
* disables NFC Foreground Dispatch
|
||||
*/
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
Log.d(Constants.TAG, "NfcActivity.onPause");
|
||||
|
||||
// disableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user,
|
||||
* enables NFC Foreground Dispatch
|
||||
*/
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(Constants.TAG, "NfcActivity.onResume");
|
||||
|
||||
// enableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/** Handle NFC communication and return a result.
|
||||
*
|
||||
* This method is called by onNewIntent above upon discovery of an NFC tag.
|
||||
* It handles initialization and login to the application, subsequently
|
||||
* calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
|
||||
* finishes the activity with an appropiate result.
|
||||
*
|
||||
* On general communication, see also
|
||||
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
|
||||
*
|
||||
* References to pages are generally related to the OpenPGP Application
|
||||
* on ISO SmartCard Systems specification.
|
||||
*
|
||||
*/
|
||||
private void nfcGreet(IsoDep isoDep) throws IOException {
|
||||
|
||||
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||
// See specification, page 51
|
||||
String accepted = "9000";
|
||||
|
||||
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||
String opening =
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if (!card(isoDep, opening).equals(accepted)) { // activate connection
|
||||
toast("Opening Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void nfcPin(IsoDep isoDep, String pin) throws IOException {
|
||||
|
||||
String data = "00CA006E00";
|
||||
String fingerprint = card(isoDep, data);
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ "82" // P2 (PW1)
|
||||
+ String.format("%02x", pin.length()) // Lc
|
||||
+ Hex.toHexString(pin.getBytes());
|
||||
if ( ! card(isoDep, login).equals("9000")) { // login
|
||||
toast("Pin Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID
|
||||
*
|
||||
* @return the user id as "name <email>"
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static String getUserId(IsoDep isoDep) throws IOException {
|
||||
String info = "00CA006500";
|
||||
String data = "00CA005E00";
|
||||
return getName(card(isoDep, info)) + " <" + (new String(Hex.decode(getDataField(card(isoDep, data))))) + ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the long key ID
|
||||
*
|
||||
* @return the long key id (last 16 bytes of the fingerprint)
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static long getKeyId(IsoDep isoDep) throws IOException {
|
||||
String keyId = nfcGetFingerprint(isoDep).substring(24);
|
||||
Log.d(Constants.TAG, "keyId: " + keyId);
|
||||
return Long.parseLong(keyId, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fingerprint of the signature key
|
||||
*
|
||||
* @return the fingerprint
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static String nfcGetFingerprint(IsoDep isoDep) throws IOException {
|
||||
String data = "00CA006E00";
|
||||
byte[] buf = isoDep.transceive(Hex.decode(data));
|
||||
|
||||
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
|
||||
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
|
||||
|
||||
Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
|
||||
if (fptlv != null) {
|
||||
ByteBuffer fpbuf = ByteBuffer.wrap(fptlv.mV);
|
||||
byte[] fp = new byte[20];
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 1: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 2: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 3: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
}
|
||||
|
||||
return "nope";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the screen
|
||||
*
|
||||
* @param text the text which should be contained within the toast
|
||||
*/
|
||||
private void toast(String text) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive new NFC Intents to this activity only by enabling foreground dispatch.
|
||||
* This can only be done in onResume!
|
||||
*/
|
||||
public void enableNfcForegroundDispatch() {
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
Intent nfcI = new Intent(this, NfcIntentActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, 0);
|
||||
IntentFilter[] writeTagFilters = new IntentFilter[]{
|
||||
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
|
||||
};
|
||||
|
||||
// https://code.google.com/p/android/issues/detail?id=62918
|
||||
// maybe mNfcAdapter.enableReaderMode(); ?
|
||||
try {
|
||||
mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e);
|
||||
}
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable foreground dispatch in onPause!
|
||||
*/
|
||||
public void disableNfcForegroundDispatch() {
|
||||
mNfcAdapter.disableForegroundDispatch(this);
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the user out of the raw card output regarding card holder related data
|
||||
*
|
||||
* @param name the raw card holder related data from the card
|
||||
* @return the name given in this data
|
||||
*/
|
||||
public static String getName(String name) {
|
||||
String slength;
|
||||
int ilength;
|
||||
name = name.substring(6);
|
||||
slength = name.substring(0, 2);
|
||||
ilength = Integer.parseInt(slength, 16) * 2;
|
||||
name = name.substring(2, ilength + 2);
|
||||
name = (new String(Hex.decode(name))).replace('<', ' ');
|
||||
return (name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the raw data from the card by four characters
|
||||
*
|
||||
* @param output the raw data from the card
|
||||
* @return the data field of that data
|
||||
*/
|
||||
private static String getDataField(String output) {
|
||||
return output.substring(0, output.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicates with the OpenPgpCard via the APDU
|
||||
*
|
||||
* @param hex the hexadecimal APDU
|
||||
* @return The answer from the card
|
||||
* @throws java.io.IOException throws an exception if something goes wrong
|
||||
*/
|
||||
public static String card(IsoDep isoDep, String hex) throws IOException {
|
||||
return getHex(isoDep.transceive(Hex.decode(hex)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array into an hex string
|
||||
*
|
||||
* @param raw the byte array representation
|
||||
* @return the hexadecimal string representation
|
||||
*/
|
||||
public static String getHex(byte[] raw) {
|
||||
return new String(Hex.encode(raw));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
* <p/>
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
public class NfcOperationActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||
|
||||
// passthrough for OpenPgpService
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
public static final String RESULT_DATA = "result_data";
|
||||
|
||||
private RequiredInputParcel mRequiredInput;
|
||||
private Intent mServiceIntent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
|
||||
mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT);
|
||||
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
|
||||
|
||||
// obtain passphrase for this subkey
|
||||
obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime);
|
||||
|
||||
switch (mRequiredInput.mType) {
|
||||
case NFC_DECRYPT: {
|
||||
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
|
||||
byte[] hash = mRequiredInput.mInputHashes[i];
|
||||
byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
|
||||
inputParcel.addCryptoData(hash, decryptedSessionKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NFC_SIGN: {
|
||||
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
|
||||
byte[] hash = mRequiredInput.mInputHashes[i];
|
||||
int algo = mRequiredInput.mSignAlgos[i];
|
||||
byte[] signedHash = nfcCalculateSignature(hash, algo);
|
||||
inputParcel.addCryptoData(hash, signedHash);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mServiceIntent != null) {
|
||||
CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
} else {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel);
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePinError() {
|
||||
|
||||
// avoid a loop
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.useDefaultYubiKeyPin()) {
|
||||
toast(getString(R.string.error_pin_nodefault));
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// clear (invalid) passphrase
|
||||
PassphraseCacheService.clearCachedPassphrase(
|
||||
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
||||
|
||||
obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,10 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@@ -63,13 +66,14 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
* This activity encapsulates a DialogFragment to emulate a dialog.
|
||||
*/
|
||||
public class PassphraseDialogActivity extends FragmentActivity {
|
||||
public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
|
||||
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
||||
|
||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||
public static final String EXTRA_SUBKEY_ID = "secret_key_id";
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
|
||||
|
||||
@@ -88,9 +92,27 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// this activity itself has no content view (see manifest)
|
||||
|
||||
long keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
long keyId;
|
||||
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
||||
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
} else {
|
||||
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
||||
switch (requiredInput.mType) {
|
||||
case PASSPHRASE_SYMMETRIC: {
|
||||
keyId = Constants.key.symmetric;
|
||||
break;
|
||||
}
|
||||
case PASSPHRASE: {
|
||||
keyId = requiredInput.getSubKeyId();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Unsupported required input type for PassphraseDialogActivity!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_DATA);
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||
|
||||
show(this, keyId, serviceIntent);
|
||||
}
|
||||
@@ -141,7 +163,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(EXTRA_SUBKEY_ID, keyId);
|
||||
args.putParcelable(EXTRA_DATA, serviceIntent);
|
||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -174,11 +196,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
R.style.Theme_AppCompat_Light_Dialog);
|
||||
|
||||
mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID);
|
||||
mServiceIntent = getArguments().getParcelable(EXTRA_DATA);
|
||||
mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT);
|
||||
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
|
||||
|
||||
alert.setTitle(R.string.title_unlock);
|
||||
// No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
|
||||
//alert.setTitle()
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(theme);
|
||||
View view = inflater.inflate(R.layout.passphrase_dialog, null);
|
||||
@@ -243,8 +266,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
case PASSPHRASE_EMPTY:
|
||||
finishCaching(new Passphrase(""));
|
||||
default:
|
||||
message = "This should not happen!";
|
||||
break;
|
||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
@@ -296,7 +318,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubikeyPin()) {
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
} else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
@@ -309,7 +331,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
AlertDialog dialog = alert.create();
|
||||
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
activity.getString(android.R.string.ok), (DialogInterface.OnClickListener) null);
|
||||
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@@ -406,17 +428,14 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase);
|
||||
if (mServiceIntent != null) {
|
||||
// TODO: Not routing passphrase through OpenPGP API currently
|
||||
// due to security concerns...
|
||||
// BUT this means you need to _cache_ passphrases!
|
||||
CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel);
|
||||
getActivity().setResult(RESULT_OK, mServiceIntent);
|
||||
} else {
|
||||
// also return passphrase back to activity
|
||||
Intent returnIntent = new Intent();
|
||||
returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
returnIntent.putExtra(EXTRA_KEY_ID, mSecretRing.getMasterKeyId());
|
||||
returnIntent.putExtra(EXTRA_SUBKEY_ID, mSubKeyId);
|
||||
returnIntent.putExtra(RESULT_CRYPTO_INPUT, inputParcel);
|
||||
getActivity().setResult(RESULT_OK, returnIntent);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ 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.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
@@ -107,10 +107,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
|
||||
initializeUseDefaultYubikeyPin(
|
||||
initializeUseDefaultYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
|
||||
|
||||
initializeUseNumKeypadForYubikeyPin(
|
||||
initializeUseNumKeypadForYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||
|
||||
}
|
||||
@@ -262,10 +262,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
|
||||
initializeUseDefaultYubikeyPin(
|
||||
initializeUseDefaultYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
|
||||
|
||||
initializeUseNumKeypadForYubikeyPin(
|
||||
initializeUseNumKeypadForYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||
}
|
||||
}
|
||||
@@ -335,23 +335,23 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
|
||||
}
|
||||
|
||||
private static void initializeUseDefaultYubikeyPin(final CheckBoxPreference mUseDefaultYubikeyPin) {
|
||||
mUseDefaultYubikeyPin.setChecked(sPreferences.useDefaultYubikeyPin());
|
||||
mUseDefaultYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {
|
||||
mUseDefaultYubiKeyPin.setChecked(sPreferences.useDefaultYubiKeyPin());
|
||||
mUseDefaultYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mUseDefaultYubikeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseDefaultYubikeyPin((Boolean) newValue);
|
||||
mUseDefaultYubiKeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseDefaultYubiKeyPin((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeUseNumKeypadForYubikeyPin(final CheckBoxPreference mUseNumKeypadForYubikeyPin) {
|
||||
mUseNumKeypadForYubikeyPin.setChecked(sPreferences.useNumKeypadForYubikeyPin());
|
||||
mUseNumKeypadForYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) {
|
||||
mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin());
|
||||
mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mUseNumKeypadForYubikeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseNumKeypadForYubikeyPin((Boolean) newValue);
|
||||
mUseNumKeypadForYubiKeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseNumKeypadForYubiKeyPin((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -61,18 +61,23 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
@@ -80,15 +85,21 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.NfcHelper;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ViewKeyActivity extends BaseActivity implements
|
||||
public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
||||
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
|
||||
|
||||
static final int REQUEST_QR_FINGERPRINT = 1;
|
||||
static final int REQUEST_DELETE = 2;
|
||||
static final int REQUEST_EXPORT = 3;
|
||||
public static final String EXTRA_DISPLAY_RESULT = "display_result";
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
ProviderHelper mProviderHelper;
|
||||
@@ -108,6 +119,8 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
private ImageView mQrCode;
|
||||
private CardView mQrCodeLayout;
|
||||
|
||||
private String mQrCodeLoaded;
|
||||
|
||||
// NFC
|
||||
private NfcHelper mNfcHelper;
|
||||
|
||||
@@ -257,6 +270,21 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mNfcHelper = new NfcHelper(this, mProviderHelper);
|
||||
mNfcHelper.initNfc(mDataUri);
|
||||
|
||||
if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) {
|
||||
OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT);
|
||||
result.createNotify(this).show();
|
||||
}
|
||||
|
||||
startFragment(savedInstanceState, mDataUri);
|
||||
|
||||
if (savedInstanceState == null && getIntent().hasExtra(EXTRA_NFC_AID)) {
|
||||
Intent intent = getIntent();
|
||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
||||
showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -264,6 +292,35 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
setContentView(R.layout.view_key_activity);
|
||||
}
|
||||
|
||||
private void startFragment(Bundle savedInstanceState, final Uri dataUri) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
if (manager.getBackStackEntryCount() == 0) {
|
||||
// Create an instance of the fragment
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
manager.popBackStack();
|
||||
} else {
|
||||
// not sure yet if we actually want this!
|
||||
// manager.popBackStack();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@@ -507,6 +564,72 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
final byte[] nfcFingerprints = nfcGetFingerprints();
|
||||
final String nfcUserId = nfcGetUserId();
|
||||
final byte[] nfcAid = nfcGetAid();
|
||||
|
||||
String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints);
|
||||
final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
|
||||
|
||||
if (!mFingerprint.equals(fp)) {
|
||||
try {
|
||||
CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_view).show();
|
||||
return;
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_import).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
|
||||
|
||||
}
|
||||
|
||||
public void showYubiKeyFragment(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) {
|
||||
ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance(
|
||||
nfcFingerprints, nfcUserId, nfcAid);
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
|
||||
manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
manager.beginTransaction()
|
||||
.addToBackStack("yubikey")
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void encrypt(Uri dataUri, boolean text) {
|
||||
// If there is no encryption key, don't bother.
|
||||
if (!mHasEncrypt) {
|
||||
@@ -638,6 +761,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap qrCode) {
|
||||
mQrCodeLoaded = fingerprint;
|
||||
// scale the image up to our actual size. we do this in code rather
|
||||
// than let the ImageView do this because we don't require filtering.
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||
@@ -705,25 +829,13 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
|
||||
case LOADER_ID_UNIFIED: {
|
||||
// if there is no data, just break
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
String oldFingerprint = mFingerprint;
|
||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
byte[] fpData = data.getBlob(INDEX_FINGERPRINT);
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(fpData);
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
startFragment(mIsSecret, fpData);
|
||||
|
||||
// get name, email, and comment from USER_ID
|
||||
KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
|
||||
if (mainUserId.name != null) {
|
||||
@@ -732,6 +844,15 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mName.setText(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
// if the refresh animation isn't playing
|
||||
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
|
||||
// re-create options menu based on mIsSecret, mIsVerified
|
||||
@@ -768,7 +889,6 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
} else if (mIsExpired) {
|
||||
if (mIsSecret) {
|
||||
mStatusText.setText(R.string.view_key_expired_secret);
|
||||
mName.setText(mainUserId.name);
|
||||
} else {
|
||||
mStatusText.setText(R.string.view_key_expired);
|
||||
}
|
||||
@@ -787,7 +907,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mStatusImage.setVisibility(View.GONE);
|
||||
color = getResources().getColor(R.color.primary);
|
||||
// reload qr code only if the fingerprint changed
|
||||
if (!mFingerprint.equals(oldFingerprint)) {
|
||||
if (!mFingerprint.equals(mQrCodeLoaded)) {
|
||||
loadQrCode(mFingerprint);
|
||||
}
|
||||
photoTask.execute(mMasterKeyId);
|
||||
@@ -873,6 +993,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mStatusImage.setAlpha(80);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -882,27 +1003,4 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
|
||||
}
|
||||
|
||||
private void startFragment(final boolean isSecret, final byte[] fingerprint) {
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
if (manager.getBackStackEntryCount() == 0) {
|
||||
// Create an instance of the fragment
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(
|
||||
mDataUri, isSecret, fingerprint);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
manager.popBackStack();
|
||||
} else {
|
||||
// not sure yet if we actually want this!
|
||||
// manager.popBackStack();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.transition.Fade;
|
||||
@@ -48,11 +49,11 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -62,21 +63,26 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
private static final String ARG_IS_SECRET = "is_secret";
|
||||
|
||||
private ListView mUserIds;
|
||||
//private ListView mLinkedSystemContact;
|
||||
|
||||
boolean mIsSecret = false;
|
||||
boolean mSystemContactLoaded = false;
|
||||
|
||||
CardView mSystemContactCard;
|
||||
LinearLayout mSystemContactLayout;
|
||||
ImageView mSystemContactPicture;
|
||||
TextView mSystemContactName;
|
||||
|
||||
private static final int LOADER_ID_USER_IDS = 0;
|
||||
private static final int LOADER_ID_LINKED_IDS = 1;
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_LINKED_CONTACT = 2;
|
||||
private static final int LOADER_ID_LINKED_IDS = 3;
|
||||
|
||||
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
||||
= "loader_linked_contact_master_key_id";
|
||||
private static final String LOADER_EXTRA_LINKED_CONTACT_IS_SECRET
|
||||
= "loader_linked_contact_is_secret";
|
||||
|
||||
private UserIdsAdapter mUserIdsAdapter;
|
||||
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||
@@ -90,35 +96,16 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ViewKeyFragment newInstance(Uri dataUri, boolean isSecret, byte[] fingerprint) {
|
||||
public static ViewKeyFragment newInstance(Uri dataUri) {
|
||||
ViewKeyFragment frag = new ViewKeyFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprint);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
Uri dataUri = args.getParcelable(ARG_DATA_URI);
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
boolean isSecret = args.getBoolean(ARG_IS_SECRET);
|
||||
byte[] fingerprint = args.getByteArray(ARG_FINGERPRINT);
|
||||
|
||||
loadData(dataUri, isSecret, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
@@ -144,6 +131,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
});
|
||||
|
||||
mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card);
|
||||
mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout);
|
||||
mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name);
|
||||
mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture);
|
||||
@@ -209,60 +197,64 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a system contact exists for given masterKeyId, and if it does, sets name, picture
|
||||
* and onClickListener for the linked system contact's layout
|
||||
* In the case of a secret key, "me" contact details are loaded
|
||||
*
|
||||
* @param masterKeyId
|
||||
* Hides card if no linked system contact exists. Sets name, picture
|
||||
* and onClickListener for the linked system contact's layout.
|
||||
* In the case of a secret key, "me" (own profile) contact details are loaded.
|
||||
*/
|
||||
private void loadLinkedSystemContact(final long masterKeyId) {
|
||||
private void loadLinkedSystemContact(final long contactId) {
|
||||
|
||||
final Context context = mSystemContactName.getContext();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
long contactId;
|
||||
String contactName = null;
|
||||
|
||||
if (mIsSecret) {//all secret keys are linked to "me" profile in contacts
|
||||
contactId = ContactHelper.getMainProfileContactId(resolver);
|
||||
List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context);
|
||||
if (mainProfileNames != null && mainProfileNames.size() > 0) {
|
||||
contactName = mainProfileNames.get(0);
|
||||
}
|
||||
|
||||
} else {
|
||||
contactId = ContactHelper.findContactId(resolver, masterKeyId);
|
||||
contactName = ContactHelper.getContactName(resolver, contactId);
|
||||
}
|
||||
|
||||
if (contactName != null) {//contact name exists for given master key
|
||||
showLinkedSystemContact();
|
||||
|
||||
mSystemContactName.setText(contactName);
|
||||
|
||||
Bitmap picture;
|
||||
if (mIsSecret) {
|
||||
picture = ContactHelper.loadMainProfilePhoto(resolver, false);
|
||||
} else {
|
||||
picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false);
|
||||
picture = ContactHelper.loadPhotoByContactId(resolver, contactId, false);
|
||||
}
|
||||
if (picture != null) mSystemContactPicture.setImageBitmap(picture);
|
||||
|
||||
final long finalContactId = contactId;
|
||||
mSystemContactLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
launchContactActivity(finalContactId, context);
|
||||
launchContactActivity(contactId, context);
|
||||
}
|
||||
});
|
||||
mSystemContactLoaded = true;
|
||||
} else {
|
||||
hideLinkedSystemContact();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideLinkedSystemContact() {
|
||||
mSystemContactCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showLinkedSystemContact() {
|
||||
mSystemContactCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* launches the default android Contacts app to view a contact with the passed
|
||||
* contactId (CONTACT_ID column from ContactsContract.RawContact table which is _ID column in
|
||||
* ContactsContract.Contact table)
|
||||
*
|
||||
* @param contactId _ID for row in ContactsContract.Contacts table
|
||||
* @param context
|
||||
*/
|
||||
private void launchContactActivity(final long contactId, Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
@@ -271,23 +263,61 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri, boolean isSecret, byte[] fingerprint) {
|
||||
@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);
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] UNIFIED_PROJECTION = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
KeychainContract.KeyRings.VERIFIED,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
KeychainContract.KeyRings.FINGERPRINT,
|
||||
KeychainContract.KeyRings.HAS_ENCRYPT
|
||||
};
|
||||
|
||||
static final int INDEX_MASTER_KEY_ID = 1;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_USER_ID = 2;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_REVOKED = 3;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_EXPIRED = 4;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_VERIFIED = 5;
|
||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
static final int INDEX_FINGERPRINT = 7;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_HAS_ENCRYPT = 8;
|
||||
|
||||
private static final String[] RAWCONTACT_PROJECTION = {
|
||||
ContactsContract.RawContacts.CONTACT_ID
|
||||
};
|
||||
|
||||
private static final int INDEX_CONTACT_ID = 0;
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
mDataUri = dataUri;
|
||||
mIsSecret = isSecret;
|
||||
mFingerprint = fingerprint;
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri);
|
||||
|
||||
// load user ids after we know if it's a secret key
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0,
|
||||
mIsSecret, mLinkedIdsExpander);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,11 +325,45 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
setContentShown(false);
|
||||
|
||||
switch (id) {
|
||||
case LOADER_ID_USER_IDS:
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
case LOADER_ID_UNIFIED: {
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS:
|
||||
case LOADER_ID_USER_IDS: {
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
//we need a separate loader for linked contact to ensure refreshing on verification
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
//passed in args to explicitly specify their need
|
||||
long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID);
|
||||
boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET);
|
||||
|
||||
Uri baseUri;
|
||||
if (isSecret)
|
||||
baseUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI;
|
||||
else
|
||||
baseUri = ContactsContract.RawContacts.CONTENT_URI;
|
||||
|
||||
return new CursorLoader(
|
||||
getActivity(),
|
||||
baseUri,
|
||||
RAWCONTACT_PROJECTION,
|
||||
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
|
||||
ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
|
||||
ContactsContract.RawContacts.DELETED + "=?",
|
||||
new String[]{//"0" for "not deleted"
|
||||
Constants.ACCOUNT_TYPE,
|
||||
Long.toString(masterKeyId),
|
||||
"0"
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
@@ -307,21 +371,71 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
/* TODO better error handling? May cause problems when a key is deleted,
|
||||
* because the notification triggers faster than the activity closes.
|
||||
*/
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
|
||||
// load user ids after we know if it's a secret key
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
mLinkedIdsAdapter =
|
||||
new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||
|
||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
// we need to load linked contact here to prevent lag introduced by loader
|
||||
// for the linked contact
|
||||
long contactId = ContactHelper.findContactId(
|
||||
getActivity().getContentResolver(),
|
||||
masterKeyId);
|
||||
loadLinkedSystemContact(contactId);
|
||||
|
||||
Bundle linkedContactData = new Bundle();
|
||||
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
||||
linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, mIsSecret);
|
||||
|
||||
// initialises loader for contact query so we can listen to any updates
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case LOADER_ID_USER_IDS: {
|
||||
mUserIdsAdapter.swapCursor(cursor);
|
||||
loadLinkedSystemContact(KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint));
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(cursor);
|
||||
mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
if (data.moveToFirst()) {// if we have a linked contact
|
||||
long contactId = data.getLong(INDEX_CONTACT_ID);
|
||||
loadLinkedSystemContact(contactId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
public class ViewKeyYubiKeyFragment extends Fragment
|
||||
implements LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_USER_ID = "user_id";
|
||||
public static final String ARG_CARD_AID = "aid";
|
||||
private byte[][] mFingerprints;
|
||||
private String mUserId;
|
||||
private byte[] mCardAid;
|
||||
private long mMasterKeyId;
|
||||
private Button vButton;
|
||||
private TextView vStatus;
|
||||
|
||||
public static ViewKeyYubiKeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) {
|
||||
ViewKeyYubiKeyFragment frag = new ViewKeyYubiKeyFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprints);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
args.putByteArray(ARG_CARD_AID, aid);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT));
|
||||
mFingerprints = new byte[buf.remaining()/40][];
|
||||
for (int i = 0; i < mFingerprints.length; i++) {
|
||||
mFingerprints[i] = new byte[20];
|
||||
buf.get(mFingerprints[i]);
|
||||
}
|
||||
mUserId = args.getString(ARG_USER_ID);
|
||||
mCardAid = args.getByteArray(ARG_CARD_AID);
|
||||
|
||||
mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[0]);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.view_key_yubikey, null);
|
||||
|
||||
TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
|
||||
TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
|
||||
|
||||
String serno = Hex.toHexString(mCardAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.yubikey_serno, serno));
|
||||
|
||||
if (!mUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder, mUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder_unset));
|
||||
}
|
||||
|
||||
vButton = (Button) view.findViewById(R.id.button_bind);
|
||||
vButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
promoteToSecretKey();
|
||||
}
|
||||
});
|
||||
|
||||
vStatus = (TextView) view.findViewById(R.id.yubikey_status);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void promoteToSecretKey() {
|
||||
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
PromoteKeyResult result =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
public static final String[] PROJECTION = new String[]{
|
||||
Keys._ID,
|
||||
Keys.KEY_ID,
|
||||
Keys.RANK,
|
||||
Keys.HAS_SECRET,
|
||||
Keys.FINGERPRINT
|
||||
};
|
||||
private static final int INDEX_KEY_ID = 1;
|
||||
private static final int INDEX_RANK = 2;
|
||||
private static final int INDEX_HAS_SECRET = 3;
|
||||
private static final int INDEX_FINGERPRINT = 4;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId),
|
||||
PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (!data.moveToFirst()) {
|
||||
// wut?
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allBound = true;
|
||||
boolean noneBound = true;
|
||||
|
||||
do {
|
||||
SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET));
|
||||
byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
Integer index = naiveIndexOf(mFingerprints, fingerprint);
|
||||
if (index == null) {
|
||||
continue;
|
||||
}
|
||||
if (keyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||
noneBound = false;
|
||||
} else {
|
||||
allBound = false;
|
||||
}
|
||||
} while (data.moveToNext());
|
||||
|
||||
if (allBound) {
|
||||
vButton.setVisibility(View.GONE);
|
||||
vStatus.setText(R.string.yubikey_status_bound);
|
||||
} else {
|
||||
vButton.setVisibility(View.VISIBLE);
|
||||
vStatus.setText(noneBound
|
||||
? R.string.yubikey_status_unbound
|
||||
: R.string.yubikey_status_partly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
|
||||
for (int i = 0; i < haystack.length; i++) {
|
||||
if (Arrays.equals(needle, haystack[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,8 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mStatus.setVisibility(View.GONE);
|
||||
if (mSlingerButton.hasOnClickListeners()) {
|
||||
mSlinger.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSlinger.setVisibility(View.GONE);
|
||||
}
|
||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.adapter;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* https://gist.github.com/yrom/3b4bcbc2370ca2290434
|
||||
*/
|
||||
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
|
||||
private int space;
|
||||
private int spanCount;
|
||||
private int lastItemInFirstLane = -1;
|
||||
|
||||
public SpacesItemDecoration(int space) {
|
||||
this(space, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param space
|
||||
* @param spanCount spans count of one lane
|
||||
*/
|
||||
public SpacesItemDecoration(int space, int spanCount) {
|
||||
this.space = space;
|
||||
this.spanCount = spanCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
|
||||
final int position = params.getViewPosition();
|
||||
final int spanSize;
|
||||
final int index;
|
||||
if (params instanceof GridLayoutManager.LayoutParams) {
|
||||
GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params;
|
||||
spanSize = gridParams.getSpanSize();
|
||||
index = gridParams.getSpanIndex();
|
||||
} else {
|
||||
spanSize = 1;
|
||||
index = position % spanCount;
|
||||
}
|
||||
// invalid value
|
||||
if (spanSize < 1 || index < 0) return;
|
||||
|
||||
if (spanSize == spanCount) { // full span
|
||||
outRect.left = space;
|
||||
outRect.right = space;
|
||||
} else {
|
||||
if (index == 0) { // left one
|
||||
outRect.left = space;
|
||||
}
|
||||
// spanCount >= 1
|
||||
if (index == spanCount - 1) { // right one
|
||||
outRect.right = space;
|
||||
}
|
||||
if (outRect.left == 0) {
|
||||
outRect.left = space / 2;
|
||||
}
|
||||
if (outRect.right == 0) {
|
||||
outRect.right = space / 2;
|
||||
}
|
||||
}
|
||||
// set top to all in first lane
|
||||
if (position < spanCount && spanSize <= spanCount) {
|
||||
if (lastItemInFirstLane < 0) { // lay out at first time
|
||||
lastItemInFirstLane = position + spanSize == spanCount ? position : lastItemInFirstLane;
|
||||
outRect.top = space;
|
||||
} else if (position <= lastItemInFirstLane) { // scroll to first lane again
|
||||
outRect.top = space;
|
||||
}
|
||||
}
|
||||
outRect.bottom = space;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
@@ -63,8 +63,8 @@ public abstract class BaseActivity extends ActionBarActivity {
|
||||
* Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines
|
||||
* see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
|
||||
*/
|
||||
protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
|
||||
View.OnClickListener cancelOnClickListener) {
|
||||
public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
|
||||
View.OnClickListener cancelOnClickListener) {
|
||||
setActionBarIcon(R.drawable.ic_close_white_24dp);
|
||||
|
||||
// Inflate the custom action bar view
|
||||
@@ -1,131 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
* 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;
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
public abstract class BaseNfcActivity extends BaseActivity {
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
*
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcActivity extends BaseActivity {
|
||||
|
||||
// actions
|
||||
public static final String ACTION_SIGN_HASH = "sign_hash";
|
||||
public static final String ACTION_DECRYPT_SESSION_KEY = "decrypt_session_key";
|
||||
|
||||
// always
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PIN = "pin";
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
// sign
|
||||
public static final String EXTRA_NFC_HASH_TO_SIGN = "nfc_hash";
|
||||
public static final String EXTRA_NFC_HASH_ALGO = "nfc_hash_algo";
|
||||
|
||||
// decrypt
|
||||
public static final String EXTRA_NFC_ENC_SESSION_KEY = "encrypted_session_key";
|
||||
|
||||
private Intent mServiceIntent;
|
||||
|
||||
private static final int TIMEOUT = 100000;
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
||||
|
||||
protected Passphrase mPin;
|
||||
private NfcAdapter mNfcAdapter;
|
||||
private IsoDep mIsoDep;
|
||||
private String mAction;
|
||||
|
||||
private String mPin;
|
||||
private Long mKeyId;
|
||||
|
||||
// sign
|
||||
private byte[] mHashToSign;
|
||||
private int mHashAlgo;
|
||||
|
||||
// decrypt
|
||||
private byte[] mEncryptedSessionKey;
|
||||
private static final int TIMEOUT = 100000;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
|
||||
// if we get are passed a key id, save it for the check
|
||||
if (data.containsKey(EXTRA_KEY_ID)) {
|
||||
mKeyId = data.getLong(EXTRA_KEY_ID);
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||
throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case ACTION_SIGN_HASH:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
|
||||
mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case ACTION_DECRYPT_SESSION_KEY:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case NfcAdapter.ACTION_TAG_DISCOVERED:
|
||||
Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!");
|
||||
toast("This should not happen! Please create a new bug report that the NFC screen is restarted!");
|
||||
finish();
|
||||
break;
|
||||
default:
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
break;
|
||||
/**
|
||||
* This activity is started as a singleTop activity.
|
||||
* All new NFC Intents which are delivered to this activity are handled here
|
||||
*/
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
try {
|
||||
handleNdefDiscoveredIntent(intent);
|
||||
} catch (IOException e) {
|
||||
handleNfcError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
public void handleNfcError(IOException e) {
|
||||
|
||||
Log.e(Constants.TAG, "nfc error", e);
|
||||
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||
|
||||
}
|
||||
|
||||
public void handlePinError() {
|
||||
toast("Wrong PIN!");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +108,7 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
Log.d(Constants.TAG, "NfcActivity.onPause");
|
||||
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
|
||||
|
||||
disableNfcForegroundDispatch();
|
||||
}
|
||||
@@ -145,25 +119,45 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(Constants.TAG, "NfcActivity.onResume");
|
||||
Log.d(Constants.TAG, "BaseNfcActivity.onResume");
|
||||
|
||||
enableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* This activity is started as a singleTop activity.
|
||||
* All new NFC Intents which are delivered to this activity are handled here
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
try {
|
||||
handleNdefDiscoveredIntent(intent);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Connection error!", e);
|
||||
toast("Connection Error: " + e.getMessage());
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
|
||||
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.useDefaultYubiKeyPin()) {
|
||||
mPin = new Passphrase("123456");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
|
||||
RequiredInputParcel.createRequiredPassphrase(requiredInput));
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
|
||||
}
|
||||
|
||||
protected void setYubiKeyPin(Passphrase pin) {
|
||||
mPin = pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE:
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
mPin = input.getPassphrase();
|
||||
break;
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +175,7 @@ public class NfcActivity extends BaseActivity {
|
||||
* on ISO SmartCard Systems specification.
|
||||
*
|
||||
*/
|
||||
private void handleNdefDiscoveredIntent(Intent intent) throws IOException {
|
||||
protected void handleNdefDiscoveredIntent(Intent intent) throws IOException {
|
||||
|
||||
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
|
||||
@@ -196,103 +190,54 @@ public class NfcActivity extends BaseActivity {
|
||||
|
||||
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||
String opening =
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if ( ! card(opening).equals(accepted)) { // activate connection
|
||||
toast("Opening Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection
|
||||
throw new IOException("Initialization failed!");
|
||||
}
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ "82" // P2 (PW1)
|
||||
+ String.format("%02x", mPin.length()) // Lc
|
||||
+ Hex.toHexString(mPin.getBytes());
|
||||
if ( ! card(login).equals(accepted)) { // login
|
||||
toast("Wrong PIN!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
onNfcPerform();
|
||||
|
||||
if (ACTION_SIGN_HASH.equals(mAction)) {
|
||||
|
||||
// If we were supplied with a key id for checking, do so
|
||||
if (mKeyId != null) {
|
||||
// For signing, we check the master key
|
||||
long keyId = nfcGetKeyId(mIsoDep, 0);
|
||||
// If it's wrong, just cancel
|
||||
if (keyId != mKeyId) {
|
||||
toast("NFC Tag has wrong signing key id!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// returns signed hash
|
||||
byte[] signedHash = nfcCalculateSignature(mHashToSign, mHashAlgo);
|
||||
|
||||
if (signedHash == null) {
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity signedHash as hex: " + getHex(signedHash));
|
||||
|
||||
// give data through for new service call
|
||||
// OpenPgpApi.EXTRA_NFC_SIGNED_HASH
|
||||
mServiceIntent.putExtra("nfc_signed_hash", signedHash);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
finish();
|
||||
|
||||
} else if (ACTION_DECRYPT_SESSION_KEY.equals(mAction)) {
|
||||
|
||||
// If we were supplied with a key id for checking, do so
|
||||
if (mKeyId != null) {
|
||||
// For decryption, we check the confidentiality key
|
||||
long keyId = nfcGetKeyId(mIsoDep, 1);
|
||||
// If it's wrong, just cancel
|
||||
if (keyId != mKeyId) {
|
||||
toast("NFC Tag has wrong encryption key id!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] decryptedSessionKey = nfcDecryptSessionKey(mEncryptedSessionKey);
|
||||
|
||||
// give data through for new service call
|
||||
// OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY
|
||||
mServiceIntent.putExtra("nfc_decrypted_session_key", decryptedSessionKey);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
mIsoDep.close();
|
||||
mIsoDep = null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID
|
||||
*
|
||||
* @return the user id as "name <email>"
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public String getUserId() throws IOException {
|
||||
String info = "00CA006500";
|
||||
String data = "00CA005E00";
|
||||
return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">";
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
final byte[] nfcFingerprints = nfcGetFingerprints();
|
||||
final String nfcUserId = nfcGetUserId();
|
||||
final byte[] nfcAid = nfcGetAid();
|
||||
|
||||
final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
|
||||
|
||||
try {
|
||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(
|
||||
BaseNfcActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Intent intent = new Intent(
|
||||
BaseNfcActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Return the key id from application specific data stored on tag, or null
|
||||
@@ -301,8 +246,8 @@ public class NfcActivity extends BaseActivity {
|
||||
* @param idx Index of the key to return the fingerprint from.
|
||||
* @return The long key id of the requested key, or null if not found.
|
||||
*/
|
||||
public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException {
|
||||
byte[] fp = nfcGetFingerprint(isoDep, idx);
|
||||
public Long nfcGetKeyId(int idx) throws IOException {
|
||||
byte[] fp = nfcGetFingerprint(idx);
|
||||
if (fp == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -318,9 +263,9 @@ public class NfcActivity extends BaseActivity {
|
||||
*
|
||||
* @return The fingerprints of all subkeys in a contiguous byte array.
|
||||
*/
|
||||
public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException {
|
||||
public byte[] nfcGetFingerprints() throws IOException {
|
||||
String data = "00CA006E00";
|
||||
byte[] buf = isoDep.transceive(Hex.decode(data));
|
||||
byte[] buf = mIsoDep.transceive(Hex.decode(data));
|
||||
|
||||
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
|
||||
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
|
||||
@@ -339,26 +284,39 @@ public class NfcActivity extends BaseActivity {
|
||||
* @param idx Index of the key to return the fingerprint from.
|
||||
* @return The fingerprint of the requested key, or null if not found.
|
||||
*/
|
||||
public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException {
|
||||
byte[] data = nfcGetFingerprints(isoDep);
|
||||
public byte[] nfcGetFingerprint(int idx) throws IOException {
|
||||
byte[] data = nfcGetFingerprints();
|
||||
|
||||
// return the master key fingerprint
|
||||
ByteBuffer fpbuf = ByteBuffer.wrap(data);
|
||||
byte[] fp = new byte[20];
|
||||
fpbuf.position(idx*20);
|
||||
fpbuf.position(idx * 20);
|
||||
fpbuf.get(fp, 0, 20);
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
public byte[] nfcGetAid() throws IOException {
|
||||
|
||||
String info = "00CA004F00";
|
||||
return mIsoDep.transceive(Hex.decode(info));
|
||||
|
||||
}
|
||||
|
||||
public String nfcGetUserId() throws IOException {
|
||||
|
||||
String info = "00CA006500";
|
||||
return nfcGetHolderName(nfcCommunicate(info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls to calculate the signature and returns the MPI value
|
||||
*
|
||||
* @param hash the hash for signing
|
||||
* @return a big integer representing the MPI for the given hash
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
|
||||
nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing)
|
||||
|
||||
// dsi, including Lc
|
||||
String dsi;
|
||||
@@ -367,7 +325,7 @@ public class NfcActivity extends BaseActivity {
|
||||
switch (hashAlgo) {
|
||||
case HashAlgorithmTags.SHA1:
|
||||
if (hash.length != 20) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
|
||||
}
|
||||
dsi = "23" // Lc
|
||||
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
|
||||
@@ -378,45 +336,45 @@ public class NfcActivity extends BaseActivity {
|
||||
break;
|
||||
case HashAlgorithmTags.RIPEMD160:
|
||||
if (hash.length != 20) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
|
||||
}
|
||||
dsi = "233021300906052B2403020105000414" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA224:
|
||||
if (hash.length != 28) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
|
||||
}
|
||||
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA256:
|
||||
if (hash.length != 32) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
|
||||
}
|
||||
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA384:
|
||||
if (hash.length != 48) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
|
||||
}
|
||||
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA512:
|
||||
if (hash.length != 64) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
|
||||
}
|
||||
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not supported hash algo!");
|
||||
throw new IOException("Not supported hash algo!");
|
||||
}
|
||||
|
||||
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
||||
String apdu =
|
||||
"002A9E9A" // CLA, INS, P1, P2
|
||||
+ dsi // digital signature input
|
||||
+ "00"; // Le
|
||||
"002A9E9A" // CLA, INS, P1, P2
|
||||
+ dsi // digital signature input
|
||||
+ "00"; // Le
|
||||
|
||||
String response = card(apdu);
|
||||
String response = nfcCommunicate(apdu);
|
||||
|
||||
// split up response into signature and status
|
||||
String status = response.substring(response.length()-4);
|
||||
@@ -426,22 +384,20 @@ public class NfcActivity extends BaseActivity {
|
||||
while (status.substring(0, 2).equals("61")) {
|
||||
Log.d(Constants.TAG, "requesting more data, status " + status);
|
||||
// Send GET RESPONSE command
|
||||
response = card("00C00000" + status.substring(2));
|
||||
response = nfcCommunicate("00C00000" + status.substring(2));
|
||||
status = response.substring(response.length()-4);
|
||||
signature += response.substring(0, response.length()-4);
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "final response:" + status);
|
||||
|
||||
if ( ! status.equals("9000")) {
|
||||
toast("Bad NFC response code: " + status);
|
||||
return null;
|
||||
if ( ! "9000".equals(status)) {
|
||||
throw new IOException("Bad NFC response code: " + status);
|
||||
}
|
||||
|
||||
// Make sure the signature we received is actually the expected number of bytes long!
|
||||
if (signature.length() != 256 && signature.length() != 512) {
|
||||
toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
|
||||
return null;
|
||||
throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
|
||||
}
|
||||
|
||||
return Hex.decode(signature);
|
||||
@@ -452,9 +408,10 @@ public class NfcActivity extends BaseActivity {
|
||||
*
|
||||
* @param encryptedSessionKey the encoded session key
|
||||
* @return the decoded session key
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
|
||||
nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption)
|
||||
|
||||
String firstApdu = "102a8086fe";
|
||||
String secondApdu = "002a808603";
|
||||
String le = "00";
|
||||
@@ -468,22 +425,48 @@ public class NfcActivity extends BaseActivity {
|
||||
two[i] = encryptedSessionKey[i + one.length + 1];
|
||||
}
|
||||
|
||||
String first = card(firstApdu + getHex(one));
|
||||
String second = card(secondApdu + getHex(two) + le);
|
||||
String first = nfcCommunicate(firstApdu + getHex(one));
|
||||
String second = nfcCommunicate(secondApdu + getHex(two) + le);
|
||||
|
||||
String decryptedSessionKey = getDataField(second);
|
||||
String decryptedSessionKey = nfcGetDataField(second);
|
||||
|
||||
Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
|
||||
|
||||
return Hex.decode(decryptedSessionKey);
|
||||
}
|
||||
|
||||
/** Verifies the user's PW1 with the appropriate mode.
|
||||
*
|
||||
* @param mode This is 0x81 for signing, 0x82 for everything else
|
||||
*/
|
||||
public void nfcVerifyPIN(int mode) throws IOException {
|
||||
if (mPin != null) {
|
||||
byte[] pin = new String(mPin.getCharArray()).getBytes();
|
||||
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||
// See specification, page 51
|
||||
String accepted = "9000";
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ String.format("%02x", mode) // P2
|
||||
+ String.format("%02x", pin.length) // Lc
|
||||
+ Hex.toHexString(pin);
|
||||
if (!nfcCommunicate(login).equals(accepted)) { // login
|
||||
handlePinError();
|
||||
throw new IOException("Bad PIN!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the screen
|
||||
*
|
||||
* @param text the text which should be contained within the toast
|
||||
*/
|
||||
private void toast(String text) {
|
||||
protected void toast(String text) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@@ -493,7 +476,7 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void enableNfcForegroundDispatch() {
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
Intent nfcI = new Intent(this, NfcActivity.class)
|
||||
Intent nfcI = new Intent(this, getClass())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
IntentFilter[] writeTagFilters = new IntentFilter[]{
|
||||
@@ -518,13 +501,7 @@ public class NfcActivity extends BaseActivity {
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the user out of the raw card output regarding card holder related data
|
||||
*
|
||||
* @param name the raw card holder related data from the card
|
||||
* @return the name given in this data
|
||||
*/
|
||||
public String getName(String name) {
|
||||
public String nfcGetHolderName(String name) {
|
||||
String slength;
|
||||
int ilength;
|
||||
name = name.substring(6);
|
||||
@@ -535,34 +512,16 @@ public class NfcActivity extends BaseActivity {
|
||||
return (name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the raw data from the card by four characters
|
||||
*
|
||||
* @param output the raw data from the card
|
||||
* @return the data field of that data
|
||||
*/
|
||||
private String getDataField(String output) {
|
||||
private String nfcGetDataField(String output) {
|
||||
return output.substring(0, output.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicates with the OpenPgpCard via the APDU
|
||||
*
|
||||
* @param hex the hexadecimal APDU
|
||||
* @return The answer from the card
|
||||
* @throws java.io.IOException throws an exception if something goes wrong
|
||||
*/
|
||||
public String card(String hex) throws IOException {
|
||||
return getHex(mIsoDep.transceive(Hex.decode(hex)));
|
||||
public String nfcCommunicate(String apdu) throws IOException {
|
||||
return getHex(mIsoDep.transceive(Hex.decode(apdu)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array into an hex string
|
||||
*
|
||||
* @param raw the byte array representation
|
||||
* @return the hexadecimal string representation
|
||||
*/
|
||||
public static String getHex(byte[] raw) {
|
||||
return new String(Hex.encode(raw));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
|
||||
/**
|
||||
* All fragments executing crypto operations need to extend this class.
|
||||
*/
|
||||
public abstract class CryptoOperationFragment extends Fragment {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||
|
||||
private void initiateInputActivity(RequiredInputParcel requiredInput) {
|
||||
|
||||
switch (requiredInput.mType) {
|
||||
case NFC_DECRYPT:
|
||||
case NFC_SIGN: {
|
||||
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC);
|
||||
return;
|
||||
}
|
||||
|
||||
case PASSPHRASE:
|
||||
case PASSPHRASE_SYMMETRIC: {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
onCryptoOperationCancelled();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CryptoInputParcel cryptoInput =
|
||||
data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
cryptoOperation(cryptoInput);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CryptoInputParcel cryptoInput =
|
||||
data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
|
||||
cryptoOperation(cryptoInput);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handlePendingMessage(Message message) {
|
||||
|
||||
if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null || !(result instanceof InputPendingResult)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InputPendingResult pendingResult = (InputPendingResult) result;
|
||||
if (pendingResult.isPending()) {
|
||||
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
|
||||
initiateInputActivity(requiredInput);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void cryptoOperation() {
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
|
||||
|
||||
protected void onCryptoOperationCancelled() {
|
||||
// Nothing to do here, in most cases
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,11 +44,24 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteFileDialogFragment newInstance(Uri... deleteUris) {
|
||||
public static DeleteFileDialogFragment newInstance(ArrayList<Uri> deleteUris) {
|
||||
DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelableArray(ARG_DELETE_URIS, deleteUris);
|
||||
args.putParcelableArrayList(ARG_DELETE_URIS, deleteUris);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public static DeleteFileDialogFragment newInstance(Uri deleteUri) {
|
||||
DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
ArrayList<Uri> list = new ArrayList<>();
|
||||
list.add(deleteUri);
|
||||
args.putParcelableArrayList(ARG_DELETE_URIS, list);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -62,7 +75,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
final Uri[] deleteUris = (Uri[]) getArguments().getParcelableArray(ARG_DELETE_URIS);
|
||||
final ArrayList<Uri> deleteUris = getArguments().getParcelableArrayList(ARG_DELETE_URIS);
|
||||
|
||||
final StringBuilder deleteFileNames = new StringBuilder();
|
||||
//Retrieving file names after deletion gives unexpected results
|
||||
@@ -127,7 +140,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
// NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars
|
||||
// would not show up!
|
||||
Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful,
|
||||
deleteUris.length - failedFileNameList.size(), deleteUris.length, failedFileNames.toString()),
|
||||
deleteUris.size() - failedFileNameList.size(), deleteUris.size(), failedFileNames.toString()),
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
if (onDeletedListener != null) {
|
||||
|
||||
@@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_TITLE = "title";
|
||||
private static final String ARG_OLD_PASSPHRASE = "old_passphrase";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
@@ -68,12 +67,11 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
|
||||
* @param messenger to communicate back after setting the passphrase
|
||||
* @return
|
||||
*/
|
||||
public static SetPassphraseDialogFragment newInstance(Messenger messenger, Passphrase oldPassphrase, int title) {
|
||||
public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
|
||||
SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_TITLE, title);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putParcelable(ARG_OLD_PASSPHRASE, oldPassphrase);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -89,7 +87,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
|
||||
|
||||
int title = getArguments().getInt(ARG_TITLE);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
Passphrase oldPassphrase = getArguments().getParcelable(ARG_OLD_PASSPHRASE);
|
||||
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
|
||||
|
||||
@@ -103,13 +100,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
|
||||
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
|
||||
mNoPassphraseCheckBox = (CheckBox) view.findViewById(R.id.passphrase_no_passphrase);
|
||||
|
||||
|
||||
if (oldPassphrase.isEmpty()) {
|
||||
mNoPassphraseCheckBox.setChecked(true);
|
||||
mPassphraseEditText.setEnabled(false);
|
||||
mPassphraseAgainEditText.setEnabled(false);
|
||||
}
|
||||
|
||||
mNoPassphraseCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment.ServiceType;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
@@ -186,7 +187,7 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
|
||||
|
||||
}
|
||||
|
||||
private void certifyLinkedIdentity (Passphrase passphrase) {
|
||||
private void certifyLinkedIdentity (CryptoInputParcel cryptoInput) {
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_saving),
|
||||
@@ -235,7 +236,7 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@@ -257,9 +258,9 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE:
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
Passphrase passphrase =
|
||||
data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
certifyLinkedIdentity(passphrase);
|
||||
CryptoInputParcel cryptoInput =
|
||||
data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
certifyLinkedIdentity(cryptoInput);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
@@ -47,6 +47,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyActio
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
@@ -86,6 +87,7 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
private ViewHolder mViewHolder;
|
||||
private int mLidRank;
|
||||
private OnIdentityLoadedListener mIdLoadedListener;
|
||||
private long mCertifyKeyId;
|
||||
|
||||
public static LinkedIdViewFragment newInstance(Uri dataUri, int rank,
|
||||
boolean isSecret, byte[] fingerprint) throws IOException {
|
||||
@@ -183,7 +185,7 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
}
|
||||
|
||||
public interface OnIdentityLoadedListener {
|
||||
public void onIdentityLoaded();
|
||||
void onIdentityLoaded();
|
||||
}
|
||||
|
||||
public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) {
|
||||
@@ -500,8 +502,8 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
}
|
||||
|
||||
// get the user's passphrase for this key (if required)
|
||||
long certifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
|
||||
if (certifyKeyId == key.none || certifyKeyId == key.symmetric) {
|
||||
mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
|
||||
if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SubtleAttentionSeeker.tint(mViewHolder.vKeySpinnerContainer, 600).start();
|
||||
} else {
|
||||
@@ -513,7 +515,7 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
Passphrase passphrase;
|
||||
try {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(
|
||||
getActivity(), certifyKeyId, certifyKeyId);
|
||||
getActivity(), mCertifyKeyId, mCertifyKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Key not found!", e);
|
||||
getActivity().finish();
|
||||
@@ -521,11 +523,11 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
}
|
||||
if (passphrase == null) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, certifyKeyId);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mCertifyKeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
} else {
|
||||
certifyResource(certifyKeyId, "");
|
||||
certifyResource(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,13 +536,9 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
String passphrase = data.getStringExtra(
|
||||
PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
long certifyKeyId = data.getLongExtra(PassphraseDialogActivity.EXTRA_KEY_ID, 0L);
|
||||
if (certifyKeyId == 0L) {
|
||||
throw new AssertionError("key id must not be 0");
|
||||
}
|
||||
certifyResource(certifyKeyId, passphrase);
|
||||
CryptoInputParcel cryptoInput = data.getParcelableExtra(
|
||||
PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
certifyResource(cryptoInput);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -551,7 +549,7 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
}
|
||||
}
|
||||
|
||||
private void certifyResource(long certifyKeyId, String passphrase) {
|
||||
private void certifyResource(CryptoInputParcel cryptoInput) {
|
||||
|
||||
if (mIsSecret) {
|
||||
return;
|
||||
@@ -564,13 +562,15 @@ public class LinkedIdViewFragment extends Fragment implements
|
||||
|
||||
long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
|
||||
CertifyAction action = new CertifyAction(masterKeyId, null,
|
||||
Arrays.asList(mLinkedId.toUserAttribute()));
|
||||
Collections.singletonList(mLinkedId.toUserAttribute()));
|
||||
|
||||
// fill values for this action
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(certifyKeyId);
|
||||
parcel.mCertifyActions.addAll(Arrays.asList(action));
|
||||
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
|
||||
parcel.mCertifyActions.addAll(Collections.singletonList(action));
|
||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
||||
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
/* if (mUploadKeyCheckbox.isChecked()) {
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
||||
|
||||
@@ -216,7 +216,15 @@ public class KeyFormattingUtils {
|
||||
* @return
|
||||
*/
|
||||
public static String convertFingerprintToHex(byte[] fingerprint) {
|
||||
return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
|
||||
return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
public static long getKeyIdFromFingerprint(byte[] fingerprint) {
|
||||
ByteBuffer buf = ByteBuffer.wrap(fingerprint);
|
||||
// skip first 12 bytes of the fingerprint
|
||||
buf.position(12);
|
||||
// the last eight bytes are the key id (big endian, which is default order in ByteBuffer)
|
||||
return buf.getLong();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* 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.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
public class AlgorithmNames {
|
||||
Activity mActivity;
|
||||
|
||||
HashMap<Integer, String> mEncryptionNames = new HashMap<>();
|
||||
HashMap<Integer, String> mHashNames = new HashMap<>();
|
||||
HashMap<Integer, String> mCompressionNames = new HashMap<>();
|
||||
|
||||
public AlgorithmNames(Activity context) {
|
||||
super();
|
||||
this.mActivity = context;
|
||||
|
||||
mEncryptionNames.put(PGPEncryptedData.AES_128, "AES-128");
|
||||
mEncryptionNames.put(PGPEncryptedData.AES_192, "AES-192");
|
||||
mEncryptionNames.put(PGPEncryptedData.AES_256, "AES-256");
|
||||
mEncryptionNames.put(PGPEncryptedData.BLOWFISH, "Blowfish");
|
||||
mEncryptionNames.put(PGPEncryptedData.TWOFISH, "Twofish");
|
||||
mEncryptionNames.put(PGPEncryptedData.CAST5, "CAST5");
|
||||
mEncryptionNames.put(PGPEncryptedData.DES, "DES");
|
||||
mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES");
|
||||
mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA");
|
||||
|
||||
mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160");
|
||||
mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1");
|
||||
mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224");
|
||||
mHashNames.put(HashAlgorithmTags.SHA256, "SHA-256");
|
||||
mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
|
||||
mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512");
|
||||
|
||||
mCompressionNames.put(CompressionAlgorithmTags.UNCOMPRESSED, mActivity.getString(R.string.choice_none)
|
||||
+ " (" + mActivity.getString(R.string.compression_fast) + ")");
|
||||
mCompressionNames.put(CompressionAlgorithmTags.ZIP,
|
||||
"ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
|
||||
mCompressionNames.put(CompressionAlgorithmTags.ZLIB,
|
||||
"ZLIB (" + mActivity.getString(R.string.compression_fast) + ")");
|
||||
mCompressionNames.put(CompressionAlgorithmTags.BZIP2,
|
||||
"BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")");
|
||||
}
|
||||
|
||||
public HashMap<Integer, String> getEncryptionNames() {
|
||||
return mEncryptionNames;
|
||||
}
|
||||
|
||||
public void setEncryptionNames(HashMap<Integer, String> encryptionNames) {
|
||||
this.mEncryptionNames = encryptionNames;
|
||||
}
|
||||
|
||||
public HashMap<Integer, String> getHashNames() {
|
||||
return mHashNames;
|
||||
}
|
||||
|
||||
public void setHashNames(HashMap<Integer, String> hashNames) {
|
||||
this.mHashNames = hashNames;
|
||||
}
|
||||
|
||||
public HashMap<Integer, String> getCompressionNames() {
|
||||
return mCompressionNames;
|
||||
}
|
||||
|
||||
public void setCompressionNames(HashMap<Integer, String> compressionNames) {
|
||||
this.mCompressionNames = compressionNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -220,15 +220,14 @@ public class ContactHelper {
|
||||
*/
|
||||
public static long getMainProfileContactId(ContentResolver resolver) {
|
||||
Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI,
|
||||
new String[]{ ContactsContract.Profile._ID}, null, null, null);
|
||||
new String[]{ContactsContract.Profile._ID}, null, null, null);
|
||||
|
||||
if(profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) {
|
||||
if (profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) {
|
||||
long contactId = profileCursor.getLong(0);
|
||||
profileCursor.close();
|
||||
return contactId;
|
||||
}
|
||||
else {
|
||||
if(profileCursor != null) {
|
||||
} else {
|
||||
if (profileCursor != null) {
|
||||
profileCursor.close();
|
||||
}
|
||||
return -1;
|
||||
@@ -330,7 +329,9 @@ public class ContactHelper {
|
||||
ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
|
||||
ContactsContract.RawContacts.DELETED + "=?",
|
||||
new String[]{//"0" for "not deleted"
|
||||
Constants.ACCOUNT_TYPE, Long.toString(masterKeyId), "0"
|
||||
Constants.ACCOUNT_TYPE,
|
||||
Long.toString(masterKeyId),
|
||||
"0"
|
||||
}, null);
|
||||
if (raw != null) {
|
||||
if (raw.moveToNext()) {
|
||||
@@ -385,23 +386,37 @@ public class ContactHelper {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
long rawContactId = findRawContactId(contentResolver, masterKeyId);
|
||||
if (rawContactId == -1) {
|
||||
return null;
|
||||
}
|
||||
Uri rawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
|
||||
Uri contactUri = ContactsContract.RawContacts.getContactLookupUri(contentResolver, rawContactUri);
|
||||
InputStream photoInputStream =
|
||||
ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes);
|
||||
if (photoInputStream == null) {
|
||||
return null;
|
||||
}
|
||||
return BitmapFactory.decodeStream(photoInputStream);
|
||||
long contactId = findContactId(contentResolver, masterKeyId);
|
||||
return loadPhotoByContactId(contentResolver, contactId, highRes);
|
||||
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap loadPhotoByContactId(ContentResolver contentResolver, long contactId,
|
||||
boolean highRes) {
|
||||
if (contactId == -1) {
|
||||
return null;
|
||||
}
|
||||
Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
||||
// older android versions (tested on API level 15) fail on lookupuris being passed to
|
||||
// openContactPhotoInputStream
|
||||
// http://stackoverflow.com/a/21214524/3000919
|
||||
// Uri lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri);
|
||||
// Also, we don't need a permanent shortcut to the contact since we load it afresh each time
|
||||
|
||||
InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream(
|
||||
contentResolver,
|
||||
contactUri,
|
||||
highRes);
|
||||
|
||||
if (photoInputStream == null) {
|
||||
return null;
|
||||
}
|
||||
return BitmapFactory.decodeStream(photoInputStream);
|
||||
}
|
||||
|
||||
public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2012-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.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2012-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.util;
|
||||
|
||||
public interface FabContainer {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class KeyUpdateHelper {
|
||||
Bundle importData = new Bundle();
|
||||
importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
|
||||
new ArrayList<ImportKeysListEntry>(keys));
|
||||
importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
|
||||
importIntent.putExtra(KeychainIntentService.EXTRA_SERVICE_INTENT, importData);
|
||||
|
||||
importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, new Messenger(mHandler));
|
||||
|
||||
|
||||
@@ -191,9 +191,6 @@ public class NfcHelper {
|
||||
mNfcAdapter.invokeBeam(mActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static subclass of {@link Handler} with a {@link WeakReference} to an {@link Activity} to avoid memory leaks.
|
||||
*/
|
||||
private static class NfcHandler extends Handler {
|
||||
private final WeakReference<Activity> mActivityReference;
|
||||
|
||||
@@ -203,12 +200,10 @@ public class NfcHelper {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Activity activity = mActivityReference.get();
|
||||
|
||||
if (activity != null) {
|
||||
if (mActivityReference.get() != null) {
|
||||
switch (msg.what) {
|
||||
case NFC_SENT:
|
||||
Notify.create(activity, R.string.nfc_successful, Notify.Style.OK).show();
|
||||
Notify.create(mActivityReference.get(), R.string.nfc_successful, Notify.Style.OK).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.util;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* If Parcelables are above 1 MB, Android OS fails to send them via the Binder IPC:
|
||||
* JavaBinder E !!! FAILED BINDER TRANSACTION !!!
|
||||
* To overcome this issue this class allows to cache Parcelables, mapped by unique UUIDs,
|
||||
* which are written to the parcel instead of the whole Parcelable.
|
||||
*/
|
||||
public class ParcelableCache<E> {
|
||||
|
||||
private static final UUID NULL_UUID = new UUID(0, 0);
|
||||
|
||||
/**
|
||||
* A HashMap of UUID:Object
|
||||
* This is used such that when we become parceled, we are
|
||||
* well below the 1 MB boundary that is specified.
|
||||
*/
|
||||
private ConcurrentHashMap<UUID, E> objectCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Dehydrate a Parcelable (such that it is available after deparcelization)
|
||||
* Returns the NULL uuid (0) if you hand it null.
|
||||
*
|
||||
* @param parcelable A Parcelable to dehydrate
|
||||
* @return a UUID, the ticket for your dehydrated Parcelable
|
||||
*/
|
||||
private UUID dehydrateParcelable(E parcelable) {
|
||||
if (parcelable == null) {
|
||||
return NULL_UUID;
|
||||
} else {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
objectCache.put(uuid, parcelable);
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehydrate a Parcelable after going through parcelization,
|
||||
* invalidating its place in the dehydration pool.
|
||||
* This is used such that when parcelized, the Parcelable is no larger than 1 MB.
|
||||
*
|
||||
* @param uuid A UUID ticket that identifies the log in question.
|
||||
* @return An OperationLog.
|
||||
*/
|
||||
private E rehydrateParcelable(UUID uuid) {
|
||||
// UUID.equals isn't well documented; we use compareTo instead.
|
||||
if (NULL_UUID.compareTo(uuid) == 0) {
|
||||
return null;
|
||||
} else {
|
||||
E parcelable = objectCache.get(uuid);
|
||||
objectCache.remove(uuid);
|
||||
return parcelable;
|
||||
}
|
||||
}
|
||||
|
||||
public E readFromParcelAndGetFromCache(Parcel source) {
|
||||
long mostSig = source.readLong();
|
||||
long leastSig = source.readLong();
|
||||
UUID mTicket = new UUID(mostSig, leastSig);
|
||||
// fetch the dehydrated parcelable out of storage (this removes it from the dehydration pool)
|
||||
return rehydrateParcelable(mTicket);
|
||||
}
|
||||
|
||||
public void cacheAndWriteToParcel(E parcelable, Parcel dest) {
|
||||
// Get a ticket for our parcelable.
|
||||
UUID mTicket = dehydrateParcelable(parcelable);
|
||||
// And write out the UUID most and least significant bits.
|
||||
dest.writeLong(mTicket.getMostSignificantBits());
|
||||
dest.writeLong(mTicket.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,21 +109,21 @@ public class Preferences {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
|
||||
}
|
||||
|
||||
public boolean useDefaultYubikeyPin() {
|
||||
public boolean useDefaultYubiKeyPin() {
|
||||
return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, false);
|
||||
}
|
||||
|
||||
public void setUseDefaultYubikeyPin(boolean useDefaultYubikeyPin) {
|
||||
public void setUseDefaultYubiKeyPin(boolean useDefaultYubikeyPin) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, useDefaultYubikeyPin);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean useNumKeypadForYubikeyPin() {
|
||||
public boolean useNumKeypadForYubiKeyPin() {
|
||||
return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, true);
|
||||
}
|
||||
|
||||
public void setUseNumKeypadForYubikeyPin(boolean useNumKeypadForYubikeyPin) {
|
||||
public void setUseNumKeypadForYubiKeyPin(boolean useNumKeypadForYubikeyPin) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, useNumKeypadForYubikeyPin);
|
||||
editor.commit();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user