From b1ea1261425e05d7eaa803e6ea72c1f0bbb5ae32 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Feb 2016 15:22:36 +0100 Subject: performance: avoid expensive getSecretKeyType call, use cached where possible --- .../keychain/operations/CertifyOperation.java | 28 ++-- .../keychain/pgp/CanonicalizedSecretKey.java | 4 +- .../keychain/pgp/CanonicalizedSecretKeyRing.java | 14 -- .../keychain/pgp/PgpDecryptVerifyOperation.java | 142 +++++++++++---------- .../keychain/pgp/PgpSignEncryptOperation.java | 19 +-- .../keychain/provider/CachedPublicKeyRing.java | 2 +- .../keychain/provider/ProviderHelper.java | 8 +- .../keychain/ui/PassphraseDialogActivity.java | 80 +++++++----- 8 files changed, 152 insertions(+), 145 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 4ad75fde1..79b42ecc4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -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 { // 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 { 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 { 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 { 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) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 7f2a00617..95a0d41cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -120,7 +120,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } - public SecretKeyType getSecretKeyType() { + // This method can potentially take a LONG time (i.e. seconds), so it should only + // ever be called by ProviderHelper to be cached in the database. + public SecretKeyType getSecretKeyTypeSuperExpensive() { S2K s2k = mSecretKey.getS2K(); if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) { // divert to card is special diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index 97b5fa6fe..1df16254f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -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 secretKeyIterator() { final Iterator it = mRing.getSecretKeys(); return new IterableIterator<>(new Iterator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 79a7a8fe1..3fc020aa7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -64,6 +64,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 +541,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation() { + new AsyncTask() { + @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); } -- cgit v1.2.3