diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
5 files changed, 245 insertions, 102 deletions
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 ee05453e0..a02ff6685 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -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) { 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 63ffc3156..6ed225a83 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<CanonicalizedSecretKey> secretKeyIterator() { final Iterator<PGPSecretKey> it = mRing.getSecretKeys(); return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java new file mode 100644 index 000000000..99b9cdab2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java @@ -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]; + } + }; + +} 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 1ebc75b7d..b6f102593 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -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,83 +563,87 @@ 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) { - // continue with the next packet in the while loop - log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1); - continue; - } + 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; + } + } - // 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); + SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(subKeyId); + if (!secretKeyType.isUsable()) { + decryptionKey = null; + log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, 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; - } + // get actual subkey which has been used for this encryption packet + CanonicalizedSecretKeyRing canonicalizedSecretKeyRing = mProviderHelper + .getCanonicalizedSecretKeyRing(masterKeyId); + CanonicalizedSecretKey candidateDecryptionKey = canonicalizedSecretKeyRing.getSecretKey(subKeyId); - /* secret key exists in database and is allowed! */ - asymmetricPacketFound = true; + if (!candidateDecryptionKey.canEncrypt()) { + log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1); + continue; + } - encryptedDataAsymmetric = encData; + 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 (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(masterKeyId, subKeyId), + cryptoInput)); + } } - // 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(candidateDecryptionKey)) { + log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1); + result.insecureEncryptionKey = true; } - } - // check for insecure encryption key - if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) { - 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; } // break out of while, only decrypt the first packet where we have a key @@ -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)); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 4c3471b21..f1d4d1272 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -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)) { |