diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
8 files changed, 258 insertions, 151 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 77977b691..1ebab7847 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -61,6 +61,8 @@ public abstract class KeyRing { private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^.*@.*\\..*$"); + /** * Splits userId string into naming part, email part, and comment part * <p/> @@ -71,25 +73,38 @@ public abstract class KeyRing { if (!TextUtils.isEmpty(userId)) { final Matcher matcher = USER_ID_PATTERN.matcher(userId); if (matcher.matches()) { - return new UserId(matcher.group(1), matcher.group(3), matcher.group(2)); + String name = matcher.group(1).isEmpty() ? null : matcher.group(1); + String comment = matcher.group(2); + String email = matcher.group(3); + if (comment == null && email == null && name != null && EMAIL_PATTERN.matcher(name).matches()) { + email = name; + name = null; + } + return new UserId(name, email, comment); } } return new UserId(null, null, null); } /** - * Returns a composed user id. Returns null if name is null! + * Returns a composed user id. Returns null if name, email and comment are empty. */ public static String createUserId(UserId userId) { - String userIdString = userId.name; // consider name a required value - if (userIdString != null && !TextUtils.isEmpty(userId.comment)) { - userIdString += " (" + userId.comment + ")"; + StringBuilder userIdBuilder = new StringBuilder(); + if (!TextUtils.isEmpty(userId.name)) { + userIdBuilder.append(userId.name); } - if (userIdString != null && !TextUtils.isEmpty(userId.email)) { - userIdString += " <" + userId.email + ">"; + if (!TextUtils.isEmpty(userId.comment)) { + userIdBuilder.append(" ("); + userIdBuilder.append(userId.comment); + userIdBuilder.append(")"); } - - return userIdString; + if (!TextUtils.isEmpty(userId.email)) { + userIdBuilder.append(" <"); + userIdBuilder.append(userId.email); + userIdBuilder.append(">"); + } + return userIdBuilder.length() == 0 ? null : userIdBuilder.toString(); } public static class UserId implements Serializable { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java index c4525e5cd..31a3f91b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java @@ -26,6 +26,8 @@ public class OpenPgpDecryptionResultBuilder { // builder private boolean mInsecure = false; private boolean mEncrypted = false; + private byte[] sessionKey; + private byte[] decryptedSessionKey; public void setInsecure(boolean insecure) { this.mInsecure = insecure; @@ -36,24 +38,26 @@ public class OpenPgpDecryptionResultBuilder { } public OpenPgpDecryptionResult build() { - OpenPgpDecryptionResult result = new OpenPgpDecryptionResult(); - if (mInsecure) { Log.d(Constants.TAG, "RESULT_INSECURE"); - result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE); - return result; + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey); } if (mEncrypted) { Log.d(Constants.TAG, "RESULT_ENCRYPTED"); - result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED); - } else { - Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED"); - result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED); + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey); } - return result; + Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED"); + return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED); } + public void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) { + if ((sessionKey == null) != (decryptedSessionKey == null)) { + throw new AssertionError("sessionKey must be null iff decryptedSessionKey is null!"); + } + this.sessionKey = sessionKey; + this.decryptedSessionKey = decryptedSessionKey; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java index aa1c2e037..ae0a31191 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java @@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp 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.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; @@ -76,7 +76,7 @@ public class PgpCertifyOperation { // get the master subkey (which we certify for) PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey(); - NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp, + SecurityTokenSignOperationsBuilder requiredInput = new SecurityTokenSignOperationsBuilder(creationTimestamp, publicKey.getKeyID(), publicKey.getKeyID()); try { 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 e15139a7f..a27e4a8d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -26,9 +26,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.security.SignatureException; import java.util.Date; import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import android.content.Context; import android.support.annotation.NonNull; @@ -60,7 +63,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; -import org.sufficientlysecure.keychain.util.CharsetVerifier; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -73,6 +75,7 @@ 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.CharsetVerifier; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; @@ -197,6 +200,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp PGPEncryptedData encryptedData; InputStream cleartextStream; + // the cached session key + byte[] sessionKey; + byte[] decryptedSessionKey; + int symmetricEncryptionAlgo = 0; boolean skippedDisallowedKey = false; @@ -304,6 +311,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp // if this worked out so far, the data is encrypted decryptionResultBuilder.setEncrypted(true); + if (esResult.sessionKey != null && esResult.decryptedSessionKey != null) { + decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey); + } if (esResult.insecureEncryptionKey) { log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1); @@ -545,10 +555,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp boolean asymmetricPacketFound = false; boolean symmetricPacketFound = false; boolean anyPacketFound = false; + boolean decryptedSessionKeyAvailable = false; PGPPublicKeyEncryptedData encryptedDataAsymmetric = null; PGPPBEEncryptedData encryptedDataSymmetric = null; CanonicalizedSecretKey decryptionKey = null; + CachingDataDecryptorFactory cachedKeyDecryptorFactory = new CachingDataDecryptorFactory( + Constants.BOUNCY_CASTLE_PROVIDER_NAME, cryptoInput.getCryptoData()); + ; Passphrase passphrase = null; @@ -569,6 +583,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp log.add(LogType.MSG_DC_ASYM, indent, KeyFormattingUtils.convertKeyIdToHex(subKeyId)); + decryptedSessionKeyAvailable = cachedKeyDecryptorFactory.hasCachedSessionData(encData); + if (decryptedSessionKeyAvailable) { + asymmetricPacketFound = true; + encryptedDataAsymmetric = encData; + break; + } + CachedPublicKeyRing cachedPublicKeyRing; try { // get actual keyring object based on master key id @@ -746,34 +767,38 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp currentProgress += 2; updateProgress(R.string.progress_extracting_key, currentProgress, 100); - try { - log.add(LogType.MSG_DC_UNLOCKING, indent + 1); - if (!decryptionKey.unlock(passphrase)) { - log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1); + CachingDataDecryptorFactory decryptorFactory; + if (decryptedSessionKeyAvailable) { + decryptorFactory = cachedKeyDecryptorFactory; + } else { + try { + log.add(LogType.MSG_DC_UNLOCKING, indent + 1); + if (!decryptionKey.unlock(passphrase)) { + log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1); + return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); + } + } catch (PgpGeneralException e) { + log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1); return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); } - } catch (PgpGeneralException e) { - log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1); - return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log)); - } - - currentProgress += 2; - updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - CachingDataDecryptorFactory decryptorFactory - = decryptionKey.getCachingDecryptorFactory(cryptoInput); + currentProgress += 2; + updateProgress(R.string.progress_preparing_streams, currentProgress, 100); - // 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 - if (!decryptorFactory.canDecrypt() - && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) { + decryptorFactory = decryptionKey.getCachingDecryptorFactory(cryptoInput); - log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); - return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation( - decryptionKey.getRing().getMasterKeyId(), - decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0] - ), 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 + if (!decryptorFactory.canDecrypt() + && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) { + log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); + return result.with(new DecryptVerifyResult(log, + RequiredInputParcel.createSecurityTokenDecryptOperation( + decryptionKey.getRing().getMasterKeyId(), + decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0] + ), cryptoInput)); + } } try { @@ -786,8 +811,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); result.encryptedData = encryptedDataAsymmetric; - cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys()); - + Map<ByteBuffer, byte[]> cachedSessionKeys = decryptorFactory.getCachedSessionKeys(); + cryptoInput.addCryptoData(cachedSessionKeys); + if (cachedSessionKeys.size() >= 1) { + Entry<ByteBuffer, byte[]> entry = cachedSessionKeys.entrySet().iterator().next(); + result.sessionKey = entry.getKey().array(); + result.decryptedSessionKey = entry.getValue(); + } } else { // there wasn't even any useful data if (!anyPacketFound) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index e43548165..404e07230 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -18,6 +18,23 @@ package org.sufficientlysecure.keychain.pgp; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.spec.ECGenParameterSpec; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; + import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; @@ -55,15 +72,15 @@ 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; +import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; 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.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenKeyToCardOperationsBuilder; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; @@ -71,22 +88,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.ProgressScaler; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.SignatureException; -import java.security.spec.ECGenParameterSpec; -import java.util.Arrays; -import java.util.Date; -import java.util.Iterator; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicBoolean; - /** * This class is the single place where ALL operations that actually modify a PGP public or secret * key take place. @@ -496,10 +497,10 @@ public class PgpKeyOperation { OperationLog log, int indent) { - NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder( + SecurityTokenSignOperationsBuilder nfcSignOps = new SecurityTokenSignOperationsBuilder( cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(), masterSecretKey.getKeyID()); - NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder( + SecurityTokenKeyToCardOperationsBuilder nfcKeyToCardOps = new SecurityTokenKeyToCardOperationsBuilder( masterSecretKey.getKeyID()); progress(R.string.progress_modify, 0); @@ -1053,13 +1054,13 @@ public class PgpKeyOperation { } // 6. If requested, change passphrase - if (saveParcel.mNewUnlock != null) { + if (saveParcel.getChangeUnlockParcel() != null) { progress(R.string.progress_modify_passphrase, 90); log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; - sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, - cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent); + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + saveParcel.getChangeUnlockParcel().mNewPassphrase, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); @@ -1192,73 +1193,80 @@ public class PgpKeyOperation { } - private static PGPSecretKeyRing applyNewUnlock( - PGPSecretKeyRing sKR, - PGPPublicKey masterPublicKey, - PGPPrivateKey masterPrivateKey, - Passphrase passphrase, - ChangeUnlockParcel newUnlock, - OperationLog log, int indent) throws PGPException { + public PgpEditKeyResult modifyKeyRingPassphrase(CanonicalizedSecretKeyRing wsKR, + CryptoInputParcel cryptoInput, + ChangeUnlockParcel changeUnlockParcel) { - if (newUnlock.mNewPassphrase != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); - - // if there is any old packet with notation data - if (hasNotationData(sKR)) { + OperationLog log = new OperationLog(); + int indent = 0; - log.add(LogType.MSG_MF_NOTATION_EMPTY, indent); + if (changeUnlockParcel.mMasterKeyId == null || changeUnlockParcel.mMasterKeyId != wsKR.getMasterKeyId()) { + log.add(LogType.MSG_MF_ERROR_KEYID, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } - // add packet with EMPTY notation data (updates old one, but will be stripped later) - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); - } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + log.add(LogType.MSG_MF, indent, + KeyFormattingUtils.convertKeyIdToHex(wsKR.getMasterKeyId())); + indent += 1; + progress(R.string.progress_building_key, 0); - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - } + // We work on bouncycastle object level here + PGPSecretKeyRing sKR = wsKR.getRing(); + PGPSecretKey masterSecretKey = sKR.getSecretKey(); + PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); + // Make sure the fingerprint matches + if (changeUnlockParcel.mFingerprint == null || !Arrays.equals(changeUnlockParcel.mFingerprint, + masterSecretKey.getPublicKey().getFingerprint())) { + log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } - return sKR; + // Find the first unstripped secret key + PGPSecretKey nonDummy = firstNonDummySecretKeyID(sKR); + if(nonDummy == null) { + log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, indent); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } - if (newUnlock.mNewPin != null) { - sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); + if (!cryptoInput.hasPassphrase()) { + log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent); - log.add(LogType.MSG_MF_NOTATION_PIN, indent); + return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase( + masterSecretKey.getKeyID(), nonDummy.getKeyID(), + cryptoInput.getSignatureTime()), cryptoInput); + } else { + progress(R.string.progress_modify_passphrase, 50); + log.add(LogType.MSG_MF_PASSPHRASE, indent); + indent += 1; - // add packet with "pin" notation data - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), - PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); - { // set subpackets - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - hashedPacketsGen.setExportable(false, false); - hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1"); - sGen.setHashedSubpackets(hashedPacketsGen.generate()); + try { + sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(), + changeUnlockParcel.mNewPassphrase, log, indent); + if (sKR == null) { + // The error has been logged above, just return a bad state + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); + } + } catch (PGPException e) { + throw new UnsupportedOperationException("Failed to build encryptor/decryptor!"); } - sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); - PGPSignature emptySig = sGen.generateCertification(masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); - return sKR; + indent -= 1; + progress(R.string.progress_done, 100); + log.add(LogType.MSG_MF_SUCCESS, indent); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); } + } - throw new UnsupportedOperationException("PIN passphrases not yet implemented!"); + private static PGPSecretKey firstNonDummySecretKeyID(PGPSecretKeyRing secRing) { + Iterator<PGPSecretKey> secretKeyIterator = secRing.getSecretKeys(); + while(secretKeyIterator.hasNext()) { + PGPSecretKey secretKey = secretKeyIterator.next(); + if(!isDummy(secretKey)){ + return secretKey; + } + } + return null; } /** This method returns true iff the provided keyring has a local direct key signature @@ -1293,9 +1301,9 @@ public class PgpKeyOperation { PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray()); + boolean keysModified = false; - // noinspection unchecked - for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { + for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) { log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent, KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID())); @@ -1307,8 +1315,8 @@ public class PgpKeyOperation { ok = true; } catch (PGPException e) { - // if this is the master key, error! - if (sKey.getKeyID() == masterPublicKey.getKeyID()) { + // if the master key failed && it's not stripped, error! + if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) { log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1); return null; } @@ -1335,7 +1343,13 @@ public class PgpKeyOperation { } sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); + keysModified = true; + } + if(!keysModified) { + // no passphrase was changed + log.add(LogType.MSG_MF_ERROR_PASSPHRASES_UNCHANGED, indent+1); + return null; } return sKR; @@ -1348,7 +1362,7 @@ public class PgpKeyOperation { PGPPublicKey masterPublicKey, int flags, long expiry, CryptoInputParcel cryptoInput, - NfcSignOperationsBuilder nfcSignOps, + SecurityTokenSignOperationsBuilder nfcSignOps, int indent, OperationLog log) throws PGPException, IOException, SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java index 580103942..8eae92e63 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -38,7 +38,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { protected Long mSignatureSubKeyId = null; protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT; protected long mAdditionalEncryptId = Constants.key.none; - protected boolean mFailOnMissingEncryptionKeyIds = false; protected String mCharset; protected boolean mCleartextSignature; protected boolean mDetachedSignature = false; @@ -65,7 +64,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { 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; @@ -96,7 +94,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { } dest.writeInt(mSignatureHashAlgorithm); dest.writeLong(mAdditionalEncryptId); - dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0); dest.writeString(mCharset); dest.writeInt(mCleartextSignature ? 1 : 0); dest.writeInt(mDetachedSignature ? 1 : 0); @@ -113,10 +110,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { this.mCharset = mCharset; } - public boolean isFailOnMissingEncryptionKeyIds() { - return mFailOnMissingEncryptionKeyIds; - } - public long getAdditionalEncryptId() { return mAdditionalEncryptId; } @@ -207,11 +200,6 @@ public class PgpSignEncryptInputParcel implements Parcelable { return this; } - public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { - mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; - return this; - } - public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) { this.mCleartextSignature = cleartextSignature; return this; 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 009876045..0bb6419eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -168,10 +168,17 @@ public class PgpSignEncryptOperation extends BaseOperation { try { long signingMasterKeyId = input.getSignatureMasterKeyId(); long signingSubKeyId = input.getSignatureSubKeyId(); - { - CanonicalizedSecretKeyRing signingKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId); - signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId()); + + CanonicalizedSecretKeyRing signingKeyRing = + mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId); + signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId()); + + + // Make sure key is not expired or revoked + if (signingKeyRing.isExpired() || signingKeyRing.isRevoked() + || signingKey.isExpired() || signingKey.isRevoked()) { + log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } // Make sure we are allowed to sign here! @@ -281,16 +288,17 @@ public class PgpSignEncryptOperation extends BaseOperation { if (encryptSubKeyIds.isEmpty()) { log.add(LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (input.isFailOnMissingEncryptionKeyIds()) { - return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); - } + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + // Make sure key is not expired or revoked + if (keyRing.isExpired() || keyRing.isRevoked()) { + log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } catch (ProviderHelper.NotFoundException e) { log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id)); - if (input.isFailOnMissingEncryptionKeyIds()) { - return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); - } + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } } } @@ -470,7 +478,13 @@ public class PgpSignEncryptOperation extends BaseOperation { InputStream in = new BufferedInputStream(inputData.getInputStream()); if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm()); + // Use preferred compression algo + int algo = input.getCompressionAlgorithm(); + if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) { + algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM; + } + + compressGen = new PGPCompressedDataGenerator(algo); bcpgOut = new BCPGOutputStream(compressGen.open(out)); } else { bcpgOut = new BCPGOutputStream(out); @@ -514,7 +528,7 @@ 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); - return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation( + return new PgpSignEncryptResult(log, RequiredInputParcel.createSecurityTokenSignOperation( signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index d696b9d70..b0db36b06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -35,6 +35,8 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import android.support.annotation.VisibleForTesting; + import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -1310,4 +1319,37 @@ public class UncachedKeyRing { || algorithm == PGPPublicKey.ECDH; } + // ONLY TO BE USED FOR TESTING!! + @VisibleForTesting + public static UncachedKeyRing forTestingOnlyAddDummyLocalSignature( + UncachedKeyRing uncachedKeyRing, String passphrase) throws Exception { + PGPSecretKeyRing sKR = (PGPSecretKeyRing) uncachedKeyRing.mRing; + + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + PGPPrivateKey masterPrivateKey = sKR.getSecretKey().extractPrivateKey(keyDecryptor); + PGPPublicKey masterPublicKey = uncachedKeyRing.mRing.getPublicKey(); + + // add packet with "pin" notation data + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), + PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + hashedPacketsGen.setNotationData(false, true, "dummynotationdata", "some data"); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + + return new UncachedKeyRing(sKR); + } + } |