Merge pull request #1708 from open-keychain/performance
performance improvements
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2016 Vincent Breitmoser
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Provider;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
|
||||
|
||||
/** This is a builder for a special PBESecretKeyDecryptor which is parametrized by a
|
||||
* fixed session key, which is used in place of the one obtained from a passphrase.
|
||||
*/
|
||||
public class SessionKeySecretKeyDecryptorBuilder
|
||||
{
|
||||
private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
|
||||
private PGPDigestCalculatorProvider calculatorProvider;
|
||||
|
||||
private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder()
|
||||
{
|
||||
this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
|
||||
{
|
||||
this.calculatorProvider = calculatorProvider;
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder setProvider(Provider provider)
|
||||
{
|
||||
this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
|
||||
|
||||
if (calculatorProviderBuilder != null)
|
||||
{
|
||||
calculatorProviderBuilder.setProvider(provider);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public SessionKeySecretKeyDecryptorBuilder setProvider(String providerName)
|
||||
{
|
||||
this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
|
||||
|
||||
if (calculatorProviderBuilder != null)
|
||||
{
|
||||
calculatorProviderBuilder.setProvider(providerName);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PBESecretKeyDecryptor build(final byte[] sessionKey)
|
||||
throws PGPException
|
||||
{
|
||||
if (calculatorProvider == null)
|
||||
{
|
||||
calculatorProvider = calculatorProviderBuilder.build();
|
||||
}
|
||||
|
||||
return new PBESecretKeyDecryptor(null, calculatorProvider)
|
||||
{
|
||||
@Override
|
||||
public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) throws PGPException {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
|
||||
throws PGPException
|
||||
{
|
||||
try
|
||||
{
|
||||
Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
|
||||
|
||||
c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
|
||||
|
||||
return c.doFinal(keyData, keyOff, keyLen);
|
||||
}
|
||||
catch (IllegalBlockSizeException e)
|
||||
{
|
||||
throw new PGPException("illegal block size: " + e.getMessage(), e);
|
||||
}
|
||||
catch (BadPaddingException e)
|
||||
{
|
||||
throw new PGPException("bad padding: " + e.getMessage(), e);
|
||||
}
|
||||
catch (InvalidAlgorithmParameterException e)
|
||||
{
|
||||
throw new PGPException("invalid parameter: " + e.getMessage(), e);
|
||||
}
|
||||
catch (InvalidKeyException e)
|
||||
{
|
||||
throw new PGPException("invalid key: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ 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;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
@@ -75,24 +76,22 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
|
||||
|
||||
// Retrieve and unlock secret key
|
||||
CanonicalizedSecretKey certificationKey;
|
||||
long masterKeyId = parcel.mMasterKeyId;
|
||||
try {
|
||||
|
||||
log.add(LogType.MSG_CRT_MASTER_FETCH, 1);
|
||||
CanonicalizedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
|
||||
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
||||
certificationKey = secretKeyRing.getSecretKey();
|
||||
|
||||
CachedPublicKeyRing cachedPublicKeyRing = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
|
||||
Passphrase passphrase;
|
||||
|
||||
switch (certificationKey.getSecretKeyType()) {
|
||||
switch (cachedPublicKeyRing.getSecretKeyType(masterKeyId)) {
|
||||
case PIN:
|
||||
case PATTERN:
|
||||
case PASSPHRASE:
|
||||
passphrase = cryptoInput.getPassphrase();
|
||||
if (passphrase == null) {
|
||||
try {
|
||||
passphrase = getCachedPassphrase(certificationKey.getKeyId(), certificationKey.getKeyId());
|
||||
passphrase = getCachedPassphrase(masterKeyId, masterKeyId);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
|
||||
// treat as a cache miss for error handling purposes
|
||||
}
|
||||
@@ -100,10 +99,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
|
||||
|
||||
if (passphrase == null) {
|
||||
return new CertifyResult(log,
|
||||
RequiredInputParcel.createRequiredSignPassphrase(
|
||||
certificationKey.getKeyId(),
|
||||
certificationKey.getKeyId(),
|
||||
null),
|
||||
RequiredInputParcel.createRequiredSignPassphrase(masterKeyId, masterKeyId, null),
|
||||
cryptoInput
|
||||
);
|
||||
}
|
||||
@@ -123,7 +119,14 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
if (!certificationKey.unlock(passphrase)) {
|
||||
// Get actual secret key
|
||||
CanonicalizedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
|
||||
certificationKey = secretKeyRing.getSecretKey();
|
||||
|
||||
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
||||
boolean unlockSuccessful = certificationKey.unlock(passphrase);
|
||||
if (!unlockSuccessful) {
|
||||
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
@@ -142,8 +145,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
|
||||
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
|
||||
|
||||
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
|
||||
cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
|
||||
certificationKey.getKeyId());
|
||||
cryptoInput.getSignatureTime(), masterKeyId, masterKeyId);
|
||||
|
||||
// Work through all requested certifications
|
||||
for (CertifyAction action : parcel.mCertifyActions) {
|
||||
|
||||
@@ -18,7 +18,16 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
@@ -33,20 +42,15 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
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.security.PrivateKey;
|
||||
import java.security.interfaces.RSAPrivateCrtKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for a PGPSecretKey.
|
||||
@@ -118,7 +122,14 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
}
|
||||
|
||||
public SecretKeyType getSecretKeyType() {
|
||||
/** This method returns the SecretKeyType for this secret key, testing for an empty
|
||||
* passphrase in the process.
|
||||
*
|
||||
* This method can potentially take a LONG time (i.e. seconds), so it should only
|
||||
* ever be called by {@link ProviderHelper} for the purpose of caching its output
|
||||
* in the database.
|
||||
*/
|
||||
public SecretKeyType getSecretKeyTypeSuperExpensive() {
|
||||
S2K s2k = mSecretKey.getS2K();
|
||||
if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) {
|
||||
// divert to card is special
|
||||
@@ -145,13 +156,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
// Otherwise, it's just a regular ol' passphrase
|
||||
return SecretKeyType.PASSPHRASE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true on right passphrase
|
||||
*/
|
||||
public boolean unlock(Passphrase passphrase) throws PgpGeneralException {
|
||||
public boolean unlock(final Passphrase passphrase) throws PgpGeneralException {
|
||||
// handle keys on OpenPGP cards like they were unlocked
|
||||
S2K s2k = mSecretKey.getS2K();
|
||||
if (s2k != null
|
||||
@@ -163,8 +173,26 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
// try to extract keys using the passphrase
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
|
||||
int keyEncryptionAlgorithm = mSecretKey.getKeyEncryptionAlgorithm();
|
||||
if (keyEncryptionAlgorithm == SymmetricKeyAlgorithmTags.NULL) {
|
||||
mPrivateKey = mSecretKey.extractPrivateKey(null);
|
||||
mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] sessionKey;
|
||||
sessionKey = passphrase.getCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k);
|
||||
if (sessionKey == null) {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
// this operation is EXPENSIVE, so we cache its result in the passed Passphrase object!
|
||||
sessionKey = keyDecryptor.makeKeyFromPassPhrase(keyEncryptionAlgorithm, s2k);
|
||||
passphrase.addCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k, sessionKey);
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new SessionKeySecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(sessionKey);
|
||||
mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor);
|
||||
mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
|
||||
} catch (PGPException e) {
|
||||
|
||||
@@ -70,20 +70,6 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
return new CanonicalizedSecretKey(this, mRing.getSecretKey(id));
|
||||
}
|
||||
|
||||
/** Returns the key id which should be used for signing.
|
||||
*
|
||||
* This method returns keys which are actually available (ie. secret available, and not stripped,
|
||||
* revoked, or expired), hence only works on keyrings where a secret key is available!
|
||||
*/
|
||||
public long getSecretSignId() throws PgpGeneralException {
|
||||
for(CanonicalizedSecretKey key : secretKeyIterator()) {
|
||||
if (key.canSign() && key.isValid() && key.getSecretKeyType().isUsable()) {
|
||||
return key.getKeyId();
|
||||
}
|
||||
}
|
||||
throw new PgpGeneralException("no valid signing key available");
|
||||
}
|
||||
|
||||
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
|
||||
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
|
||||
return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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.pgp;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
|
||||
|
||||
/** This is an immutable and parcelable class which stores the full s2k parametrization
|
||||
* of an encrypted secret key, i.e. all fields of the {@link S2K} class (type, hash algo,
|
||||
* iteration count, iv) plus the encryptionAlgorithm. This class is intended to be used
|
||||
* as key in a HashMap for session key caching purposes, and overrides the
|
||||
* {@link #hashCode} and {@link #equals} methods in a suitable way.
|
||||
*
|
||||
* Note that although it is a rather unlikely scenario that secret keys of the same key
|
||||
* are encrypted with different ciphers, the encryption algorithm still determines the
|
||||
* length of the specific session key and thus needs to be considered for purposes of
|
||||
* session key caching.
|
||||
*
|
||||
* @see org.bouncycastle.bcpg.S2K
|
||||
*/
|
||||
public class ComparableS2K implements Parcelable {
|
||||
|
||||
private final int encryptionAlgorithm;
|
||||
private final int s2kType;
|
||||
private final int s2kHashAlgo;
|
||||
private final long s2kItCount;
|
||||
private final byte[] s2kIV;
|
||||
|
||||
Integer cachedHashCode;
|
||||
|
||||
public ComparableS2K(int encryptionAlgorithm, S2K s2k) {
|
||||
this.encryptionAlgorithm = encryptionAlgorithm;
|
||||
this.s2kType = s2k.getType();
|
||||
this.s2kHashAlgo = s2k.getHashAlgorithm();
|
||||
this.s2kItCount = s2k.getIterationCount();
|
||||
this.s2kIV = s2k.getIV();
|
||||
}
|
||||
|
||||
protected ComparableS2K(Parcel in) {
|
||||
encryptionAlgorithm = in.readInt();
|
||||
s2kType = in.readInt();
|
||||
s2kHashAlgo = in.readInt();
|
||||
s2kItCount = in.readLong();
|
||||
s2kIV = in.createByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (cachedHashCode == null) {
|
||||
cachedHashCode = encryptionAlgorithm;
|
||||
cachedHashCode = 31 * cachedHashCode + s2kType;
|
||||
cachedHashCode = 31 * cachedHashCode + s2kHashAlgo;
|
||||
cachedHashCode = 31 * cachedHashCode + (int) (s2kItCount ^ (s2kItCount >>> 32));
|
||||
cachedHashCode = 31 * cachedHashCode + Arrays.hashCode(s2kIV);
|
||||
}
|
||||
|
||||
return cachedHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
boolean isComparableS2K = o instanceof ComparableS2K;
|
||||
if (!isComparableS2K) {
|
||||
return false;
|
||||
}
|
||||
ComparableS2K other = (ComparableS2K) o;
|
||||
return encryptionAlgorithm == other.encryptionAlgorithm
|
||||
&& s2kType == other.s2kType
|
||||
&& s2kHashAlgo == other.s2kHashAlgo
|
||||
&& s2kItCount == other.s2kItCount
|
||||
&& Arrays.equals(s2kIV, other.s2kIV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(encryptionAlgorithm);
|
||||
dest.writeInt(s2kType);
|
||||
dest.writeInt(s2kHashAlgo);
|
||||
dest.writeLong(s2kItCount);
|
||||
dest.writeByteArray(s2kIV);
|
||||
}
|
||||
|
||||
public static final Creator<ComparableS2K> CREATOR = new Creator<ComparableS2K>() {
|
||||
@Override
|
||||
public ComparableS2K createFromParcel(Parcel in) {
|
||||
return new ComparableS2K(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComparableS2K[] newArray(int size) {
|
||||
return new ComparableS2K[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
* Copyright (C) 2015-2016 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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
|
||||
@@ -64,6 +65,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
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.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
@@ -539,7 +542,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||
CanonicalizedSecretKey secretEncryptionKey = null;
|
||||
CanonicalizedSecretKey decryptionKey = null;
|
||||
|
||||
Passphrase passphrase = null;
|
||||
|
||||
@@ -560,85 +563,89 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
log.add(LogType.MSG_DC_ASYM, indent,
|
||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||
|
||||
CanonicalizedSecretKeyRing secretKeyRing;
|
||||
CachedPublicKeyRing cachedPublicKeyRing;
|
||||
try {
|
||||
// get actual keyring object based on master key id
|
||||
secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
|
||||
cachedPublicKeyRing = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)
|
||||
);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
long masterKeyId = cachedPublicKeyRing.getMasterKeyId();
|
||||
|
||||
// allow only specific keys for decryption?
|
||||
if (input.getAllowedKeyIds() != null) {
|
||||
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
||||
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
|
||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||
|
||||
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
|
||||
// this key is in our db, but NOT allowed!
|
||||
// continue with the next packet in the while loop
|
||||
result.skippedDisallowedKey = true;
|
||||
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
|
||||
if (!secretKeyType.isUsable()) {
|
||||
decryptionKey = null;
|
||||
log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get actual subkey which has been used for this encryption packet
|
||||
CanonicalizedSecretKeyRing canonicalizedSecretKeyRing = mProviderHelper
|
||||
.getCanonicalizedSecretKeyRing(masterKeyId);
|
||||
CanonicalizedSecretKey candidateDecryptionKey = canonicalizedSecretKeyRing.getSecretKey(subKeyId);
|
||||
|
||||
if (!candidateDecryptionKey.canEncrypt()) {
|
||||
log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (secretKeyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||
passphrase = null;
|
||||
} else if (secretKeyType == SecretKeyType.PASSPHRASE_EMPTY) {
|
||||
passphrase = new Passphrase("");
|
||||
} 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
|
||||
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);
|
||||
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (passphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
return result.with(new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, subKeyId),
|
||||
cryptoInput));
|
||||
}
|
||||
}
|
||||
|
||||
// check for insecure encryption key
|
||||
if ( ! PgpSecurityConstants.isSecureKey(candidateDecryptionKey)) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
result.insecureEncryptionKey = true;
|
||||
}
|
||||
|
||||
// we're good, write down the data for later
|
||||
asymmetricPacketFound = true;
|
||||
encryptedDataAsymmetric = encData;
|
||||
decryptionKey = candidateDecryptionKey;
|
||||
|
||||
} catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
|
||||
// continue with the next packet in the while loop
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// allow only specific keys for decryption?
|
||||
if (input.getAllowedKeyIds() != null) {
|
||||
long masterKeyId = secretKeyRing.getMasterKeyId();
|
||||
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
||||
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
|
||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||
|
||||
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
|
||||
// this key is in our db, but NOT allowed!
|
||||
// continue with the next packet in the while loop
|
||||
result.skippedDisallowedKey = true;
|
||||
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// get subkey which has been used for this encryption packet
|
||||
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
|
||||
|
||||
if (!secretEncryptionKey.canEncrypt()) {
|
||||
secretEncryptionKey = null;
|
||||
log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!secretEncryptionKey.getSecretKeyType().isUsable()) {
|
||||
secretEncryptionKey = null;
|
||||
log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* secret key exists in database and is allowed! */
|
||||
asymmetricPacketFound = true;
|
||||
|
||||
encryptedDataAsymmetric = encData;
|
||||
|
||||
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
|
||||
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);
|
||||
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (passphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
return result.with(new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredDecryptPassphrase(
|
||||
secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
|
||||
cryptoInput));
|
||||
}
|
||||
}
|
||||
|
||||
// check for insecure encryption key
|
||||
if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
|
||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||
result.insecureEncryptionKey = true;
|
||||
}
|
||||
|
||||
// break out of while, only decrypt the first packet where we have a key
|
||||
break;
|
||||
|
||||
@@ -735,7 +742,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||
if (!secretEncryptionKey.unlock(passphrase)) {
|
||||
if (!decryptionKey.unlock(passphrase)) {
|
||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||
}
|
||||
@@ -748,7 +755,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||
|
||||
CachingDataDecryptorFactory decryptorFactory
|
||||
= secretEncryptionKey.getCachingDecryptorFactory(cryptoInput);
|
||||
= decryptionKey.getCachingDecryptorFactory(cryptoInput);
|
||||
|
||||
// special case: if the decryptor does not have a session key cached for this encrypted
|
||||
// data, and can't actually decrypt on its own, return a pending intent
|
||||
@@ -757,8 +764,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
||||
|
||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||
return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
||||
secretEncryptionKey.getRing().getMasterKeyId(),
|
||||
secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
||||
decryptionKey.getRing().getMasterKeyId(),
|
||||
decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
||||
), cryptoInput));
|
||||
|
||||
}
|
||||
|
||||
@@ -166,12 +166,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
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());
|
||||
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
||||
long signingMasterKeyId = input.getSignatureMasterKeyId();
|
||||
long signingSubKeyId = input.getSignatureSubKeyId();
|
||||
{
|
||||
CanonicalizedSecretKeyRing signingKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId);
|
||||
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
||||
}
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
@@ -179,7 +180,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
switch (signingKey.getSecretKeyType()) {
|
||||
switch (mProviderHelper.getCachedPublicKeyRing(signingMasterKeyId).getSecretKeyType(signingSubKeyId)) {
|
||||
case DIVERT_TO_CARD:
|
||||
case PASSPHRASE_EMPTY: {
|
||||
if (!signingKey.unlock(new Passphrase())) {
|
||||
@@ -196,14 +197,14 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
Passphrase localPassphrase = cryptoInput.getPassphrase();
|
||||
if (localPassphrase == null) {
|
||||
try {
|
||||
localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId());
|
||||
localPassphrase = getCachedPassphrase(signingMasterKeyId, signingKey.getKeyId());
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
|
||||
}
|
||||
}
|
||||
if (localPassphrase == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
|
||||
signingMasterKeyId, signingKey.getKeyId(),
|
||||
cryptoInput.getSignatureTime()), cryptoInput);
|
||||
}
|
||||
if (!signingKey.unlock(localPassphrase)) {
|
||||
|
||||
@@ -74,7 +74,7 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
public long extractOrGetMasterKeyId() throws PgpKeyNotFoundException {
|
||||
// try extracting from the uri first
|
||||
String firstSegment = mUri.getPathSegments().get(1);
|
||||
if (!firstSegment.equals("find")) try {
|
||||
if (!"find".equals(firstSegment)) try {
|
||||
return Long.parseLong(firstSegment);
|
||||
} catch (NumberFormatException e) {
|
||||
// didn't work? oh well.
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
@@ -254,8 +255,9 @@ public class ProviderHelper {
|
||||
KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
|
||||
}
|
||||
|
||||
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) {
|
||||
return new CachedPublicKeyRing(this, queryUri);
|
||||
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
|
||||
long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId();
|
||||
return getCachedPublicKeyRing(masterKeyId);
|
||||
}
|
||||
|
||||
public CachedPublicKeyRing getCachedPublicKeyRing(long id) {
|
||||
@@ -828,7 +830,7 @@ public class ProviderHelper {
|
||||
mIndent += 1;
|
||||
for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) {
|
||||
long id = sub.getKeyId();
|
||||
SecretKeyType mode = sub.getSecretKeyType();
|
||||
SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive();
|
||||
values.put(Keys.HAS_SECRET, mode.getNum());
|
||||
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
|
||||
new String[]{Long.toString(id)});
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
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.provider.ProviderHelper.NotFoundException;
|
||||
@@ -112,12 +113,10 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// handle empty passphrases by directly returning an empty crypto input parcel
|
||||
try {
|
||||
CanonicalizedSecretKeyRing pubRing =
|
||||
new ProviderHelper(this).getCanonicalizedSecretKeyRing(
|
||||
requiredInput.getMasterKeyId());
|
||||
CachedPublicKeyRing pubRing =
|
||||
new ProviderHelper(this).getCachedPublicKeyRing(requiredInput.getMasterKeyId());
|
||||
// use empty passphrase for empty passphrase
|
||||
if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() ==
|
||||
SecretKeyType.PASSPHRASE_EMPTY) {
|
||||
if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) {
|
||||
// also return passphrase back to activity
|
||||
Intent returnIntent = new Intent();
|
||||
cryptoInputParcel.mPassphrase = new Passphrase("");
|
||||
@@ -161,7 +160,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
private TextView mPassphraseText;
|
||||
private EditText[] mBackupCodeEditText;
|
||||
|
||||
private CanonicalizedSecretKeyRing mSecretRing = null;
|
||||
private boolean mIsCancelled = false;
|
||||
private RequiredInputParcel mRequiredInput;
|
||||
|
||||
@@ -233,24 +231,20 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
long subKeyId = mRequiredInput.getSubKeyId();
|
||||
|
||||
ProviderHelper helper = new ProviderHelper(activity);
|
||||
mSecretRing = helper.getCanonicalizedSecretKeyRing(
|
||||
CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
|
||||
// yes the inner try/catch block is necessary, otherwise the final variable
|
||||
// above can't be statically verified to have been set in all cases because
|
||||
// the catch clause doesn't return.
|
||||
try {
|
||||
String mainUserId = mSecretRing.getPrimaryUserIdWithFallback();
|
||||
KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
|
||||
if (mainUserIdSplit.name != null) {
|
||||
userId = mainUserIdSplit.name;
|
||||
} else {
|
||||
userId = getString(R.string.user_id_no_name);
|
||||
}
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
userId = null;
|
||||
String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback();
|
||||
KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
|
||||
if (mainUserIdSplit.name != null) {
|
||||
userId = mainUserIdSplit.name;
|
||||
} else {
|
||||
userId = getString(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType();
|
||||
keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
|
||||
switch (keyType) {
|
||||
case PASSPHRASE:
|
||||
message = getString(R.string.passphrase_for, userId);
|
||||
@@ -271,7 +265,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
} catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
|
||||
alert.setTitle(R.string.title_key_not_found);
|
||||
alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@@ -389,7 +383,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
final int timeToLiveSeconds = mTimeToLiveSpinner.getSelectedTimeToLive();
|
||||
|
||||
// Early breakout if we are dealing with a symmetric key
|
||||
if (mSecretRing == null) {
|
||||
if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) {
|
||||
if (!mRequiredInput.mSkipCaching) {
|
||||
PassphraseCacheService.addCachedPassphrase(getActivity(),
|
||||
Constants.key.symmetric, Constants.key.symmetric, passphrase,
|
||||
@@ -403,32 +397,48 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
mLayout.setDisplayedChild(1);
|
||||
positive.setEnabled(false);
|
||||
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
new AsyncTask<Void, Void, CanonicalizedSecretKey>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
protected CanonicalizedSecretKey doInBackground(Void... params) {
|
||||
try {
|
||||
// wait some 100ms here, give the user time to appreciate the progress bar
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
// never mind
|
||||
long timeBeforeOperation = System.currentTimeMillis();
|
||||
|
||||
Long subKeyId = mRequiredInput.getSubKeyId();
|
||||
CanonicalizedSecretKeyRing secretKeyRing =
|
||||
new ProviderHelper(getActivity()).getCanonicalizedSecretKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
|
||||
CanonicalizedSecretKey secretKeyToUnlock =
|
||||
secretKeyRing.getSecretKey(subKeyId);
|
||||
|
||||
// this is the operation may take a very long time (100ms to several seconds!)
|
||||
boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase);
|
||||
|
||||
// if it didn't take that long, give the user time to appreciate the progress bar
|
||||
long operationTime = System.currentTimeMillis() -timeBeforeOperation;
|
||||
if (operationTime < 100) {
|
||||
try {
|
||||
Thread.sleep(100 -operationTime);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// make sure this unlocks
|
||||
return mSecretRing.getSecretKey(mRequiredInput.getSubKeyId()).unlock(passphrase);
|
||||
} catch (PgpGeneralException e) {
|
||||
|
||||
return unlockSucceeded ? secretKeyToUnlock : null;
|
||||
} catch (NotFoundException | PgpGeneralException e) {
|
||||
Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
getActivity().setResult(RESULT_CANCELED);
|
||||
dismiss();
|
||||
getActivity().finish();
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle a good or bad passphrase. This happens in the UI thread! */
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
protected void onPostExecute(CanonicalizedSecretKey result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
// if we were cancelled in the meantime, the result isn't relevant anymore
|
||||
@@ -437,7 +447,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
}
|
||||
|
||||
// if the passphrase was wrong, reset and re-enable the dialogue
|
||||
if (!result) {
|
||||
if (result == null) {
|
||||
mPassphraseEditText.setText("");
|
||||
mPassphraseEditText.setError(getString(R.string.wrong_passphrase));
|
||||
mLayout.setDisplayedChild(0);
|
||||
@@ -455,8 +465,8 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
try {
|
||||
PassphraseCacheService.addCachedPassphrase(getActivity(),
|
||||
mSecretRing.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase,
|
||||
mSecretRing.getPrimaryUserIdWithFallback(), timeToLiveSeconds);
|
||||
mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase,
|
||||
result.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "adding of a passphrase failed", e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>
|
||||
*
|
||||
* 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
|
||||
@@ -22,13 +23,23 @@ import android.os.Parcelable;
|
||||
import android.text.Editable;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.ComparableS2K;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
/**
|
||||
* Passwords should not be stored as Strings in memory.
|
||||
* This class wraps a char[] that can be erased after it is no longer used.
|
||||
* This class wraps a char[] array that is overwritten before the object is freed, to avoid
|
||||
* keeping passphrases in memory as much as possible.
|
||||
*
|
||||
* In addition to the raw passphrases, this class can cache the session key output of an applied
|
||||
* S2K algorithm for a given set of S2K parameters. Since S2K operations are very expensive, this
|
||||
* mechanism should be used to cache session keys whenever possible.
|
||||
*
|
||||
* See also:
|
||||
* <p/>
|
||||
* http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx
|
||||
@@ -38,6 +49,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class Passphrase implements Parcelable {
|
||||
private char[] mPassphrase;
|
||||
private HashMap<ComparableS2K, byte[]> mCachedSessionKeys;
|
||||
|
||||
/**
|
||||
* According to http://stackoverflow.com/a/15844273 EditText is not using String internally
|
||||
@@ -87,8 +99,24 @@ public class Passphrase implements Parcelable {
|
||||
return mPassphrase.length;
|
||||
}
|
||||
|
||||
public char charAt(int index) {
|
||||
return mPassphrase[index];
|
||||
/** @return A cached session key, or null if none exists for the given parameters. */
|
||||
public byte[] getCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k) {
|
||||
if (mCachedSessionKeys == null) {
|
||||
return null;
|
||||
}
|
||||
return mCachedSessionKeys.get(new ComparableS2K(keyEncryptionAlgorithm, s2k));
|
||||
}
|
||||
|
||||
/** Adds a session key for a set of s2k parameters to this Passphrase object's
|
||||
* cache. The caller should make sure that the supplied session key is the result
|
||||
* of an S2K operation applied to exactly the passphrase stored by this object
|
||||
* with the given parameters.
|
||||
*/
|
||||
public void addCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k, byte[] sessionKey) {
|
||||
if (mCachedSessionKeys == null) {
|
||||
mCachedSessionKeys = new HashMap<>();
|
||||
}
|
||||
mCachedSessionKeys.put(new ComparableS2K(keyEncryptionAlgorithm, s2k), sessionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,6 +126,12 @@ public class Passphrase implements Parcelable {
|
||||
if (mPassphrase != null) {
|
||||
Arrays.fill(mPassphrase, ' ');
|
||||
}
|
||||
if (mCachedSessionKeys == null) {
|
||||
return;
|
||||
}
|
||||
for (byte[] cachedSessionKey : mCachedSessionKeys.values()) {
|
||||
Arrays.fill(cachedSessionKey, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,10 +178,29 @@ public class Passphrase implements Parcelable {
|
||||
|
||||
private Passphrase(Parcel source) {
|
||||
mPassphrase = source.createCharArray();
|
||||
int size = source.readInt();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
mCachedSessionKeys = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ComparableS2K cachedS2K = source.readParcelable(getClass().getClassLoader());
|
||||
byte[] cachedSessionKey = source.createByteArray();
|
||||
mCachedSessionKeys.put(cachedS2K, cachedSessionKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeCharArray(mPassphrase);
|
||||
if (mCachedSessionKeys == null || mCachedSessionKeys.isEmpty()) {
|
||||
dest.writeInt(0);
|
||||
return;
|
||||
}
|
||||
dest.writeInt(mCachedSessionKeys.size());
|
||||
for (Entry<ComparableS2K,byte[]> entry : mCachedSessionKeys.entrySet()) {
|
||||
dest.writeParcelable(entry.getKey(), 0);
|
||||
dest.writeByteArray(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<Passphrase> CREATOR = new Creator<Passphrase>() {
|
||||
|
||||
Reference in New Issue
Block a user