From c7b0b650c1467f7f0d7d8d62798c458597be0225 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 00:44:12 +0100 Subject: [PATCH 01/25] introduce handling of notation direct key signatures, and experimental pin notation packet --- .../operations/results/OperationResult.java | 4 +- .../keychain/pgp/PgpKeyOperation.java | 49 ++++++++++++++- .../keychain/pgp/UncachedKeyRing.java | 59 +++++++++++++++---- .../keychain/pgp/WrappedSignature.java | 18 ++++++ .../src/main/res/values-de/strings.xml | 2 +- .../src/main/res/values-es/strings.xml | 2 +- .../src/main/res/values-fr/strings.xml | 2 +- .../src/main/res/values-it/strings.xml | 2 +- .../src/main/res/values-ja/strings.xml | 2 +- .../src/main/res/values-sl/strings.xml | 2 +- .../src/main/res/values-sr/strings.xml | 2 +- .../src/main/res/values-sv/strings.xml | 2 +- OpenKeychain/src/main/res/values/strings.xml | 4 +- 13 files changed, 126 insertions(+), 24 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index dc45fabc3..bf14a918b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -370,13 +370,15 @@ public abstract class OperationResult implements Parcelable { MSG_KC_ERROR_MASTER_ALGO (LogLevel.ERROR, R.string.msg_kc_error_master_algo), MSG_KC_ERROR_DUP_KEY (LogLevel.ERROR, R.string.msg_kc_error_dup_key), MSG_KC_MASTER (LogLevel.DEBUG, R.string.msg_kc_master), + MSG_KC_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_bad_type), MSG_KC_REVOKE_BAD_ERR (LogLevel.WARN, R.string.msg_kc_revoke_bad_err), MSG_KC_REVOKE_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_revoke_bad_local), MSG_KC_REVOKE_BAD_TIME (LogLevel.WARN, R.string.msg_kc_revoke_bad_time), - MSG_KC_REVOKE_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_revoke_bad_type), MSG_KC_REVOKE_BAD_TYPE_UID (LogLevel.WARN, R.string.msg_kc_revoke_bad_type_uid), MSG_KC_REVOKE_BAD (LogLevel.WARN, R.string.msg_kc_revoke_bad), MSG_KC_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_revoke_dup), + MSG_KC_NOTATION_DUP (LogLevel.DEBUG, R.string.msg_kc_notation_dup), + MSG_KC_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_kc_notation_empty), MSG_KC_SUB (LogLevel.DEBUG, R.string.msg_kc_sub), MSG_KC_SUB_BAD(LogLevel.WARN, R.string.msg_kc_sub_bad), MSG_KC_SUB_BAD_ERR(LogLevel.WARN, R.string.msg_kc_sub_bad_err), 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 c125165a8..08d8164ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -34,6 +34,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; @@ -877,7 +878,8 @@ public class PgpKeyOperation { log.add(LogType.MSG_MF_PASSPHRASE, indent); indent += 1; - sKR = applyNewUnlock(sKR, masterPublicKey, passphrase, saveParcel.mNewUnlock, log, indent); + sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, + passphrase, saveParcel.mNewUnlock, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); @@ -909,19 +911,60 @@ public class PgpKeyOperation { private static PGPSecretKeyRing applyNewUnlock( PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, + PGPPrivateKey masterPrivateKey, String passphrase, ChangeUnlockParcel newUnlock, OperationLog log, int indent) throws PGPException { if (newUnlock.mNewPassphrase != null) { - return applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); + sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); + + // add packet with EMPTY notation data (updates old one, but will be stripped later) + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .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); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey); + + return sKR; + } + + if (newUnlock.mNewPin != null) { + sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); + + // add packet with EMPTY notation data (updates old one, but will be stripped later) + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + { // set subpackets + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + hashedPacketsGen.setExportable(false, false); + hashedPacketsGen.setNotationData(false, false, "pin@unlock.sufficientlysecure.org", "1"); + sGen.setHashedSubpackets(hashedPacketsGen.generate()); + } + sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); + PGPSignature emptySig = sGen.generateCertification(masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey); + + return sKR; } throw new UnsupportedOperationException("PIN passphrases not yet implemented!"); } - private static PGPSecretKeyRing applyNewPassphrase( PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, 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 b343c779a..8d1eaa40e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -41,7 +41,6 @@ import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -302,6 +301,7 @@ public class UncachedKeyRing { PGPPublicKey modified = masterKey; PGPSignature revocation = null; + PGPSignature notation = null; for (PGPSignature zert : new IterableIterator(masterKey.getKeySignatures())) { int type = zert.getSignatureType(); @@ -318,9 +318,9 @@ public class UncachedKeyRing { } WrappedSignature cert = new WrappedSignature(zert); - if (type != PGPSignature.KEY_REVOCATION) { + if (type != PGPSignature.KEY_REVOCATION && type != PGPSignature.DIRECT_KEY) { // Unknown type, just remove - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); + log.add(LogType.MSG_KC_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -334,14 +334,6 @@ public class UncachedKeyRing { continue; } - if (cert.isLocal()) { - // Remove revocation certs with "local" flag - log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); - modified = PGPPublicKey.removeCertification(modified, zert); - badCerts += 1; - continue; - } - try { cert.init(masterKey); if (!cert.verifySignature(masterKey)) { @@ -357,6 +349,41 @@ public class UncachedKeyRing { continue; } + if (cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + // special case: direct key signatures! + if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) { + // must be local, otherwise strip! + if (!cert.isLocal()) { + log.add(LogType.MSG_KC_BAD_TYPE, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; + } + + // first notation? fine then. + if (notation == null) { + notation = zert; + // more notations? at least one is superfluous, then. + } else if (notation.getCreationTime().before(zert.getCreationTime())) { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + notation = zert; + } else { + log.add(LogType.MSG_KC_NOTATION_DUP, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + redundantCerts += 1; + } + continue; + } + // first revocation? fine then. if (revocation == null) { revocation = zert; @@ -373,6 +400,16 @@ public class UncachedKeyRing { } } + // If we have a notation packet, check if there is even any data in it? + if (notation != null) { + // If there isn't, might as well strip it + if (new WrappedSignature(notation).getNotation().isEmpty()) { + log.add(LogType.MSG_KC_NOTATION_EMPTY, indent); + modified = PGPPublicKey.removeCertification(modified, notation); + redundantCerts += 1; + } + } + ArrayList processedUserIds = new ArrayList(); for (byte[] rawUserId : new IterableIterator(masterKey.getRawUserIDs())) { String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index 93afb987a..132a28604 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.SignatureSubpacket; import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.sig.Exportable; +import org.spongycastle.bcpg.sig.NotationData; import org.spongycastle.bcpg.sig.Revocable; import org.spongycastle.bcpg.sig.RevocationReason; import org.spongycastle.openpgp.PGPException; @@ -37,6 +38,7 @@ import java.io.IOException; import java.security.SignatureException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; /** OpenKeychain wrapper around PGPSignature objects. * @@ -239,4 +241,20 @@ public class WrappedSignature { SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE); return ! ((Exportable) p).isExportable(); } + + public HashMap getNotation() { + HashMap result = new HashMap(); + + // If there is any notation data + if (mSig.getHashedSubPackets() != null + && mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.NOTATION_DATA)) { + // Iterate over notation data + for (NotationData data : mSig.getHashedSubPackets().getNotationDataOccurrences()) { + result.put(data.getNotationName(), data.getNotationValueBytes()); + } + } + + return result; + } + } diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index 3bb5eb9bb..ecc3d7d37 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -607,7 +607,7 @@ Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat Entferne Schlüsselbund Widerrufszertifikat mit \"Lokal\" Attribut Entferne Schlüsselbund Widerrufszertifikat mit zukünftigem Zeitstempel - Entferne Hauptschlüsselbeglaubigung unbekannter Art (%s) + Entferne Hauptschlüsselbeglaubigung unbekannter Art (%s) Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat Entferne redundantes Schlüsselbund Widerrufszertifikat Verarbeite Unterschlüssel %s diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index d95f5bfb4..3de5efb80 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -652,7 +652,7 @@ Eliminando certificado defectuoso de revocación de juego de claves Eliminando certificado de revocación de juego de claves, con distintivo \"local\" Eliminando certificado de revocación de juego de claves, con marca de tiempo futura - Eliminando certificado de clave maestra, de tipo desconocido (%s) + Eliminando certificado de clave maestra, de tipo desconocido (%s) Eliminando certificado de identificación de usuario en posición incorrecta Eliminando certificado defectuoso de revocación de juego de claves Eliminando certificado redundante de revocación de juego de claves diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index d107bb756..ed54f8d16 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -652,7 +652,7 @@ Suppression du mauvais certificat de révocation du trousseau Suppression du certificat de révocation du trousseau ayant le drapeau « local » Suppression du certificat de révocation du trousseau ayant une estampille temporelle dans le futur - Suppression du certificat de clef maîtresse de type inconnu (%s) + Suppression du certificat de clef maîtresse de type inconnu (%s) Suppression du certificat de l\'ID d\'utilisateur en mauvaise position Suppression du mauvais certificat de révocation du trousseau Suppression du certificat redondant de révocation du trousseau diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index ceab689ab..cfe1253e2 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -602,7 +602,7 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars Rimozione di certificato di revoca del portachiavi corrotto Rimozione certificato di revoca del portachiavi con caratteristica \"locale\" Rimozione certificato di revoca del portachiavi con marca temporale futura - Rimozione certificato della chiave principale di tipo sconosciuto (%s) + Rimozione certificato della chiave principale di tipo sconosciuto (%s) Rimozione certificato di revoca del portachiavi corrotto Rimozione certificato di revoca del portachiavi ridondante Elaborazione sottochiave %s diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index 607dfa833..a4e1d4275 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -618,7 +618,7 @@ 問題のある鍵輪の破棄証明を破棄中 鍵輪のローカルフラグ付き破棄証明を破棄中 鍵輪の未来にタイムスタンプがある破棄証明を破棄中 - 問題のある主鍵の不明な型 (%s) の証明を破棄中 + 問題のある主鍵の不明な型 (%s) の証明を破棄中 不正な位置のユーザID検証を破棄中 問題のある鍵輪の破棄証明を破棄中 重複している鍵輪の破棄証明を破棄中 diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index c99b24d79..c30fd87b5 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -450,7 +450,7 @@ Umikam slab certifikat za preklic zbirk ključev Umikam certifikat za preklic zbirk ključev z oznako \"lokalno\" Umikam certifikat za preklic zbirk ključev s časovno znamko v prihodnosti - Umikam certifikat glavnega ključa neznanega tipa (%s) + Umikam certifikat glavnega ključa neznanega tipa (%s) Umikam slab certifikat za preklic zbirk ključev Umikam odvečen certifikat za preklic zbirk ključev Obdelujem podključ %s diff --git a/OpenKeychain/src/main/res/values-sr/strings.xml b/OpenKeychain/src/main/res/values-sr/strings.xml index ff50a637b..31ed39440 100644 --- a/OpenKeychain/src/main/res/values-sr/strings.xml +++ b/OpenKeychain/src/main/res/values-sr/strings.xml @@ -672,7 +672,7 @@ Уклањам лош сертификат опозива привеска Уклањам сертификат опозива привеска са заставицом „локални“ Уклањам сертификат опозива привеска са временском ознаком у будућности - Уклањам сертификат главног кључа непознатог типа (%s) + Уклањам сертификат главног кључа непознатог типа (%s) Уклањам сертификат корисничког ИД-а са погрешног места Уклањам лош сертификат опозива привеска Уклањам сувишни сертификат опозива привеска diff --git a/OpenKeychain/src/main/res/values-sv/strings.xml b/OpenKeychain/src/main/res/values-sv/strings.xml index a56eb7f75..c76c6c356 100644 --- a/OpenKeychain/src/main/res/values-sv/strings.xml +++ b/OpenKeychain/src/main/res/values-sv/strings.xml @@ -573,7 +573,7 @@ Bearbetar huvudnyckel Tar bort dåligt återkallelsecertifikat för nyckelring Tar bort återkallelsecertifikat för nyckelring med framtida tidstämpel - Tar bort huvudnyckelcertifikat av okänd typ (%s) + Tar bort huvudnyckelcertifikat av okänd typ (%s) Tar bort dåligt återkallelsecertifikat för nyckelring Tar bort överflödigt återkallelsecertifikat för nyckelring Bearbetar undernyckel %s diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 9ea16a0ab..52c75109a 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -709,13 +709,15 @@ "The master key uses an unknown (%s) algorithm!" "Subkey %s occurs twice in keyring. Keyring is malformed, not importing!" "Processing master key" + "Removing master key certificate of unknown type (%s)" "Removing bad keyring revocation certificate" "Removing keyring revocation certificate with "local" flag" "Removing keyring revocation certificate with future timestamp" - "Removing master key certificate of unknown type (%s)" "Removing user ID certificate in bad position" "Removing bad keyring revocation certificate" "Removing redundant keyring revocation certificate" + "Removing redundant notation certificate" + "Removing empty notation certificate" "Processing subkey %s" "Removing invalid subkey binding certificate" "Removing bad subkey binding certificate" From 2a5ac4e67c000078e5f1ead3d880397af6dd2cd4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 11:59:57 +0100 Subject: [PATCH 02/25] Revert "Remove restriction for max parallel forks for gradle tests" This reverts commit ce34192dbbff388c0ccd384fba15cd95075a14ca. Conflicts: build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f6fff5d3c..8c3b355d4 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ task wrapper(type: Wrapper) { subprojects { tasks.withType(Test) { - maxParallelForks = 5 + maxParallelForks = 1 } } From 7524881a01efe96d7467e439098ad440d9afb11b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 12:02:37 +0100 Subject: [PATCH 03/25] fix unit tests for ChangeUnlockParcel --- .../keychain/pgp/PgpEncryptDecryptTest.java | 5 +++-- .../keychain/pgp/PgpKeyOperationTest.java | 21 ++++++++++--------- .../pgp/UncachedKeyringCanonicalizeTest.java | 3 ++- .../pgp/UncachedKeyringMergeTest.java | 5 +++-- .../keychain/pgp/UncachedKeyringTest.java | 3 ++- .../keychain/service/SaveKeyringParcel.java | 3 +++ 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index ebe31c9f0..8c1a358b0 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.TestingUtils; @@ -68,7 +69,7 @@ public class PgpEncryptDecryptTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); parcel.mAddUserIds.add("bloom"); - parcel.mNewUnlock = mKeyPhrase1; + parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1); EditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); @@ -86,7 +87,7 @@ public class PgpEncryptDecryptTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); parcel.mAddUserIds.add("belle"); - parcel.mNewUnlock = mKeyPhrase2; + parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase2); EditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 79778c6d0..6fa907520 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.EditKeyResult; 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.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.support.KeyringBuilder; @@ -91,7 +92,7 @@ public class PgpKeyOperationTest { parcel.mAddUserIds.add("twi"); parcel.mAddUserIds.add("pink"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); PgpKeyOperation op = new PgpKeyOperation(null); EditKeyResult result = op.createSecretKeyRing(parcel); @@ -127,7 +128,7 @@ public class PgpKeyOperationTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, new Random().nextInt(256)+255, null, KeyFlags.CERTIFY_OTHER, 0L)); parcel.mAddUserIds.add("shy"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating ring with < 512 bytes keysize should fail", parcel, LogType.MSG_CR_ERROR_KEYSIZE_512); @@ -138,7 +139,7 @@ public class PgpKeyOperationTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.ELGAMAL, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); parcel.mAddUserIds.add("shy"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating ring with ElGamal master key should fail", parcel, LogType.MSG_CR_ERROR_FLAGS_ELGAMAL); @@ -149,7 +150,7 @@ public class PgpKeyOperationTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, null)); parcel.mAddUserIds.add("lotus"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating master key with null expiry should fail", parcel, LogType.MSG_CR_ERROR_NULL_EXPIRY); @@ -160,7 +161,7 @@ public class PgpKeyOperationTest { parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); parcel.mAddUserIds.add("shy"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating ring with non-certifying master key should fail", parcel, LogType.MSG_CR_ERROR_NO_CERTIFY); @@ -170,7 +171,7 @@ public class PgpKeyOperationTest { parcel.reset(); parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating ring without user ids should fail", parcel, LogType.MSG_CR_ERROR_NO_USER_ID); @@ -179,7 +180,7 @@ public class PgpKeyOperationTest { { parcel.reset(); parcel.mAddUserIds.add("shy"); - parcel.mNewUnlock = passphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); assertFailure("creating ring with no master key should fail", parcel, LogType.MSG_CR_ERROR_NO_MASTER); @@ -910,7 +911,7 @@ public class PgpKeyOperationTest { public void testPassphraseChange() throws Exception { // change passphrase to empty - parcel.mNewUnlock = ""; + parcel.mNewUnlock = new ChangeUnlockParcel(""); UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", @@ -923,7 +924,7 @@ public class PgpKeyOperationTest { // modify keyring, change to non-empty passphrase String otherPassphrase = TestingUtils.genPassphrase(true); - parcel.mNewUnlock = otherPassphrase; + parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase); modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, ""); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", @@ -948,7 +949,7 @@ public class PgpKeyOperationTest { PacketTags.SECRET_SUBKEY, sKeyNoPassphrase.tag); String otherPassphrase2 = TestingUtils.genPassphrase(true); - parcel.mNewUnlock = otherPassphrase2; + parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase2); { // if we replace a secret key with one without passphrase modified = KeyringTestingHelper.removePacket(modified, sKeyNoPassphrase.position); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java index cbdbd0e72..7d8a10ee0 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java @@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; @@ -104,7 +105,7 @@ public class UncachedKeyringCanonicalizeTest { parcel.mAddUserIds.add("twi"); parcel.mAddUserIds.add("pink"); // passphrase is tested in PgpKeyOperationTest, just use empty here - parcel.mNewUnlock = ""; + parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); EditKeyResult result = op.createSecretKeyRing(parcel); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 006c77fbc..799640d6c 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.util.ProgressScaler; @@ -96,7 +97,7 @@ public class UncachedKeyringMergeTest { parcel.mAddUserIds.add("twi"); parcel.mAddUserIds.add("pink"); // passphrase is tested in PgpKeyOperationTest, just use empty here - parcel.mNewUnlock = ""; + parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); OperationResult.OperationLog log = new OperationResult.OperationLog(); @@ -112,7 +113,7 @@ public class UncachedKeyringMergeTest { parcel.mAddUserIds.add("shy"); // passphrase is tested in PgpKeyOperationTest, just use empty here - parcel.mNewUnlock = ""; + parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); OperationResult.OperationLog log = new OperationResult.OperationLog(); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java index d8d825783..34c637abf 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -57,7 +58,7 @@ public class UncachedKeyringTest { parcel.mAddUserIds.add("twi"); parcel.mAddUserIds.add("pink"); // passphrase is tested in PgpKeyOperationTest, just use empty here - parcel.mNewUnlock = ""; + parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); EditKeyResult result = op.createSecretKeyRing(parcel); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index f5d4e5bd4..810190fee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -254,6 +254,9 @@ public class SaveKeyringParcel implements Parcelable { // A new pin to use. Must only contain [0-9]+ public final String mNewPin; + public ChangeUnlockParcel(String newPassphrase) { + this(newPassphrase, null); + } public ChangeUnlockParcel(String newPassphrase, String newPin) { if (newPassphrase == null && newPin == null) { throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!"); From 438405d3d2b12e6254ea166cd402ae6ed3a4209c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 12:34:24 +0100 Subject: [PATCH 04/25] add PIN as a SecretKeyType --- .../operations/results/OperationResult.java | 1 + .../keychain/pgp/CanonicalizedSecretKey.java | 10 +++++++++- .../keychain/pgp/CanonicalizedSecretKeyRing.java | 14 ++++++++++++++ .../keychain/pgp/PgpKeyOperation.java | 2 +- .../keychain/pgp/WrappedSignature.java | 6 +++--- .../keychain/provider/ProviderHelper.java | 5 +++++ .../keychain/ui/PassphraseDialogActivity.java | 3 +++ OpenKeychain/src/main/res/values/strings.xml | 2 ++ 8 files changed, 38 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index bf14a918b..606dd49d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -359,6 +359,7 @@ public abstract class OperationResult implements Parcelable { MSG_IS_SUBKEY_STRIPPED (LogLevel.DEBUG, R.string.msg_is_subkey_stripped), MSG_IS_SUBKEY_DIVERT (LogLevel.DEBUG, R.string.msg_is_subkey_divert), MSG_IS_SUBKEY_EMPTY (LogLevel.DEBUG, R.string.msg_is_subkey_empty), + MSG_IS_SUBKEY_PIN (LogLevel.DEBUG, R.string.msg_is_subkey_pin), MSG_IS_SUCCESS_IDENTICAL (LogLevel.OK, R.string.msg_is_success_identical), MSG_IS_SUCCESS (LogLevel.OK, R.string.msg_is_success), 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 f9fa41528..5c99a9854 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -49,6 +49,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -83,7 +84,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } public enum SecretKeyType { - UNAVAILABLE(0), GNU_DUMMY(1), PASSPHRASE(2), PASSPHRASE_EMPTY(3), DIVERT_TO_CARD(4); + UNAVAILABLE(0), GNU_DUMMY(1), PASSPHRASE(2), PASSPHRASE_EMPTY(3), DIVERT_TO_CARD(4), PIN(5); final int mNum; @@ -101,6 +102,8 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return PASSPHRASE_EMPTY; case 4: return DIVERT_TO_CARD; + case 5: + return PIN; // if this case happens, it's probably a check from a database value default: return UNAVAILABLE; @@ -135,6 +138,11 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // It means the passphrase is empty return SecretKeyType.PASSPHRASE_EMPTY; } catch (PGPException e) { + HashMap notation = getRing().getLocalNotationData(); + if (notation.containsKey("unlock.pin@sufficientlysecure.org") + && "1".equals(notation.get("unlock.pin@sufficientlysecure.org"))) { + return SecretKeyType.PIN; + } // Otherwise, it's just a regular ol' passphrase return SecretKeyType.PASSPHRASE; } 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 e20155cc6..eb589c3f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -26,6 +26,7 @@ import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; @@ -36,6 +37,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -130,4 +132,16 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { }); } + public HashMap getLocalNotationData() { + HashMap result = new HashMap(); + Iterator it = getRing().getPublicKey().getKeySignatures(); + while (it.hasNext()) { + WrappedSignature sig = new WrappedSignature(it.next()); + if (sig.isLocal()) { + result.putAll(sig.getNotation()); + } + } + return result; + } + } 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 08d8164ca..6fedbc683 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -949,7 +949,7 @@ public class PgpKeyOperation { { // set subpackets PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); hashedPacketsGen.setExportable(false, false); - hashedPacketsGen.setNotationData(false, false, "pin@unlock.sufficientlysecure.org", "1"); + hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1"); sGen.setHashedSubpackets(hashedPacketsGen.generate()); } sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index 132a28604..c395ca52d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -242,15 +242,15 @@ public class WrappedSignature { return ! ((Exportable) p).isExportable(); } - public HashMap getNotation() { - HashMap result = new HashMap(); + public HashMap getNotation() { + HashMap result = new HashMap(); // If there is any notation data if (mSig.getHashedSubPackets() != null && mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.NOTATION_DATA)) { // Iterate over notation data for (NotationData data : mSig.getHashedSubPackets().getNotationDataOccurrences()) { - result.put(data.getNotationName(), data.getNotationValueBytes()); + result.put(data.getNotationName(), data.getNotationValue()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 05dc99c5e..6daa26cd3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -682,6 +682,11 @@ public class ProviderHelper { KeyFormattingUtils.convertKeyIdToHex(id) ); break; + case PIN: + log(LogType.MSG_IS_SUBKEY_PIN, + KeyFormattingUtils.convertKeyIdToHex(id) + ); + break; case GNU_DUMMY: log(LogType.MSG_IS_SUBKEY_STRIPPED, KeyFormattingUtils.convertKeyIdToHex(id) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index deff648ba..4bd413778 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -170,6 +170,9 @@ public class PassphraseDialogActivity extends FragmentActivity { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); break; + case PIN: + message = getString(R.string.pin_for, userId); + break; case DIVERT_TO_CARD: message = getString(R.string.yubikey_pin, userId); break; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 52c75109a..f22e4f27a 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -205,6 +205,7 @@ "Please enter a passphrase." "Symmetric encryption." "Enter passphrase for '%s'" + "Enter pin for '%s'" "Enter PIN to access YubiKey for '%s'" "Hold YubiKey against the back of your device." "Are you sure you want to delete\n%s?" @@ -696,6 +697,7 @@ "Subkey %s unavailable in secret key" "Marked secret subkey %s as available" "Marked secret subkey %s as available, with empty passphrase" + "Marked secret subkey %s as available, with pin passphrase" "Marked secret subkey %s as stripped" "Marked secret subkey %s as 'divert to smartcard/NFC'" "Keyring contains no new data, nothing to do" From 22ea1defa80d7cb185e75bed20ec9ed28377df8c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 13:07:36 +0100 Subject: [PATCH 05/25] add test case for PIN unlock --- .../keychain/pgp/PgpKeyOperationTest.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 6fa907520..6b53a93ce 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -41,6 +41,7 @@ import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; @@ -100,6 +101,7 @@ public class PgpKeyOperationTest { Assert.assertNotNull("initial test key creation must succeed", result.getRing()); staticRing = result.getRing(); + staticRing = staticRing.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); // we sleep here for a second, to make sure all new certificates have different timestamps Thread.sleep(1000); @@ -912,7 +914,9 @@ public class PgpKeyOperationTest { // change passphrase to empty parcel.mNewUnlock = new ChangeUnlockParcel(""); - UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB); + // note that canonicalization here necessarily strips the empty notation packet + UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, + passphrase, true, false); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -925,7 +929,7 @@ public class PgpKeyOperationTest { // modify keyring, change to non-empty passphrase String otherPassphrase = TestingUtils.genPassphrase(true); parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase); - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, ""); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, "", true, false); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -982,6 +986,28 @@ public class PgpKeyOperationTest { } + @Test + public void testUnlockPin() throws Exception { + + // change passphrase to a pin type + parcel.mNewUnlock = new ChangeUnlockParcel(null, "52351"); + UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB); + + Assert.assertEquals("exactly four packets should have been modified (the secret keys + notation packet)", + 4, onlyB.size()); + + RawPacket dkSig = onlyB.get(1); + Assert.assertEquals("second modified packet should be notation data", + PacketTags.SIGNATURE, dkSig.tag); + + // check that notation data contains pin + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); + Assert.assertEquals("secret key type should be 'pin' after this", + SecretKeyType.PIN, + secretRing.getSecretKey().getSecretKeyType()); + + } + private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel, UncachedKeyRing ring, ArrayList onlyA, From 2223cdd4056341576a540b065bcb34a1e579b141 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 13:07:56 +0100 Subject: [PATCH 06/25] some small notation data fixes --- .../keychain/pgp/PgpKeyOperation.java | 6 ++++-- .../keychain/pgp/UncachedKeyRing.java | 14 ++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) 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 6fedbc683..c8ce349f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -933,7 +933,8 @@ public class PgpKeyOperation { PGPSignature emptySig = sGen.generateCertification(masterPublicKey); masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); return sKR; } @@ -956,7 +957,8 @@ public class PgpKeyOperation { PGPSignature emptySig = sGen.generateCertification(masterPublicKey); masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); return sKR; } 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 8d1eaa40e..5e5a28e83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -349,14 +349,6 @@ public class UncachedKeyRing { continue; } - if (cert.isLocal()) { - // Remove revocation certs with "local" flag - log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); - modified = PGPPublicKey.removeCertification(modified, zert); - badCerts += 1; - continue; - } - // special case: direct key signatures! if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) { // must be local, otherwise strip! @@ -382,6 +374,12 @@ public class UncachedKeyRing { redundantCerts += 1; } continue; + } else if (cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + badCerts += 1; + continue; } // first revocation? fine then. From 71c53f13ece742c4c3673ef1939313af84e92eb6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 30 Dec 2014 16:24:20 +0100 Subject: [PATCH 07/25] fix small merge conflict oversight --- .../keychain/ui/PassphraseDialogActivity.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index cd564551a..fd9324992 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -214,9 +214,6 @@ public class PassphraseDialogActivity extends FragmentActivity { case DIVERT_TO_CARD: message = getString(R.string.yubikey_pin_for, userId); break; - case PIN: - message = getString(R.string.pin_for, userId); - break; default: message = "This should not happen!"; break; From bf537c4a81371e95cff613d4f9847bf2365e6ec8 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 1 Jan 2015 18:24:09 +0100 Subject: [PATCH 08/25] fix unit tests (canonicalize after create) --- .../keychain/pgp/UncachedKeyringCanonicalizeTest.java | 2 ++ .../keychain/pgp/UncachedKeyringMergeTest.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java index 7d8a10ee0..b3a149eec 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java @@ -113,6 +113,8 @@ public class UncachedKeyringCanonicalizeTest { staticRing = result.getRing(); Assert.assertNotNull("initial test key creation must succeed", staticRing); + staticRing = staticRing.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); + // just for later reference totalPackets = 9; diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 799640d6c..7e2cbee0c 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -34,6 +34,7 @@ import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.util.Strings; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; @@ -104,6 +105,7 @@ public class UncachedKeyringMergeTest { EditKeyResult result = op.createSecretKeyRing(parcel); staticRingA = result.getRing(); + staticRingA = staticRingA.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); } { @@ -119,6 +121,7 @@ public class UncachedKeyringMergeTest { OperationResult.OperationLog log = new OperationResult.OperationLog(); EditKeyResult result = op.createSecretKeyRing(parcel); staticRingB = result.getRing(); + staticRingB = staticRingB.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); } Assert.assertNotNull("initial test key creation must succeed", staticRingA); From 5e9d8ca8c6af6a3c274208799cd4b2a22de2d843 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 1 Jan 2015 20:21:32 +0100 Subject: [PATCH 09/25] tests: add multi key encryption/decryption test, new way for fake passphrase cache --- .../keychain/pgp/PgpEncryptDecryptTest.java | 170 +++++++++++++----- .../keychain/pgp/PgpDecryptVerify.java | 2 +- .../keychain/pgp/PgpSignEncrypt.java | 2 +- 3 files changed, 127 insertions(+), 47 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 8c1a358b0..6a396697c 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -119,7 +119,7 @@ public class PgpEncryptDecryptTest { InputData data = new InputData(in, in.available()); Builder b = new PgpSignEncrypt.Builder(Robolectric.application, new ProviderHelper(Robolectric.application), - null, // new DummyPassphraseCache(mPassphrase, 0L) + null, data, out); b.setSymmetricPassphrase(mPassphrase); @@ -216,11 +216,8 @@ public class PgpEncryptDecryptTest { ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); InputData data = new InputData(in, in.available()); - PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder( - Robolectric.application, - new ProviderHelper(Robolectric.application), - null, // new DummyPassphraseCache(null, null), - data, out); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null); b.setPassphrase(mKeyPhrase1); DecryptVerifyResult result = b.build().execute(); Assert.assertTrue("decryption with provided passphrase must succeed", result.success()); @@ -231,74 +228,157 @@ public class PgpEncryptDecryptTest { // TODO how to test passphrase cache? - /*{ // decryption with passphrase cached should succeed + { // decryption with passphrase cached should succeed ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); InputData data = new InputData(in, in.available()); - PassphraseCacheService.addCachedPassphrase( - Robolectric.application, mStaticRing1.getMasterKeyId(), - mStaticRing1.getMasterKeyId(), mKeyPhrase1, "dummy"); + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); - PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder( - Robolectric.application, - new ProviderHelper(Robolectric.application), - null, // new DummyPassphraseCache(mKeyPhrase1, null), - data, out); DecryptVerifyResult result = b.build().execute(); Assert.assertTrue("decryption with cached passphrase must succeed", result.success()); Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", out.toByteArray(), plaintext.getBytes()); Assert.assertNull("signature should be empty", result.getSignatureResult()); - }*/ + } - /*{ // decryption with no passphrase provided should return status pending + { // decryption with no passphrase provided should return status pending ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); InputData data = new InputData(in, in.available()); - PgpDecryptVerify.Builder b = new PgpDecryptVerify.Builder( - Robolectric.application, - new ProviderHelper(Robolectric.application), - null, // new DummyPassphraseCache(null, null), - data, out); + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + null, mStaticRing1.getMasterKeyId(), null); DecryptVerifyResult result = b.build().execute(); Assert.assertFalse("decryption with no passphrase must return pending", result.success()); Assert.assertTrue("decryption with no passphrase should return pending", result.isPending()); Assert.assertEquals("decryption with no passphrase should return pending passphrase", DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, result.getResult()); - }*/ + } } - static class DummyPassphraseCache implements PassphraseCacheInterface { + @Test + public void testMultiAsymmetricEncryptDecrypt() { - String mPassphrase; - Long mExpectedId; - public DummyPassphraseCache(String passphrase, Long expectedId) { - mPassphrase = passphrase; - mExpectedId = expectedId; + String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true); + byte[] ciphertext; + + { // encrypt data with a given passphrase + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes()); + + InputData data = new InputData(in, in.available()); + Builder b = new PgpSignEncrypt.Builder( + Robolectric.application, + new ProviderHelper(Robolectric.application), + null, // new DummyPassphraseCache(mPassphrase, 0L), + data, out); + + b.setEncryptionMasterKeyIds(new long[] { + mStaticRing1.getMasterKeyId(), + mStaticRing2.getMasterKeyId() + }); + b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); + SignEncryptResult result = b.build().execute(); + Assert.assertTrue("encryption must succeed", result.success()); + + ciphertext = out.toByteArray(); } - @Override - public String getCachedPassphrase(long masterKeyId, long subKeyId) throws NoSecretKeyException { - if (mExpectedId != null){ - Assert.assertEquals("requested passphrase must be for expected id", - (long) mExpectedId, subKeyId); + { // decryption with passphrase cached should succeed for the first key + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); + + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success()); + Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", + out.toByteArray(), plaintext.getBytes()); + Assert.assertNull("signature should be empty", result.getSignatureResult()); + } + + { // decryption with passphrase cached should succeed for the first key + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + // allow only the second to decrypt + HashSet allowed = new HashSet(); + allowed.add(mStaticRing2.getMasterKeyId()); + + // provide passphrase for the second, and check that the first is never asked for! + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase2, mStaticRing2.getMasterKeyId(), null); + b.setAllowedKeyIds(allowed); + + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success()); + Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", + out.toByteArray(), plaintext.getBytes()); + Assert.assertNull("signature should be empty", result.getSignatureResult()); + } + + { // decryption with passphrase cached should succeed for the other key if first is gone + + // delete first key from database + new ProviderHelper(Robolectric.application).getContentResolver().delete( + KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase2, mStaticRing2.getMasterKeyId(), null); + + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with cached passphrase must succeed", result.success()); + Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", + out.toByteArray(), plaintext.getBytes()); + Assert.assertNull("signature should be empty", result.getSignatureResult()); + } + + } + + private PgpDecryptVerify.Builder builderWithFakePassphraseCache ( + InputData data, OutputStream out, + final String passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) { + + return new PgpDecryptVerify.Builder(Robolectric.application, + new ProviderHelper(Robolectric.application), + null, + data, out) { + public PgpDecryptVerify build() { + return new PgpDecryptVerify(this) { + @Override + public String getCachedPassphrase(long masterKeyId, long subKeyId) + throws NoSecretKeyException { + if (checkMasterKeyId != null) { + Assert.assertEquals("requested passphrase should be for expected master key id", + (long) checkMasterKeyId, masterKeyId); + } + if (checkSubKeyId != null) { + Assert.assertEquals("requested passphrase should be for expected sub key id", + (long) checkSubKeyId, subKeyId); + } + if (passphrase == null) { + return null; + } + return passphrase; + } + }; } - return mPassphrase; - } - - @Override - public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException { - if (mExpectedId != null){ - Assert.assertEquals("requested passphrase must be for expected id", - (long) mExpectedId, subKeyId); - } - return mPassphrase; - } + }; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index 4f086c2a6..f5f0ce9a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -84,7 +84,7 @@ public class PgpDecryptVerify extends BaseOperation { private boolean mDecryptMetadataOnly; private byte[] mDecryptedSessionKey; - private PgpDecryptVerify(Builder builder) { + protected PgpDecryptVerify(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); // private Constructor can only be called from Builder diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java index f89027a19..3c3bcc890 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncrypt.java @@ -95,7 +95,7 @@ public class PgpSignEncrypt extends BaseOperation { } } - private PgpSignEncrypt(Builder builder) { + protected PgpSignEncrypt(Builder builder) { super(builder.mContext, builder.mProviderHelper, builder.mProgressable); // private Constructor can only be called from Builder From 9f7b2472cfdfbd48072dbcc122656945fc319650 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 1 Jan 2015 20:21:52 +0100 Subject: [PATCH 10/25] tests: be less verbose about inserting keyrings for encrypt/decrypt --- .../keychain/pgp/PgpEncryptDecryptTest.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 6a396697c..8d0c4ab80 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -28,6 +28,7 @@ import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt.Builder; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; @@ -41,7 +42,10 @@ import org.sufficientlysecure.keychain.util.TestingUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; import java.security.Security; +import java.util.HashSet; @RunWith(RobolectricTestRunner.class) @org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 @@ -53,10 +57,13 @@ public class PgpEncryptDecryptTest { static String mKeyPhrase1 = TestingUtils.genPassphrase(true); static String mKeyPhrase2 = TestingUtils.genPassphrase(true); + static PrintStream oldShadowStream; + @BeforeClass public static void setUpOnce() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); - ShadowLog.stream = System.out; + oldShadowStream = ShadowLog.stream; + // ShadowLog.stream = System.out; PgpKeyOperation op = new PgpKeyOperation(null); @@ -102,8 +109,14 @@ public class PgpEncryptDecryptTest { public void setUp() { ProviderHelper providerHelper = new ProviderHelper(Robolectric.application); + // don't log verbosely here, we're not here to test imports + ShadowLog.stream = oldShadowStream; + providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler()); - providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler()); + providerHelper.saveSecretKeyRing(mStaticRing2, new ProgressScaler()); + + // ok NOW log verbosely! + ShadowLog.stream = System.out; } @Test From 2d38079574381ddafc47bd1f31dc6e35a2d67b6a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 1 Jan 2015 20:46:39 +0100 Subject: [PATCH 11/25] add empty notation data packet only if necessary --- .../keychain/pgp/PgpKeyOperation.java | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) 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 c8ce349f8..e39bde6b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -919,22 +919,25 @@ public class PgpKeyOperation { if (newUnlock.mNewPassphrase != null) { sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent); - // add packet with EMPTY notation data (updates old one, but will be stripped later) - PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( - masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) - .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); + // if there is any old packet with notation data + if (hasNotationData(sKR)) { + // add packet with EMPTY notation data (updates old one, but will be stripped later) + PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( + masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) + .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); - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); - sKR = PGPSecretKeyRing.insertSecretKey(sKR, - PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, + PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey)); + } return sKR; } @@ -942,7 +945,7 @@ public class PgpKeyOperation { if (newUnlock.mNewPin != null) { sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); - // add packet with EMPTY notation data (updates old one, but will be stripped later) + // add packet with "pin" notation data PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); @@ -967,6 +970,22 @@ public class PgpKeyOperation { } + /** This method returns true iff the provided keyring has a local direct key signature + * with notation data. + */ + private static boolean hasNotationData(PGPSecretKeyRing sKR) { + // noinspection unchecked + Iterator sigs = sKR.getPublicKey().getKeySignatures(); + while (sigs.hasNext()) { + WrappedSignature sig = new WrappedSignature(sigs.next()); + if (sig.getSignatureType() == PGPSignature.DIRECT_KEY + && sig.isLocal() && !sig.getNotation().isEmpty()) { + return true; + } + } + return false; + } + private static PGPSecretKeyRing applyNewPassphrase( PGPSecretKeyRing sKR, PGPPublicKey masterPublicKey, From 1c1ae769ef78a9bd3c95f97b95215616ee9b15db Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 1 Jan 2015 21:13:07 +0100 Subject: [PATCH 12/25] small improvements regarding pin tests and logging --- .../keychain/pgp/PgpKeyOperationTest.java | 28 +++++++++++++++---- .../operations/results/OperationResult.java | 2 ++ .../keychain/pgp/PgpKeyOperation.java | 5 ++++ OpenKeychain/src/main/res/values/strings.xml | 4 ++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 6b53a93ce..103e2dc88 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -916,7 +916,7 @@ public class PgpKeyOperationTest { parcel.mNewUnlock = new ChangeUnlockParcel(""); // note that canonicalization here necessarily strips the empty notation packet UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, - passphrase, true, false); + passphrase); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -929,7 +929,7 @@ public class PgpKeyOperationTest { // modify keyring, change to non-empty passphrase String otherPassphrase = TestingUtils.genPassphrase(true); parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase); - modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, "", true, false); + modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, ""); Assert.assertEquals("exactly three packets should have been modified (the secret keys)", 3, onlyB.size()); @@ -989,11 +989,15 @@ public class PgpKeyOperationTest { @Test public void testUnlockPin() throws Exception { + String pin = "5235125"; + // change passphrase to a pin type - parcel.mNewUnlock = new ChangeUnlockParcel(null, "52351"); + parcel.mNewUnlock = new ChangeUnlockParcel(null, pin); UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB); - Assert.assertEquals("exactly four packets should have been modified (the secret keys + notation packet)", + Assert.assertEquals("exactly three packets should have been added (the secret keys + notation packet)", + 3, onlyA.size()); + Assert.assertEquals("exactly four packets should have been added (the secret keys + notation packet)", 4, onlyB.size()); RawPacket dkSig = onlyB.get(1); @@ -1001,11 +1005,25 @@ public class PgpKeyOperationTest { PacketTags.SIGNATURE, dkSig.tag); // check that notation data contains pin - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( + modified.getEncoded(), false, 0); Assert.assertEquals("secret key type should be 'pin' after this", SecretKeyType.PIN, secretRing.getSecretKey().getSecretKeyType()); + // need to sleep for a sec, so the timestamp changes for notation data + Thread.sleep(1000); + + { + parcel.mNewUnlock = new ChangeUnlockParcel("phrayse", null); + applyModificationWithChecks(parcel, modified, onlyA, onlyB, pin, true, false); + + Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)", + 4, onlyA.size()); + Assert.assertEquals("exactly three packets should have been added (no more notation packet)", + 3, onlyB.size()); + } + } private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 606dd49d5..426b0827e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -461,6 +461,8 @@ public abstract class OperationResult implements Parcelable { MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig), MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing), MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master), + MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin), + MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty), MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase), MSG_MF_PASSPHRASE_KEY (LogLevel.DEBUG, R.string.msg_mf_passphrase_key), MSG_MF_PASSPHRASE_EMPTY_RETRY (LogLevel.DEBUG, R.string.msg_mf_passphrase_empty_retry), 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 e39bde6b0..5ac5f7a9a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -921,6 +921,9 @@ public class PgpKeyOperation { // if there is any old packet with notation data if (hasNotationData(sKR)) { + + log.add(LogType.MSG_MF_NOTATION_EMPTY, indent); + // add packet with EMPTY notation data (updates old one, but will be stripped later) PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) @@ -945,6 +948,8 @@ public class PgpKeyOperation { if (newUnlock.mNewPin != null) { sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent); + log.add(LogType.MSG_MF_NOTATION_PIN, indent); + // add packet with "pin" notation data PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512) diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 05ea2a99a..be409078a 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -803,7 +803,9 @@ "Internal PGP error!" "Signature exception!" "Modifying master certifications" - "Changing passphrase for keyring…" + "Adding empty notation packet" + "Adding PIN notation packet" + "Changing passphrase for keyring" "Re-encrypting subkey %s with new passphrase" "Setting new passphrase failed, trying again with empty old passphrase" "Passphrase for subkey could not be changed! (Does it have a different one from the other keys?)" From 7223abcf0cfa99a866a10483b017555fb768738f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 00:07:05 +0100 Subject: [PATCH 13/25] extend canonicalize to strip local certificates on export --- .../operations/ImportExportOperation.java | 27 +++++++--- .../operations/results/OperationResult.java | 14 ++--- .../keychain/pgp/UncachedKeyRing.java | 53 ++++++++++++++++--- .../src/main/res/values-de/strings.xml | 10 ++-- .../src/main/res/values-es/strings.xml | 12 ++--- .../src/main/res/values-fr/strings.xml | 12 ++--- .../src/main/res/values-it/strings.xml | 10 ++-- .../src/main/res/values-ja/strings.xml | 12 ++--- .../src/main/res/values-sl/strings.xml | 10 ++-- .../src/main/res/values-sr/strings.xml | 12 ++--- .../src/main/res/values-sv/strings.xml | 8 +-- OpenKeychain/src/main/res/values/strings.xml | 14 ++--- 12 files changed, 123 insertions(+), 71 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java index da532d2dc..6ca28142f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java @@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.Progressable; @@ -399,7 +400,7 @@ public class ImportExportOperation extends BaseOperation { } - private ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, + ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) { /* TODO isn't this checked above, with the isStorageMounted call? @@ -469,12 +470,16 @@ public class ImportExportOperation extends BaseOperation { log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId)); - { // export public key part - byte[] data = cursor.getBlob(1); - arOutStream.write(data); + byte[] data = cursor.getBlob(1); + CanonicalizedKeyRing ring = + UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); - okPublic += 1; - } + okPublic += 1; + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + updateProgress(progress++, numKeys); + continue; } finally { // make sure this is closed if (arOutStream != null) { @@ -491,12 +496,18 @@ public class ImportExportOperation extends BaseOperation { arOutStream.setHeader("Version", version); } - // export secret key part + // export secret key part log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId)); byte[] data = cursor.getBlob(2); - arOutStream.write(data); + CanonicalizedKeyRing ring = + UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true); + ring.encode(arOutStream); okSecret += 1; + } catch (PgpGeneralException e) { + log.add(LogType.MSG_EXPORT_ERROR_KEY, 2); + updateProgress(progress++, numKeys); + continue; } finally { // make sure this is closed if (arOutStream != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 426b0827e..bd73a9609 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -371,12 +371,13 @@ public abstract class OperationResult implements Parcelable { MSG_KC_ERROR_MASTER_ALGO (LogLevel.ERROR, R.string.msg_kc_error_master_algo), MSG_KC_ERROR_DUP_KEY (LogLevel.ERROR, R.string.msg_kc_error_dup_key), MSG_KC_MASTER (LogLevel.DEBUG, R.string.msg_kc_master), - MSG_KC_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_bad_type), - MSG_KC_REVOKE_BAD_ERR (LogLevel.WARN, R.string.msg_kc_revoke_bad_err), - MSG_KC_REVOKE_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_revoke_bad_local), - MSG_KC_REVOKE_BAD_TIME (LogLevel.WARN, R.string.msg_kc_revoke_bad_time), - MSG_KC_REVOKE_BAD_TYPE_UID (LogLevel.WARN, R.string.msg_kc_revoke_bad_type_uid), - MSG_KC_REVOKE_BAD (LogLevel.WARN, R.string.msg_kc_revoke_bad), + MSG_KC_MASTER_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_master_bad_type), + MSG_KC_MASTER_BAD_LOCAL(LogLevel.WARN, R.string.msg_kc_master_bad_local), + MSG_KC_MASTER_BAD_ERR(LogLevel.WARN, R.string.msg_kc_master_bad_err), + MSG_KC_MASTER_BAD_TIME(LogLevel.WARN, R.string.msg_kc_master_bad_time), + MSG_KC_MASTER_BAD_TYPE_UID(LogLevel.WARN, R.string.msg_kc_master_bad_type_uid), + MSG_KC_MASTER_BAD(LogLevel.WARN, R.string.msg_kc_master_bad), + MSG_KC_MASTER_LOCAL(LogLevel.WARN, R.string.msg_kc_master_local), MSG_KC_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_revoke_dup), MSG_KC_NOTATION_DUP (LogLevel.DEBUG, R.string.msg_kc_notation_dup), MSG_KC_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_kc_notation_empty), @@ -618,6 +619,7 @@ public abstract class OperationResult implements Parcelable { MSG_EXPORT_ERROR_STORAGE (LogLevel.ERROR, R.string.msg_export_error_storage), MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db), MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io), + MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key), MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success), MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success), 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 5e5a28e83..c4cd0b3e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -266,6 +266,35 @@ public class UncachedKeyRing { */ @SuppressWarnings("ConstantConditions") public CanonicalizedKeyRing canonicalize(OperationLog log, int indent) { + return canonicalize(log, indent, false); + } + + + /** "Canonicalizes" a public key, removing inconsistencies in the process. + * + * More specifically: + * - Remove all non-verifying self-certificates + * - Remove all "future" self-certificates + * - Remove all certificates flagged as "local" + * - Remove all certificates which are superseded by a newer one on the same target, + * including revocations with later re-certifications. + * - Remove all certificates in other positions if not of known type: + * - key revocation signatures on the master key + * - subkey binding signatures for subkeys + * - certifications and certification revocations for user ids + * - If a subkey retains no valid subkey binding certificate, remove it + * - If a user id retains no valid self certificate, remove it + * - If the key is a secret key, remove all certificates by foreign keys + * - If no valid user id remains, log an error and return null + * + * This operation writes an OperationLog which can be used as part of an OperationResultParcel. + * + * @param forExport if this is true, non-exportable signatures will be removed + * @return A canonicalized key, or null on fatal error (log will include a message in this case) + * + */ + @SuppressWarnings("ConstantConditions") + public CanonicalizedKeyRing canonicalize(OperationLog log, int indent, boolean forExport) { log.add(isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC, indent, KeyFormattingUtils.convertKeyIdToHex(getMasterKeyId())); @@ -311,7 +340,7 @@ public class UncachedKeyRing { || type == PGPSignature.CASUAL_CERTIFICATION || type == PGPSignature.POSITIVE_CERTIFICATION || type == PGPSignature.CERTIFICATION_REVOCATION) { - log.add(LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE_UID, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -320,7 +349,7 @@ public class UncachedKeyRing { if (type != PGPSignature.KEY_REVOCATION && type != PGPSignature.DIRECT_KEY) { // Unknown type, just remove - log.add(LogType.MSG_KC_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent, "0x" + Integer.toString(type, 16)); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -328,7 +357,7 @@ public class UncachedKeyRing { if (cert.getCreationTime().after(nowPlusOneDay)) { // Creation date in the future? No way! - log.add(LogType.MSG_KC_REVOKE_BAD_TIME, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TIME, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -337,23 +366,31 @@ public class UncachedKeyRing { try { cert.init(masterKey); if (!cert.verifySignature(masterKey)) { - log.add(LogType.MSG_KC_REVOKE_BAD, indent); + log.add(LogType.MSG_KC_MASTER_BAD, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } } catch (PgpGeneralException e) { - log.add(LogType.MSG_KC_REVOKE_BAD_ERR, indent); + log.add(LogType.MSG_KC_MASTER_BAD_ERR, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; } - // special case: direct key signatures! + // if this is for export, we always remove any non-exportable certs + if (forExport && cert.isLocal()) { + // Remove revocation certs with "local" flag + log.add(LogType.MSG_KC_MASTER_LOCAL, indent); + modified = PGPPublicKey.removeCertification(modified, zert); + continue; + } + + // special case: non-exportable, direct key signatures for notations! if (cert.getSignatureType() == PGPSignature.DIRECT_KEY) { // must be local, otherwise strip! if (!cert.isLocal()) { - log.add(LogType.MSG_KC_BAD_TYPE, indent); + log.add(LogType.MSG_KC_MASTER_BAD_TYPE, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; @@ -376,7 +413,7 @@ public class UncachedKeyRing { continue; } else if (cert.isLocal()) { // Remove revocation certs with "local" flag - log.add(LogType.MSG_KC_REVOKE_BAD_LOCAL, indent); + log.add(LogType.MSG_KC_MASTER_BAD_LOCAL, indent); modified = PGPPublicKey.removeCertification(modified, zert); badCerts += 1; continue; diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index fa74b287b..52d979983 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -604,11 +604,11 @@ Schlüsselbund hat keine gültigen Benutzerkennungen! Der Hauptschlüssel verwendet einen unbekannten (%s) Algorithmus! Verarbeite Hauptschlüssel - Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat - Entferne Schlüsselbund Widerrufszertifikat mit \"Lokal\" Attribut - Entferne Schlüsselbund Widerrufszertifikat mit zukünftigem Zeitstempel - Entferne Hauptschlüsselbeglaubigung unbekannter Art (%s) - Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat + Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat + Entferne Schlüsselbund Widerrufszertifikat mit \"Lokal\" Attribut + Entferne Schlüsselbund Widerrufszertifikat mit zukünftigem Zeitstempel + Entferne Hauptschlüsselbeglaubigung unbekannter Art (%s) + Entferne fehlerhaftes Schlüsselbund Widerrufszertifikat Entferne redundantes Schlüsselbund Widerrufszertifikat Verarbeite Unterschlüssel %s Entferne ungültige Unterschlüssel Zwischenbeglaubigung diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index afae20592..bf532a2aa 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -649,12 +649,12 @@ ¡La clave maestra usa un algoritmo (%s) desconocido! La subclave %s aparece dos veces en el juego de claves (keyring). El juego de claves está mal formado, ¡no se va a importar! Procesando clave maestra - Eliminando certificado defectuoso de revocación de juego de claves - Eliminando certificado de revocación de juego de claves, con distintivo \"local\" - Eliminando certificado de revocación de juego de claves, con marca de tiempo futura - Eliminando certificado de clave maestra, de tipo desconocido (%s) - Eliminando certificado de identificación de usuario en posición incorrecta - Eliminando certificado defectuoso de revocación de juego de claves + Eliminando certificado defectuoso de revocación de juego de claves + Eliminando certificado de revocación de juego de claves, con distintivo \"local\" + Eliminando certificado de revocación de juego de claves, con marca de tiempo futura + Eliminando certificado de clave maestra, de tipo desconocido (%s) + Eliminando certificado de identificación de usuario en posición incorrecta + Eliminando certificado defectuoso de revocación de juego de claves Eliminando certificado redundante de revocación de juego de claves Procesando subclave %s Eliminando certificado no válido de vinculación de subclave diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index 60b7a3bcf..b252dc9e0 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -649,12 +649,12 @@ La clef maîtresse utilise un algorithme (%s) inconnu ! La sous-clef %s se présente deux fois dans le trousseau. Le trousseau est mal formé, pas d\'importation ! Traitement de la clef maîtresse - Suppression du mauvais certificat de révocation du trousseau - Suppression du certificat de révocation du trousseau ayant le drapeau « local » - Suppression du certificat de révocation du trousseau ayant une estampille temporelle dans le futur - Suppression du certificat de clef maîtresse de type inconnu (%s) - Suppression du certificat de l\'ID d\'utilisateur en mauvaise position - Suppression du mauvais certificat de révocation du trousseau + Suppression du mauvais certificat de révocation du trousseau + Suppression du certificat de révocation du trousseau ayant le drapeau « local » + Suppression du certificat de révocation du trousseau ayant une estampille temporelle dans le futur + Suppression du certificat de clef maîtresse de type inconnu (%s) + Suppression du certificat de l\'ID d\'utilisateur en mauvaise position + Suppression du mauvais certificat de révocation du trousseau Suppression du certificat redondant de révocation du trousseau Traitement de la sous-clef %s Suppression du certificat invalide de liaison de la sous-clef diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index 6a33e25f1..f5a6696ae 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -599,11 +599,11 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars Canonicalizzazione portachiavi segreto %s Questa è una chiave OpenPGP versione 3, è deprecata e non più supportata! Elaborazione chiave principale - Rimozione di certificato di revoca del portachiavi corrotto - Rimozione certificato di revoca del portachiavi con caratteristica \"locale\" - Rimozione certificato di revoca del portachiavi con marca temporale futura - Rimozione certificato della chiave principale di tipo sconosciuto (%s) - Rimozione certificato di revoca del portachiavi corrotto + Rimozione di certificato di revoca del portachiavi corrotto + Rimozione certificato di revoca del portachiavi con caratteristica \"locale\" + Rimozione certificato di revoca del portachiavi con marca temporale futura + Rimozione certificato della chiave principale di tipo sconosciuto (%s) + Rimozione certificato di revoca del portachiavi corrotto Rimozione certificato di revoca del portachiavi ridondante Elaborazione sottochiave %s Rimozione certificato vincolante di sottochiave non valido diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index 5ed93c931..815a77431 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -615,12 +615,12 @@ 主鍵で不明なアルゴリズム(%s)を利用しています! 鍵輪の中に副鍵 %s が2度出現しました。鍵輪が不整形となっており、インポートできあせん! 主鍵処理中 - 問題のある鍵輪の破棄証明を破棄中 - 鍵輪のローカルフラグ付き破棄証明を破棄中 - 鍵輪の未来にタイムスタンプがある破棄証明を破棄中 - 問題のある主鍵の不明な型 (%s) の証明を破棄中 - 不正な位置のユーザID検証を破棄中 - 問題のある鍵輪の破棄証明を破棄中 + 問題のある鍵輪の破棄証明を破棄中 + 鍵輪のローカルフラグ付き破棄証明を破棄中 + 鍵輪の未来にタイムスタンプがある破棄証明を破棄中 + 問題のある主鍵の不明な型 (%s) の証明を破棄中 + 不正な位置のユーザID検証を破棄中 + 問題のある鍵輪の破棄証明を破棄中 重複している鍵輪の破棄証明を破棄中 副鍵 %s の処理中 証明が付随する不正な副鍵を破棄中 diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index 1841ee46b..6eeb7bd72 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -447,11 +447,11 @@ Zasebna zbirka ključev uspešno uvožena Obdelujem glavni ključ... - Umikam slab certifikat za preklic zbirk ključev - Umikam certifikat za preklic zbirk ključev z oznako \"lokalno\" - Umikam certifikat za preklic zbirk ključev s časovno znamko v prihodnosti - Umikam certifikat glavnega ključa neznanega tipa (%s) - Umikam slab certifikat za preklic zbirk ključev + Umikam slab certifikat za preklic zbirk ključev + Umikam certifikat za preklic zbirk ključev z oznako \"lokalno\" + Umikam certifikat za preklic zbirk ključev s časovno znamko v prihodnosti + Umikam certifikat glavnega ključa neznanega tipa (%s) + Umikam slab certifikat za preklic zbirk ključev Umikam odvečen certifikat za preklic zbirk ključev Obdelujem podključ %s Umikam neveljaven certifikat za povezovanje podključev diff --git a/OpenKeychain/src/main/res/values-sr/strings.xml b/OpenKeychain/src/main/res/values-sr/strings.xml index 5ba833972..b4955b9eb 100644 --- a/OpenKeychain/src/main/res/values-sr/strings.xml +++ b/OpenKeychain/src/main/res/values-sr/strings.xml @@ -669,12 +669,12 @@ Главни кључ користи непознат алгоритам (%s)! Поткључ %s се појављује два пута у привеску. Привезак је деформисан, не увозим! Обрађујем главни кључ - Уклањам лош сертификат опозива привеска - Уклањам сертификат опозива привеска са заставицом „локални“ - Уклањам сертификат опозива привеска са временском ознаком у будућности - Уклањам сертификат главног кључа непознатог типа (%s) - Уклањам сертификат корисничког ИД-а са погрешног места - Уклањам лош сертификат опозива привеска + Уклањам лош сертификат опозива привеска + Уклањам сертификат опозива привеска са заставицом „локални“ + Уклањам сертификат опозива привеска са временском ознаком у будућности + Уклањам сертификат главног кључа непознатог типа (%s) + Уклањам сертификат корисничког ИД-а са погрешног места + Уклањам лош сертификат опозива привеска Уклањам сувишни сертификат опозива привеска Обрађујем поткључ %s Уклањам неисправан повезујући сертификат поткључа diff --git a/OpenKeychain/src/main/res/values-sv/strings.xml b/OpenKeychain/src/main/res/values-sv/strings.xml index fda0ace11..7d414d3f0 100644 --- a/OpenKeychain/src/main/res/values-sv/strings.xml +++ b/OpenKeychain/src/main/res/values-sv/strings.xml @@ -571,10 +571,10 @@ Den här nyckeln är skapad med OpenPGP version 3, vilken är en föråldrad version som inte längre stöds! Den här huvudnyckeln använder en okänd (%s) algoritm! Bearbetar huvudnyckel - Tar bort dåligt återkallelsecertifikat för nyckelring - Tar bort återkallelsecertifikat för nyckelring med framtida tidstämpel - Tar bort huvudnyckelcertifikat av okänd typ (%s) - Tar bort dåligt återkallelsecertifikat för nyckelring + Tar bort dåligt återkallelsecertifikat för nyckelring + Tar bort återkallelsecertifikat för nyckelring med framtida tidstämpel + Tar bort huvudnyckelcertifikat av okänd typ (%s) + Tar bort dåligt återkallelsecertifikat för nyckelring Tar bort överflödigt återkallelsecertifikat för nyckelring Bearbetar undernyckel %s Inget giltigt certifikat hittades för %s, tar bort från nyckelring diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index be409078a..8788038aa 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -711,12 +711,13 @@ "The master key uses an unknown (%s) algorithm!" "Subkey %s occurs twice in keyring. Keyring is malformed, not importing!" "Processing master key" - "Removing master key certificate of unknown type (%s)" - "Removing bad keyring revocation certificate" - "Removing keyring revocation certificate with "local" flag" - "Removing keyring revocation certificate with future timestamp" - "Removing user ID certificate in bad position" - "Removing bad keyring revocation certificate" + "Removing master key certificate of unknown type (%s)" + "Removing master key certificate with "local" flag" + "Removing bad master key certificate" + "Removing keyring revocation certificate with future timestamp" + "Removing user ID certificate in bad position" + "Removing bad master key certificate" + "Removing master key certificate with "local" flag" "Removing redundant keyring revocation certificate" "Removing redundant notation certificate" "Removing empty notation certificate" @@ -980,6 +981,7 @@ "Storage is not ready for writing!" "Database error!" "Input/output error!" + "Error preprocessing key data!" "Export operation successful" "Nothing to delete!" From 57e3266fa5dfeab605ba327c7daaccfd57b87d12 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 00:15:08 +0100 Subject: [PATCH 14/25] tests: add test for key export --- .../keychain/operations/ExportTest.java | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java new file mode 100644 index 000000000..110fec2d3 --- /dev/null +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser + * + * 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 . + */ + +package org.sufficientlysecure.keychain.operations; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLog; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.util.ProgressScaler; +import org.sufficientlysecure.keychain.util.TestingUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.security.Security; +import java.util.Iterator; + +@RunWith(RobolectricTestRunner.class) +@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 +public class ExportTest { + + static String mPassphrase = TestingUtils.genPassphrase(true); + + static UncachedKeyRing mStaticRing1, mStaticRing2; + static String mKeyPhrase1 = TestingUtils.genPassphrase(true); + static String mKeyPhrase2 = TestingUtils.genPassphrase(true); + + static PrintStream oldShadowStream; + + @BeforeClass + public static void setUpOnce() throws Exception { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + oldShadowStream = ShadowLog.stream; + // ShadowLog.stream = System.out; + + PgpKeyOperation op = new PgpKeyOperation(null); + + { + SaveKeyringParcel parcel = new SaveKeyringParcel(); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); + parcel.mAddUserIds.add("snips"); + parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1); + + EditKeyResult result = op.createSecretKeyRing(parcel); + Assert.assertTrue("initial test key creation must succeed", result.success()); + Assert.assertNotNull("initial test key creation must succeed", result.getRing()); + + mStaticRing1 = result.getRing(); + } + + { + SaveKeyringParcel parcel = new SaveKeyringParcel(); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); + parcel.mAddUserIds.add("snails"); + parcel.mNewUnlock = new ChangeUnlockParcel(null, "1234"); + + EditKeyResult result = op.createSecretKeyRing(parcel); + Assert.assertTrue("initial test key creation must succeed", result.success()); + Assert.assertNotNull("initial test key creation must succeed", result.getRing()); + + mStaticRing2 = result.getRing(); + } + + } + + @Before + public void setUp() { + ProviderHelper providerHelper = new ProviderHelper(Robolectric.application); + + // don't log verbosely here, we're not here to test imports + ShadowLog.stream = oldShadowStream; + + providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler()); + providerHelper.saveSecretKeyRing(mStaticRing2, new ProgressScaler()); + + // ok NOW log verbosely! + ShadowLog.stream = System.out; + } + + @Test + public void testExportAllPublic() throws Exception { + ImportExportOperation op = new ImportExportOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ExportResult result = op.exportKeyRings(new OperationLog(), null, false, out); + + Assert.assertTrue("export must be a success", result.success()); + + long masterKeyId1, masterKeyId2; + if (mStaticRing1.getMasterKeyId() < mStaticRing2.getMasterKeyId()) { + masterKeyId1 = mStaticRing1.getMasterKeyId(); + masterKeyId2 = mStaticRing2.getMasterKeyId(); + } else { + masterKeyId2 = mStaticRing1.getMasterKeyId(); + masterKeyId1 = mStaticRing2.getMasterKeyId(); + } + + Iterator unc = + UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray())); + + { + Assert.assertTrue("export must have two keys (1/2)", unc.hasNext()); + UncachedKeyRing ring = unc.next(); + Assert.assertEquals("first exported key has correct masterkeyid", + masterKeyId1, ring.getMasterKeyId()); + Assert.assertFalse("first exported key must not be secret", ring.isSecret()); + checkForLocal(ring); + } + + { + Assert.assertTrue("export must have two keys (2/2)", unc.hasNext()); + UncachedKeyRing ring = unc.next(); + Assert.assertEquals("second exported key has correct masterkeyid", + masterKeyId2, ring.getMasterKeyId()); + Assert.assertFalse("second exported key must not be secret", ring.isSecret()); + checkForLocal(ring); + } + + out = new ByteArrayOutputStream(); + result = op.exportKeyRings(new OperationLog(), null, true, out); + + Assert.assertTrue("export must be a success", result.success()); + + unc = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray())); + + { + Assert.assertTrue("export must have four keys (1/4)", unc.hasNext()); + UncachedKeyRing ring = unc.next(); + Assert.assertEquals("1/4 exported key has correct masterkeyid", + masterKeyId1, ring.getMasterKeyId()); + Assert.assertFalse("1/4 exported key must not be public", ring.isSecret()); + checkForLocal(ring); + + Assert.assertTrue("export must have four keys (2/4)", unc.hasNext()); + ring = unc.next(); + Assert.assertEquals("2/4 exported key has correct masterkeyid", + masterKeyId1, ring.getMasterKeyId()); + Assert.assertTrue("2/4 exported key must be public", ring.isSecret()); + checkForLocal(ring); + } + + { + Assert.assertTrue("export must have four keys (3/4)", unc.hasNext()); + UncachedKeyRing ring = unc.next(); + Assert.assertEquals("3/4 exported key has correct masterkeyid", + masterKeyId2, ring.getMasterKeyId()); + Assert.assertFalse("3/4 exported key must not be public", ring.isSecret()); + checkForLocal(ring); + + Assert.assertTrue("export must have four keys (4/4)", unc.hasNext()); + ring = unc.next(); + Assert.assertEquals("4/4 exported key has correct masterkeyid", + masterKeyId2, ring.getMasterKeyId()); + Assert.assertTrue("4/4 exported key must be public", ring.isSecret()); + checkForLocal(ring); + } + + } + + private void checkForLocal(UncachedKeyRing ring) { + Iterator sigs = ring.getPublicKey().getSignatures(); + while (sigs.hasNext()) { + Assert.assertFalse("there must be no local signatures in an exported keyring", + sigs.next().isLocal()); + } + } + +} From 704fc2dd45f20c7edc68b2930e2a73179d5fbdae Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 01:11:43 +0100 Subject: [PATCH 15/25] tests: for export, check that a token local cert actually exists before export --- .../keychain/operations/ExportTest.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java index 110fec2d3..8ac23b1ee 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java @@ -120,10 +120,13 @@ public class ExportTest { } @Test - public void testExportAllPublic() throws Exception { + public void testExportAll() throws Exception { ImportExportOperation op = new ImportExportOperation(Robolectric.application, new ProviderHelper(Robolectric.application), null); + // make sure there is a local cert (so the later checks that there are none are meaningful) + Assert.assertTrue("second keyring has local certification", checkForLocal(mStaticRing2)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); ExportResult result = op.exportKeyRings(new OperationLog(), null, false, out); @@ -147,7 +150,8 @@ public class ExportTest { Assert.assertEquals("first exported key has correct masterkeyid", masterKeyId1, ring.getMasterKeyId()); Assert.assertFalse("first exported key must not be secret", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); } { @@ -156,7 +160,8 @@ public class ExportTest { Assert.assertEquals("second exported key has correct masterkeyid", masterKeyId2, ring.getMasterKeyId()); Assert.assertFalse("second exported key must not be secret", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); } out = new ByteArrayOutputStream(); @@ -172,14 +177,16 @@ public class ExportTest { Assert.assertEquals("1/4 exported key has correct masterkeyid", masterKeyId1, ring.getMasterKeyId()); Assert.assertFalse("1/4 exported key must not be public", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); Assert.assertTrue("export must have four keys (2/4)", unc.hasNext()); ring = unc.next(); Assert.assertEquals("2/4 exported key has correct masterkeyid", masterKeyId1, ring.getMasterKeyId()); Assert.assertTrue("2/4 exported key must be public", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); } { @@ -188,24 +195,29 @@ public class ExportTest { Assert.assertEquals("3/4 exported key has correct masterkeyid", masterKeyId2, ring.getMasterKeyId()); Assert.assertFalse("3/4 exported key must not be public", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); Assert.assertTrue("export must have four keys (4/4)", unc.hasNext()); ring = unc.next(); Assert.assertEquals("4/4 exported key has correct masterkeyid", masterKeyId2, ring.getMasterKeyId()); Assert.assertTrue("4/4 exported key must be public", ring.isSecret()); - checkForLocal(ring); + Assert.assertFalse("there must be no local signatures in an exported keyring", + checkForLocal(ring)); } } - private void checkForLocal(UncachedKeyRing ring) { + /** This function checks whether or not there are any local signatures in a keyring. */ + private boolean checkForLocal(UncachedKeyRing ring) { Iterator sigs = ring.getPublicKey().getSignatures(); while (sigs.hasNext()) { - Assert.assertFalse("there must be no local signatures in an exported keyring", - sigs.next().isLocal()); + if (sigs.next().isLocal()) { + return true; + } } + return false; } } From b52fb903803c2dfca578c140dfdc776927c6fc4b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 01:57:49 +0100 Subject: [PATCH 16/25] fix and test for bad certificate version numbers (#1012) closes #1012 --- .../keychain/pgp/UncachedKeyringTest.java | 17 ++++++++++++++ .../test-keys/broken_cert_version.asc | 17 ++++++++++++++ .../keychain/pgp/UncachedKeyRing.java | 22 ++++++++++++------- extern/spongycastle | 2 +- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 OpenKeychain-Test/src/test/resources/test-keys/broken_cert_version.asc diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java index 34c637abf..89fefb767 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java @@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockPar import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Iterator; @RunWith(RobolectricTestRunner.class) @@ -129,4 +130,20 @@ public class UncachedKeyringTest { pubRing.extractPublicKeyRing(); } + @Test(expected = IOException.class) + public void testBrokenVersionCert() throws Throwable { + // this is a test for one of the patches we use on top of stock bouncycastle, which + // returns an IOException rather than a RuntimeException in case of a bad certificate + // version byte + readRingFromResource("/test-keys/broken_cert_version.asc"); + } + + UncachedKeyRing readRingFromResource(String name) throws Throwable { + try { + return UncachedKeyRing.fromStream(UncachedKeyringTest.class.getResourceAsStream(name)).next(); + } catch (RuntimeException e) { + throw e.getCause(); + } + } + } diff --git a/OpenKeychain-Test/src/test/resources/test-keys/broken_cert_version.asc b/OpenKeychain-Test/src/test/resources/test-keys/broken_cert_version.asc new file mode 100644 index 000000000..e2d2abd8e --- /dev/null +++ b/OpenKeychain-Test/src/test/resources/test-keys/broken_cert_version.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFSl5DIBCADqsGJJ8RhV4Uw6a3Q48QyTMrUtvZquOTlLVaqOdEFZNL5/OBal +prft9LNkcOkIVA89Hdn715WwkmG2OJiJoQ/ZAKwal1CPGm4Q8kZIM7k57ISJL6J5 +300e7UIznc74XbG7eFNxNcjCM9wG12vW2rFwc+ogJtkBSf0IXukPwtUkRK+H5ufO +lpqS5NNZfiGbNQCrb+YsGZNRk4QTGR6WGyaIRHlcG8G00VPGNSauTqe/11MO9MoF +BvPgFeur3nefWunCQ+uDmzIEs8r94gaHu3LWbctd5w5x/o/PDfTSSiO+U8zzXrKC +4ZpEl5bk7t7jH1hYMLWyO6nn0vWTOMO1EYLBABEBAAG0GGJyb2tlbiBzaWduYXR1 +cmUgdmVyc2lvbokBOMATAQIAIgUCVKXkMgIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC +HgECF4AACgkQDe00lH/2SnprLggAh64TsdHDfIhTNc1DeJLCuvuHsitAcUdEEnue +yJjodxboKNSplIwnmb5CpM3P8f736dNaW77Yd6aO4IeAy6cBlxT1tSRkJMsp+cBt +kBa3lRr+GnWZlLZs3coL2g0t5RbuyYKyQxm2qvgFJGi/7Qfty5nJOW5U1ElT3VT8 +jISNdQdDAIaBsCE+TuyW3VsP3PqnJ7x14K7VhkFuCyvYB9paLcJBnan93R0Ja0Ip +Cv1pbrNxXp0UELf0RYc2X5C1m6otZ9LKf3PmzxlEkApkb1TZUEBak2Za5p99koZT ++pg/XpZPyawi+gZeYkBAohxRGmzG/a4L+YacAZHbchfN0eG7lg== +=mxTR +-----END PGP PUBLIC KEY BLOCK----- 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 c4cd0b3e5..006af6073 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -143,17 +143,22 @@ public class UncachedKeyRing { throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); } - UncachedKeyRing ring = parsed.next(); + try { + UncachedKeyRing ring = parsed.next(); - if (parsed.hasNext()) { - throw new PgpGeneralException("Expected single keyring in stream, found at least two"); + if (parsed.hasNext()) { + throw new PgpGeneralException("Expected single keyring in stream, found at least two"); + } + + return ring; + + } catch (RuntimeException e) { + // yes this is bad style. we should rework this in a better way + throw new PgpGeneralException(e.getCause()); } - - return ring; - } - public static Iterator fromStream(final InputStream stream) throws IOException { + public static Iterator fromStream(final InputStream stream) { return new Iterator() { @@ -190,7 +195,8 @@ public class UncachedKeyRing { mObjectFactory = null; } } catch (IOException e) { - Log.e(Constants.TAG, "IOException while processing stream. ArmoredInputStream CRC check failed?", e); + throw new RuntimeException(e); + // Log.e(Constants.TAG, "IOException while processing stream. ArmoredInputStream CRC check failed?", e); } catch (ArrayIndexOutOfBoundsException e) { Log.e(Constants.TAG, "ArmoredInputStream decode failed, symbol is not in decodingTable!", e); } diff --git a/extern/spongycastle b/extern/spongycastle index 375084d55..1d9ee197d 160000 --- a/extern/spongycastle +++ b/extern/spongycastle @@ -1 +1 @@ -Subproject commit 375084d55341b575274e49d9a69fa4cf9356682a +Subproject commit 1d9ee197d8fcc18dcdd1ae9649a5dc53e910b18c From 56f2a3137bbe56fee328bfbece625cf4a2f9e51d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 02:05:12 +0100 Subject: [PATCH 17/25] get rid of RuntimeException control flow in UncachedKeyRing.fromStream --- .../keychain/operations/ExportTest.java | 3 +- .../keychain/pgp/UncachedKeyringTest.java | 9 ++-- .../keychain/pgp/UncachedKeyRing.java | 44 ++++++++----------- .../ui/adapter/ImportKeysListLoader.java | 3 +- 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java index 8ac23b1ee..4470ef411 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java @@ -33,6 +33,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; @@ -141,7 +142,7 @@ public class ExportTest { masterKeyId1 = mStaticRing2.getMasterKeyId(); } - Iterator unc = + IteratorWithIOThrow unc = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray())); { diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java index 89fefb767..e5c00221b 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java @@ -27,6 +27,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowLog; import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; @@ -110,7 +111,7 @@ public class UncachedKeyringTest { ring.encodeArmored(out, "OpenKeychain"); pubRing.encodeArmored(out, "OpenKeychain"); - Iterator it = + IteratorWithIOThrow it = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray())); Assert.assertTrue("there should be two rings in the stream", it.hasNext()); Assert.assertArrayEquals("first ring should be the first we put in", @@ -139,11 +140,7 @@ public class UncachedKeyringTest { } UncachedKeyRing readRingFromResource(String name) throws Throwable { - try { - return UncachedKeyRing.fromStream(UncachedKeyringTest.class.getResourceAsStream(name)).next(); - } catch (RuntimeException e) { - throw e.getCause(); - } + return UncachedKeyRing.fromStream(UncachedKeyringTest.class.getResourceAsStream(name)).next(); } } 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 006af6073..a445e161f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -137,35 +137,30 @@ public class UncachedKeyRing { public static UncachedKeyRing decodeFromData(byte[] data) throws PgpGeneralException, IOException { - Iterator parsed = fromStream(new ByteArrayInputStream(data)); + IteratorWithIOThrow parsed = fromStream(new ByteArrayInputStream(data)); if ( ! parsed.hasNext()) { throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); } - try { - UncachedKeyRing ring = parsed.next(); + UncachedKeyRing ring = parsed.next(); - if (parsed.hasNext()) { - throw new PgpGeneralException("Expected single keyring in stream, found at least two"); - } - - return ring; - - } catch (RuntimeException e) { - // yes this is bad style. we should rework this in a better way - throw new PgpGeneralException(e.getCause()); + if (parsed.hasNext()) { + throw new PgpGeneralException("Expected single keyring in stream, found at least two"); } + + return ring; + } - public static Iterator fromStream(final InputStream stream) { + public static IteratorWithIOThrow fromStream(final InputStream stream) { - return new Iterator() { + return new IteratorWithIOThrow() { UncachedKeyRing mNext = null; PGPObjectFactory mObjectFactory = null; - private void cacheNext() { + private void cacheNext() throws IOException { if (mNext != null) { return; } @@ -194,22 +189,19 @@ public class UncachedKeyRing { // if we are past the while loop, that means the objectFactory had no next mObjectFactory = null; } - } catch (IOException e) { - throw new RuntimeException(e); - // Log.e(Constants.TAG, "IOException while processing stream. ArmoredInputStream CRC check failed?", e); } catch (ArrayIndexOutOfBoundsException e) { - Log.e(Constants.TAG, "ArmoredInputStream decode failed, symbol is not in decodingTable!", e); + throw new IOException(e); } } @Override - public boolean hasNext() { + public boolean hasNext() throws IOException { cacheNext(); return mNext != null; } @Override - public UncachedKeyRing next() { + public UncachedKeyRing next() throws IOException { try { cacheNext(); return mNext; @@ -217,15 +209,15 @@ public class UncachedKeyRing { mNext = null; } } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } }; } + public interface IteratorWithIOThrow { + public boolean hasNext() throws IOException; + public E next() throws IOException; + } + public void encodeArmored(OutputStream out, String version) throws IOException { ArmoredOutputStream aos = new ArmoredOutputStream(out); if (version != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 41c7e0cbc..cecad2716 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.PositionAwareInputStream; @@ -127,7 +128,7 @@ public class ImportKeysListLoader BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); try { // parse all keyrings - Iterator it = UncachedKeyRing.fromStream(bufferedInput); + IteratorWithIOThrow it = UncachedKeyRing.fromStream(bufferedInput); while (it.hasNext()) { UncachedKeyRing ring = it.next(); ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring); From 920fbdfb427877d89d9a6bcf62b6dc9fba846c2e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 02:24:11 +0100 Subject: [PATCH 18/25] throw exception if no data is found in getGenericData --- .../sufficientlysecure/keychain/provider/ProviderHelper.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 6daa26cd3..7cb57ddfe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -192,6 +192,9 @@ public class ProviderHelper { } pos += 1; } + } else { + // If no data was found, throw an appropriate exception + throw new NotFoundException(); } return result; From 9cf800bcd8870961acc1719fc56f5e8dab21d0a3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 02:50:14 +0100 Subject: [PATCH 19/25] tests: add multiple keys with signature test case --- .../keychain/pgp/PgpEncryptDecryptTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 8d0c4ab80..d03375a61 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.openintents.openpgp.OpenPgpSignatureResult; import org.robolectric.*; import org.robolectric.shadows.ShadowLog; import org.spongycastle.bcpg.sig.KeyFlags; @@ -36,6 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.TestingUtils; @@ -363,6 +365,79 @@ public class PgpEncryptDecryptTest { } + @Test + public void testMultiAsymmetricSignEncryptDecryptVerify() { + + String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true); + byte[] ciphertext; + + { // encrypt data with a given passphrase + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes()); + + InputData data = new InputData(in, in.available()); + Builder b = new PgpSignEncrypt.Builder( + Robolectric.application, + new ProviderHelper(Robolectric.application), + null, // new DummyPassphraseCache(mPassphrase, 0L), + data, out); + + b.setEncryptionMasterKeyIds(new long[] { + mStaticRing1.getMasterKeyId(), + mStaticRing2.getMasterKeyId() + }); + b.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId()); + b.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1)); + b.setSignaturePassphrase(mKeyPhrase1); + b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); + SignEncryptResult result = b.build().execute(); + Assert.assertTrue("encryption must succeed", result.success()); + + ciphertext = out.toByteArray(); + } + + { // decryption with passphrase cached should succeed for the first key + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); + + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success()); + Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", + out.toByteArray(), plaintext.getBytes()); + Assert.assertEquals("signature should be verified and certified", + OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED, result.getSignatureResult().getStatus()); + } + + { // decryption with passphrase cached should succeed for the other key if first is gone + + // delete first key from database + new ProviderHelper(Robolectric.application).getContentResolver().delete( + KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, + mKeyPhrase2, mStaticRing2.getMasterKeyId(), null); + + DecryptVerifyResult result = b.build().execute(); + Assert.assertTrue("decryption with cached passphrase must succeed", result.success()); + Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", + out.toByteArray(), plaintext.getBytes()); + Assert.assertEquals("signature key should be missing", + OpenPgpSignatureResult.SIGNATURE_KEY_MISSING, + result.getSignatureResult().getStatus()); + } + + } + private PgpDecryptVerify.Builder builderWithFakePassphraseCache ( InputData data, OutputStream out, final String passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) { From 5057ea1744eac05fc999db1756cf0d739ae41a72 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 2 Jan 2015 14:28:03 +0100 Subject: [PATCH 20/25] deal with non-existent user ids during import (#994) --- .../keychain/keyimport/ImportKeysListEntry.java | 3 ++- .../keychain/pgp/UncachedPublicKey.java | 6 ++++-- OpenKeychain/src/main/res/values/strings.xml | 13 +++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 854a90713..d60d7757b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -287,7 +288,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable { // if there was no user id flagged as primary, use the first one if (mPrimaryUserId == null) { - mPrimaryUserId = mUserIds.get(0); + mPrimaryUserId = context.getString(R.string.user_id_none); } mKeyId = key.getKeyId(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index c4cacaca7..fe3ab96a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -186,12 +186,14 @@ public class UncachedPublicKey { } /** - * Returns primary user id if existing. If not, return first encountered user id. + * Returns primary user id if existing. If not, return first encountered user id. If there + * is no user id, return null (this can only happen for not yet canonicalized keys during import) */ public String getPrimaryUserIdWithFallback() { String userId = getPrimaryUserId(); if (userId == null) { - userId = (String) mPublicKey.getUserIDs().next(); + Iterator it = mPublicKey.getUserIDs(); + userId = it.hasNext() ? it.next() : null; } return userId; } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 8788038aa..55497361b 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1024,6 +1024,12 @@ "Clear Cache" "Passphrase" + + "Take back your privacy with OpenKeychain!" + "Create my key" + "Import from file" + "Skip Setup" + "Certifier" "Certificate Details" @@ -1054,11 +1060,6 @@ "Saving of multiple files not supported. This is a limitation on current Android." "Key:" "To start a key exchange, choose the number of participants on the right side, then hit the “Start exchange” button.\n\nYou will be asked two more questions to make sure only the right participants are in the exchange and their fingerprints are correct." - - - "Take back your privacy with OpenKeychain!" - "Create my key" - "Import from file" - "Skip Setup" + ]]> From 320f7d35efb059b99b31506426554e9a8f138d8f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 3 Jan 2015 13:55:06 +0100 Subject: [PATCH 21/25] encapsulate high level edit key into new operation class --- .../keychain/operations/ExportTest.java | 7 +- .../keychain/pgp/PgpEncryptDecryptTest.java | 6 +- .../keychain/pgp/PgpKeyOperationTest.java | 18 +-- .../pgp/UncachedKeyringCanonicalizeTest.java | 4 +- .../pgp/UncachedKeyringMergeTest.java | 6 +- .../keychain/pgp/UncachedKeyringTest.java | 4 +- .../keychain/operations/EditKeyOperation.java | 132 ++++++++++++++++++ .../operations/results/EditKeyResult.java | 20 +-- .../operations/results/OperationResult.java | 8 ++ .../operations/results/PgpEditKeyResult.java | 63 +++++++++ .../keychain/pgp/PgpKeyOperation.java | 79 ++++++----- .../service/KeychainIntentService.java | 96 ++----------- .../keychain/ui/CreateKeyFinalFragment.java | 10 +- OpenKeychain/src/main/res/values/strings.xml | 8 ++ 14 files changed, 294 insertions(+), 167 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java index 4470ef411..b6fdbfc6c 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/ExportTest.java @@ -27,10 +27,9 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowLog; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; @@ -79,7 +78,7 @@ public class ExportTest { parcel.mAddUserIds.add("snips"); parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); Assert.assertNotNull("initial test key creation must succeed", result.getRing()); @@ -97,7 +96,7 @@ public class ExportTest { parcel.mAddUserIds.add("snails"); parcel.mNewUnlock = new ChangeUnlockParcel(null, "1234"); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); Assert.assertNotNull("initial test key creation must succeed", result.getRing()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index d03375a61..008edcda4 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -28,13 +28,13 @@ import org.robolectric.shadows.ShadowLog; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.openpgp.PGPEncryptedData; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt.Builder; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; @@ -80,7 +80,7 @@ public class PgpEncryptDecryptTest { parcel.mAddUserIds.add("bloom"); parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); Assert.assertNotNull("initial test key creation must succeed", result.getRing()); @@ -98,7 +98,7 @@ public class PgpEncryptDecryptTest { parcel.mAddUserIds.add("belle"); parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase2); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); Assert.assertNotNull("initial test key creation must succeed", result.getRing()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 103e2dc88..52115a76d 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -40,7 +40,7 @@ import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; @@ -96,7 +96,7 @@ public class PgpKeyOperationTest { parcel.mNewUnlock = new ChangeUnlockParcel(passphrase); PgpKeyOperation op = new PgpKeyOperation(null); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); Assert.assertNotNull("initial test key creation must succeed", result.getRing()); @@ -962,7 +962,7 @@ public class PgpKeyOperationTest { // we should still be able to modify it (and change its passphrase) without errors PgpKeyOperation op = new PgpKeyOperation(null); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); - EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase); Assert.assertTrue("key modification must succeed", result.success()); Assert.assertFalse("log must not contain a warning", result.getLog().containsWarnings()); @@ -978,7 +978,7 @@ public class PgpKeyOperationTest { PgpKeyOperation op = new PgpKeyOperation(null); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); - EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase2); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase2); Assert.assertTrue("key modification must succeed", result.success()); Assert.assertTrue("log must contain a failed passphrase change warning", result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL)); @@ -1056,7 +1056,7 @@ public class PgpKeyOperationTest { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); PgpKeyOperation op = new PgpKeyOperation(null); - EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); Assert.assertTrue("key modification must succeed", result.success()); UncachedKeyRing rawModified = result.getRing(); Assert.assertNotNull("key modification must not return null", rawModified); @@ -1113,7 +1113,7 @@ public class PgpKeyOperationTest { private void assertFailure(String reason, SaveKeyringParcel parcel, LogType expected) { - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertFalse(reason, result.success()); Assert.assertNull(reason, result.getRing()); @@ -1127,7 +1127,7 @@ public class PgpKeyOperationTest { throws Exception { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); - EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); Assert.assertFalse(reason, result.success()); Assert.assertNull(reason, result.getRing()); @@ -1141,7 +1141,7 @@ public class PgpKeyOperationTest { throws Exception { CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); - EditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); + PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase); Assert.assertFalse(reason, result.success()); Assert.assertNull(reason, result.getRing()); @@ -1152,7 +1152,7 @@ public class PgpKeyOperationTest { private UncachedKeyRing assertCreateSuccess(String reason, SaveKeyringParcel parcel) { - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue(reason, result.success()); Assert.assertNotNull(reason, result.getRing()); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java index b3a149eec..721d1a51d 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java @@ -53,7 +53,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.spongycastle.util.Strings; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -108,7 +108,7 @@ public class UncachedKeyringCanonicalizeTest { parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); Assert.assertTrue("initial test key creation must succeed", result.success()); staticRing = result.getRing(); Assert.assertNotNull("initial test key creation must succeed", staticRing); diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index 7e2cbee0c..7f6f480d4 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -33,7 +33,7 @@ import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.util.Strings; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; @@ -103,7 +103,7 @@ public class UncachedKeyringMergeTest { OperationResult.OperationLog log = new OperationResult.OperationLog(); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); staticRingA = result.getRing(); staticRingA = staticRingA.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); } @@ -119,7 +119,7 @@ public class UncachedKeyringMergeTest { PgpKeyOperation op = new PgpKeyOperation(null); OperationResult.OperationLog log = new OperationResult.OperationLog(); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); staticRingB = result.getRing(); staticRingB = staticRingB.canonicalize(new OperationLog(), 0).getUncachedKeyRing(); } diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java index e5c00221b..a3c58a5c8 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java @@ -26,7 +26,7 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowLog; import org.spongycastle.bcpg.sig.KeyFlags; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; @@ -63,7 +63,7 @@ public class UncachedKeyringTest { parcel.mNewUnlock = new ChangeUnlockParcel(""); PgpKeyOperation op = new PgpKeyOperation(null); - EditKeyResult result = op.createSecretKeyRing(parcel); + PgpEditKeyResult result = op.createSecretKeyRing(parcel); staticRing = result.getRing(); staticPubRing = staticRing.extractPublicKeyRing(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java new file mode 100644 index 000000000..4d466593b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -0,0 +1,132 @@ +package org.sufficientlysecure.keychain.operations; + +import android.content.Context; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.ProgressScaler; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** An operation which implements a high level key edit operation. + * + * This operation provides a higher level interface to the edit and + * create key operations in PgpKeyOperation. It takes care of fetching + * and saving the key before and after the operation. + * + * @see CertifyActionsParcel + * + */ +public class EditKeyOperation extends BaseOperation { + + public EditKeyOperation(Context context, ProviderHelper providerHelper, + Progressable progressable, AtomicBoolean cancelled) { + super(context, providerHelper, progressable, cancelled); + } + + public EditKeyResult execute(SaveKeyringParcel saveParcel, String passphrase) { + + OperationLog log = new OperationLog(); + log.add(LogType.MSG_ED, 0); + + if (saveParcel == null) { + log.add(LogType.MSG_ED_ERROR_NO_PARCEL, 1); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Perform actual modification (or creation) + PgpEditKeyResult modifyResult; + { + PgpKeyOperation keyOperations = + new PgpKeyOperation(new ProgressScaler(mProgressable, 10, 60, 100), mCancelled); + + // If a key id is specified, fetch and edit + if (saveParcel.mMasterKeyId != null) { + try { + + log.add(LogType.MSG_ED_FETCHING, 1, + KeyFormattingUtils.convertKeyIdToHex(saveParcel.mMasterKeyId)); + CanonicalizedSecretKeyRing secRing = + mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); + + modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); + + } catch (NotFoundException e) { + log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + } else { + // otherwise, create new one + modifyResult = keyOperations.createSecretKeyRing(saveParcel); + } + } + + // Add the result to the log + log.add(modifyResult, 1); + + // Check if the action was cancelled + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new EditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); + } + + // If the edit operation didn't succeed, exit here + if (!modifyResult.success()) { + // error is already logged by modification + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // Cannot cancel from here on out! + mProgressable.setPreventCancel(); + + // It's a success, so this must be non-null now + UncachedKeyRing ring = modifyResult.getRing(); + + // Save the new keyring. + SaveKeyringResult saveResult = mProviderHelper + .saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 60, 95, 100)); + log.add(saveResult, 1); + + // If the save operation didn't succeed, exit here + if (!saveResult.success()) { + return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + } + + // There is a new passphrase - cache it + if (saveParcel.mNewUnlock != null) { + log.add(LogType.MSG_ED_CACHING_NEW, 1); + PassphraseCacheService.addCachedPassphrase(mContext, + ring.getMasterKeyId(), + ring.getMasterKeyId(), + saveParcel.mNewUnlock.mNewPassphrase != null + ? saveParcel.mNewUnlock.mNewPassphrase + : saveParcel.mNewUnlock.mNewPin, + ring.getPublicKey().getPrimaryUserIdWithFallback()); + } + + updateProgress(R.string.progress_done, 100, 100); + + // make sure new data is synced into contacts + ContactSyncAdapterService.requestSync(); + + log.add(LogType.MSG_ED_SUCCESS, 0); + return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId()); + + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java index f2acfef1e..abcf575af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java @@ -20,34 +20,24 @@ package org.sufficientlysecure.keychain.operations.results; import android.os.Parcel; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; - public class EditKeyResult extends OperationResult { - private transient UncachedKeyRing mRing; - public final long mRingMasterKeyId; + public final Long mMasterKeyId; - public EditKeyResult(int result, OperationLog log, - UncachedKeyRing ring) { + public EditKeyResult(int result, OperationLog log, Long masterKeyId) { super(result, log); - mRing = ring; - mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; - } - - public UncachedKeyRing getRing() { - return mRing; + mMasterKeyId = masterKeyId; } public EditKeyResult(Parcel source) { super(source); - mRingMasterKeyId = source.readLong(); + mMasterKeyId = source.readLong(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeLong(mRingMasterKeyId); + dest.writeLong(mMasterKeyId); } public static Creator CREATOR = new Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index bd73a9609..771708490 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -508,6 +508,14 @@ public abstract class OperationResult implements Parcelable { MSG_CON_WARN_DELETE_PUBLIC (LogLevel.WARN, R.string.msg_con_warn_delete_public), MSG_CON_WARN_DELETE_SECRET (LogLevel.WARN, R.string.msg_con_warn_delete_secret), + // edit key (higher level operation than modify) + MSG_ED (LogLevel.START, R.string.msg_ed), + MSG_ED_CACHING_NEW (LogLevel.DEBUG, R.string.msg_ed_caching_new), + MSG_ED_ERROR_NO_PARCEL (LogLevel.ERROR, R.string.msg_ed_error_no_parcel), + MSG_ED_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_ed_error_key_not_found), + MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching), + MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success), + // messages used in UI code MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert), MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java new file mode 100644 index 000000000..611353ac9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014 Vincent Breitmoser + * + * 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 . + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.os.Parcel; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; + +public class PgpEditKeyResult extends OperationResult { + + private transient UncachedKeyRing mRing; + public final long mRingMasterKeyId; + + public PgpEditKeyResult(int result, OperationLog log, + UncachedKeyRing ring) { + super(result, log); + mRing = ring; + mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; + } + + public UncachedKeyRing getRing() { + return mRing; + } + + public PgpEditKeyResult(Parcel source) { + super(source); + mRingMasterKeyId = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mRingMasterKeyId); + } + + public static Creator CREATOR = new Creator() { + public PgpEditKeyResult createFromParcel(final Parcel source) { + return new PgpEditKeyResult(source); + } + + public PgpEditKeyResult[] newArray(final int size) { + return new PgpEditKeyResult[size]; + } + }; + +} 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 5ac5f7a9a..128928bb3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -34,7 +34,6 @@ import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignatureGenerator; import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; @@ -50,7 +49,7 @@ import org.sufficientlysecure.keychain.R; 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.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; @@ -302,7 +301,7 @@ public class PgpKeyOperation { } } - public EditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) { + public PgpEditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) { OperationLog log = new OperationLog(); int indent = 0; @@ -315,23 +314,23 @@ public class PgpKeyOperation { if (saveParcel.mAddSubKeys.isEmpty()) { log.add(LogType.MSG_CR_ERROR_NO_MASTER, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (saveParcel.mAddUserIds.isEmpty()) { log.add(LogType.MSG_CR_ERROR_NO_USER_ID, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } SubkeyAdd add = saveParcel.mAddSubKeys.remove(0); if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { log.add(LogType.MSG_CR_ERROR_NO_CERTIFY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (add.mExpiry == null) { log.add(LogType.MSG_CR_ERROR_NULL_EXPIRY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } subProgressPush(10, 30); @@ -340,7 +339,7 @@ public class PgpKeyOperation { // return null if this failed (an error will already have been logged by createKey) if (keyPair == null) { - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } progress(R.string.progress_building_master_key, 40); @@ -367,10 +366,10 @@ public class PgpKeyOperation { } catch (PGPException e) { log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); Log.e(Constants.TAG, "pgp error encoding key", e); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (IOException e) { Log.e(Constants.TAG, "io error encoding key", e); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -390,7 +389,7 @@ public class PgpKeyOperation { * handling of errors should be done in UI code! * */ - public EditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, + public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, String passphrase) { OperationLog log = new OperationLog(); @@ -415,7 +414,7 @@ public class PgpKeyOperation { // Make sure this is called with a proper SaveKeyringParcel if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) { log.add(LogType.MSG_MF_ERROR_KEYID, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // We work on bouncycastle object level here @@ -426,7 +425,7 @@ public class PgpKeyOperation { if (saveParcel.mFingerprint == null || !Arrays.equals(saveParcel.mFingerprint, masterSecretKey.getPublicKey().getFingerprint())) { log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // read masterKeyFlags, and use the same as before. @@ -440,7 +439,7 @@ public class PgpKeyOperation { } - private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, + private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, int masterKeyFlags, long masterKeyExpiry, SaveKeyringParcel saveParcel, String passphrase, OperationLog log) { @@ -462,7 +461,7 @@ public class PgpKeyOperation { masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); } catch (PGPException e) { log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -471,7 +470,7 @@ public class PgpKeyOperation { // Check if we were cancelled if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } { // work on master secret key @@ -488,7 +487,7 @@ public class PgpKeyOperation { if (userId.equals("")) { log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // this operation supersedes all previous binding and revocation certificates, @@ -500,7 +499,7 @@ public class PgpKeyOperation { if (cert.getKeyID() != masterPublicKey.getKeyID()) { // foreign certificate?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION @@ -542,7 +541,7 @@ public class PgpKeyOperation { } if (!exists) { log.add(LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // a duplicate revocation will be removed during canonicalization, so no need to @@ -573,7 +572,7 @@ public class PgpKeyOperation { if (cert.getKeyID() != masterPublicKey.getKeyID()) { // foreign certificate?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // we know from canonicalization that if there is any revocation here, it // is valid and not superseded by a newer certification. @@ -594,7 +593,7 @@ public class PgpKeyOperation { if (currentCert == null) { // no certificate found?! error error error log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // we definitely should not update certifications of revoked keys, so just leave it. @@ -602,7 +601,7 @@ public class PgpKeyOperation { // revoked user ids cannot be primary! if (userId.equals(saveParcel.mChangePrimaryUserId)) { log.add(LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } continue; } @@ -651,7 +650,7 @@ public class PgpKeyOperation { if (!ok) { log.add(LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } } @@ -667,7 +666,7 @@ public class PgpKeyOperation { // Check if we were cancelled - again if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } // 4a. For each subkey change, generate new subkey binding certificate @@ -683,7 +682,7 @@ public class PgpKeyOperation { if (sKey == null) { log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // expiry must not be in the past @@ -691,7 +690,7 @@ public class PgpKeyOperation { new Date(change.mExpiry*1000).before(new Date())) { log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // if this is the master key, update uid certificates instead @@ -701,7 +700,7 @@ public class PgpKeyOperation { if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { log.add(LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } PGPPublicKey pKey = @@ -709,7 +708,7 @@ public class PgpKeyOperation { flags, expiry, indent, log); if (pKey == null) { // error log entry has already been added by updateMasterCertificates itself - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } masterSecretKey = PGPSecretKey.replacePublicKey(sKey, pKey); masterPublicKey = pKey; @@ -764,7 +763,7 @@ public class PgpKeyOperation { if (sKey == null) { log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent+1, KeyFormattingUtils.convertKeyIdToHex(revocation)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } PGPPublicKey pKey = sKey.getPublicKey(); @@ -789,7 +788,7 @@ public class PgpKeyOperation { if (sKey == null) { log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING, indent+1, KeyFormattingUtils.convertKeyIdToHex(strip)); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // IT'S DANGEROUS~ @@ -807,7 +806,7 @@ public class PgpKeyOperation { // Check if we were cancelled - again. This operation is expensive so we do it each loop. if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size())); @@ -817,12 +816,12 @@ public class PgpKeyOperation { if (add.mExpiry == null) { log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } if (add.mExpiry > 0L && new Date(add.mExpiry*1000).before(new Date())) { log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // generate a new secret key (privkey only for now) @@ -834,7 +833,7 @@ public class PgpKeyOperation { subProgressPop(); if (keyPair == null) { log.add(LogType.MSG_MF_ERROR_PGP, indent +1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } // add subkey binding signature (making this a sub rather than master key) @@ -869,7 +868,7 @@ public class PgpKeyOperation { // Check if we were cancelled - again. This operation is expensive so we do it each loop. if (checkCancelled()) { log.add(LogType.MSG_OPERATION_CANCELLED, indent); - return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null); } // 6. If requested, change passphrase @@ -882,7 +881,7 @@ public class PgpKeyOperation { passphrase, saveParcel.mNewUnlock, log, indent); if (sKR == null) { // The error has been logged above, just return a bad state - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } indent -= 1; @@ -891,20 +890,20 @@ public class PgpKeyOperation { } catch (IOException e) { Log.e(Constants.TAG, "encountered IOException while modifying key", e); log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (PGPException e) { Log.e(Constants.TAG, "encountered pgp error while modifying key", e); log.add(LogType.MSG_MF_ERROR_PGP, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } catch (SignatureException e) { Log.e(Constants.TAG, "encountered SignatureException while modifying key", e); log.add(LogType.MSG_MF_ERROR_SIG, indent+1); - return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null); + return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); } progress(R.string.progress_done, 100); log.add(LogType.MSG_MF_SUCCESS, indent); - return new EditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); + return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 9d073256b..479810203 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -27,11 +27,13 @@ import android.os.Messenger; import android.os.RemoteException; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.DeleteOperation; +import org.sufficientlysecure.keychain.operations.EditKeyOperation; import org.sufficientlysecure.keychain.operations.results.DeleteResult; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.util.FileHelper; @@ -41,31 +43,23 @@ import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.operations.ImportExportOperation; -import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ProgressScaler; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -342,83 +336,16 @@ public class KeychainIntentService extends IntentService implements Progressable } else if (ACTION_EDIT_KEYRING.equals(action)) { - try { - /* Input */ - SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - if (saveParcel == null) { - Log.e(Constants.TAG, "bug: missing save_keyring_parcel in data!"); - return; - } + // Input + SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); + String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE); - /* Operation */ - PgpKeyOperation keyOperations = - new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100), mActionCanceled); - EditKeyResult modifyResult; + // Operation + EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled); + EditKeyResult result = op.execute(saveParcel, passphrase); - if (saveParcel.mMasterKeyId != null) { - String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE); - CanonicalizedSecretKeyRing secRing = - new ProviderHelper(this).getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); - - modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); - } else { - modifyResult = keyOperations.createSecretKeyRing(saveParcel); - } - - // If the edit operation didn't succeed, exit here - if (!modifyResult.success()) { - // always return SaveKeyringResult, so create one out of the EditKeyResult - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); - return; - } - - UncachedKeyRing ring = modifyResult.getRing(); - - // Check if the action was cancelled - if (mActionCanceled.get()) { - OperationLog log = modifyResult.getLog(); - // If it wasn't added before, add log entry - if (!modifyResult.cancelled()) { - log.add(LogType.MSG_OPERATION_CANCELLED, 0); - } - // If so, just stop without saving - modifyResult = new EditKeyResult( - EditKeyResult.RESULT_CANCELLED, log, null); - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, modifyResult); - return; - } - - // Save the keyring. The ProviderHelper is initialized with the previous log - SaveKeyringResult saveResult = new ProviderHelper(this, modifyResult.getLog()) - .saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100)); - - // If the edit operation didn't succeed, exit here - if (!saveResult.success()) { - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); - return; - } - - // cache new passphrase - if (saveParcel.mNewUnlock != null) { - PassphraseCacheService.addCachedPassphrase(this, - ring.getMasterKeyId(), - ring.getMasterKeyId(), - saveParcel.mNewUnlock.mNewPassphrase != null - ? saveParcel.mNewUnlock.mNewPassphrase - : saveParcel.mNewUnlock.mNewPin, - ring.getPublicKey().getPrimaryUserIdWithFallback()); - } - - setProgress(R.string.progress_done, 100, 100); - - // make sure new data is synced into contacts - ContactSyncAdapterService.requestSync(); - - /* Output */ - sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, saveResult); - } catch (Exception e) { - sendErrorToHandler(e); - } + // Result + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result); } else if (ACTION_EXPORT_KEYRING.equals(action)) { @@ -430,7 +357,6 @@ public class KeychainIntentService extends IntentService implements Progressable boolean exportAll = data.getBoolean(EXPORT_ALL); long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - // Operation ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); ExportResult result; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index b48f10bbf..2804a5ce6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -34,6 +34,7 @@ import android.widget.TextView; import org.spongycastle.bcpg.sig.KeyFlags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -190,14 +191,14 @@ public class CreateKeyFinalFragment extends Fragment { if (returnData == null) { return; } - final SaveKeyringResult result = + final EditKeyResult result = returnData.getParcelable(OperationResult.EXTRA_RESULT); if (result == null) { Log.e(Constants.TAG, "result == null"); return; } - if (mUploadCheckbox.isChecked()) { + if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { // result will be displayed after upload uploadKey(result); } else { @@ -227,7 +228,8 @@ public class CreateKeyFinalFragment extends Fragment { getActivity().startService(intent); } - private void uploadKey(final SaveKeyringResult saveKeyResult) { + // TODO move into EditKeyOperation + private void uploadKey(final EditKeyResult saveKeyResult) { // Send all information needed to service to upload key in other thread final Intent intent = new Intent(getActivity(), KeychainIntentService.class); @@ -235,7 +237,7 @@ public class CreateKeyFinalFragment extends Fragment { // set data uri as path to keyring Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - saveKeyResult.mRingMasterKeyId); + saveKeyResult.mMasterKeyId); intent.setData(blobUri); // fill values for this action diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 55497361b..60b84ea52 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -859,6 +859,14 @@ "Exception deleting public cache file" "Exception deleting secret cache file" + + "Performing key operation" + "Caching new passphrase" + "Missing SaveKeyringParcel! (this is a bug, please report)" + "Key not found!" + "Fetching key to modify (%s)" + "Key operation successful" + "Editing of NFC keys is not (yet) supported!" "Cannot edit keyring with stripped master key!" From 2192a6baddd09ae81f86a8bfd5e844086154f5ba Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 3 Jan 2015 21:07:33 +0100 Subject: [PATCH 22/25] add more descriptive string about non-mdc encrypted data --- .../keychain/operations/results/OperationResult.java | 1 + .../org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java | 2 +- OpenKeychain/src/main/res/values/strings.xml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 771708490..beafdcbb2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -539,6 +539,7 @@ public abstract class OperationResult implements Parcelable { MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase), MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key), MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check), + MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing), MSG_DC_ERROR_INVALID_SIGLIST(LogLevel.ERROR, R.string.msg_dc_error_invalid_siglist), MSG_DC_ERROR_IO (LogLevel.ERROR, R.string.msg_dc_error_io), MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index f5f0ce9a4..b58df085f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -633,7 +633,7 @@ public class PgpDecryptVerify extends BaseOperation { // Handle missing integrity protection like failed integrity protection! // The MDC packet can be stripped by an attacker! if (!signatureResultBuilder.isValidSignature()) { - log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent); + log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 60b84ea52..16fd2691b 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -890,6 +890,7 @@ "Error unlocking key, bad passphrase!" "Unknown error unlocking key!" "Integrity check error!" + "Missing integrity check! This can happen because the encrypting application is out of date, or from a downgrade attack." "No valid signature data found!" "Encountered IO Exception during operation!" "No encrypted data found in stream!" From 47ace7cea31ee794ed88bdf4163dd38fc33e8fc5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 3 Jan 2015 22:14:12 +0100 Subject: [PATCH 23/25] make certify routines more robust (#1016) --- .../keychain/operations/CertifyOperation.java | 6 ++++++ .../keychain/operations/results/OperationResult.java | 1 + .../keychain/pgp/CanonicalizedSecretKey.java | 6 ++++++ OpenKeychain/src/main/res/values/strings.xml | 1 + 4 files changed, 14 insertions(+) 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 3bd412c36..d231038da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -94,6 +94,12 @@ public class CertifyOperation extends BaseOperation { try { + if (action.mMasterKeyId == parcel.mMasterKeyId) { + log.add(LogType.MSG_CRT_ERROR_SELF, 2); + certifyError += 1; + continue; + } + if (action.mUserIds == null) { log.add(LogType.MSG_CRT_CERTIFY_ALL, 2, KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index beafdcbb2..1388c0eac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -587,6 +587,7 @@ public abstract class OperationResult implements Parcelable { MSG_CRT_CERTIFYING (LogLevel.DEBUG, R.string.msg_crt_certifying), MSG_CRT_CERTIFY_ALL (LogLevel.DEBUG, R.string.msg_crt_certify_all), MSG_CRT_CERTIFY_SOME (LogLevel.DEBUG, R.plurals.msg_crt_certify_some), + MSG_CRT_ERROR_SELF (LogLevel.ERROR, R.string.msg_crt_error_self), MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found), MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing), MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), 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 42e59b3bc..6965ca7cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -300,6 +300,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } + if (!isMasterKey()) { + throw new AssertionError("tried to certify with non-master key, this is a programming error!"); + } + if (publicKeyRing.getMasterKeyId() == getKeyId()) { + throw new AssertionError("key tried to self-certify, this is a programming error!"); + } // create a signatureGenerator from the supplied masterKeyId and passphrase PGPSignatureGenerator signatureGenerator; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 16fd2691b..8dc708f1c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -942,6 +942,7 @@ "Certifying one user ID for key %2$s" "Certifying %1$d user IDs for key %2$s" + "Cannot issue self-certificate like this!" "Master key not found!" "No keys certified!" "Error unlocking master key!" From 07251f35aec9913e913719456fc20aa051810b15 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 3 Jan 2015 23:01:57 +0100 Subject: [PATCH 24/25] minor stuff --- .../keychain/operations/CertifyOperation.java | 7 ++++--- .../keychain/provider/KeychainContract.java | 1 + .../keychain/provider/ProviderHelper.java | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) 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 d231038da..a5eb95b07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -50,9 +50,6 @@ public class CertifyOperation extends BaseOperation { CanonicalizedSecretKey certificationKey; try { - // certification is always with the master key id, so use that one - String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); - log.add(LogType.MSG_CRT_MASTER_FETCH, 1); CanonicalizedSecretKeyRing secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); @@ -62,6 +59,10 @@ public class CertifyOperation extends BaseOperation { log.add(LogType.MSG_CRT_ERROR_DIVERT, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); } + + // certification is always with the master key id, so use that one + String passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId); + if (!certificationKey.unlock(passphrase)) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 6127002bb..2c02e429d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -307,6 +307,7 @@ public class KeychainContract { public static final String USER_ID = UserIdsColumns.USER_ID; public static final String SIGNER_UID = "signer_user_id"; + public static final int UNVERIFIED = 0; public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SELF = 2; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 7cb57ddfe..4f1b4b6c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -543,6 +543,7 @@ public class ProviderHelper { UserIdItem item = uids.get(userIdRank); operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); if (item.selfCert != null) { + // TODO get rid of "self verified" status? this cannot even happen anymore! operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); } From 0c3f9ae0a67cb98b2ac3ac59985aec03e6a15861 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 3 Jan 2015 23:02:11 +0100 Subject: [PATCH 25/25] tests: add a couple of CertifyOperation test cases --- .../operations/CertifyOperationTest.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java new file mode 100644 index 000000000..c05f4a029 --- /dev/null +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2014 Vincent Breitmoser + * + * 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 . + */ + +package org.sufficientlysecure.keychain.operations; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLog; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.sufficientlysecure.keychain.operations.results.CertifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.ExportResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; +import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel; +import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.ProgressScaler; +import org.sufficientlysecure.keychain.util.TestingUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.security.Security; +import java.util.Iterator; + +@RunWith(RobolectricTestRunner.class) +@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19 +public class CertifyOperationTest { + + static String mPassphrase = TestingUtils.genPassphrase(true); + + static UncachedKeyRing mStaticRing1, mStaticRing2; + static String mKeyPhrase1 = TestingUtils.genPassphrase(true); + static String mKeyPhrase2 = TestingUtils.genPassphrase(true); + + static PrintStream oldShadowStream; + + @BeforeClass + public static void setUpOnce() throws Exception { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + oldShadowStream = ShadowLog.stream; + // ShadowLog.stream = System.out; + + PgpKeyOperation op = new PgpKeyOperation(null); + + { + SaveKeyringParcel parcel = new SaveKeyringParcel(); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); + parcel.mAddUserIds.add("derp"); + parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1); + + PgpEditKeyResult result = op.createSecretKeyRing(parcel); + Assert.assertTrue("initial test key creation must succeed", result.success()); + Assert.assertNotNull("initial test key creation must succeed", result.getRing()); + + mStaticRing1 = result.getRing(); + } + + { + SaveKeyringParcel parcel = new SaveKeyringParcel(); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); + parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( + Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L)); + parcel.mAddUserIds.add("ditz"); + parcel.mNewUnlock = new ChangeUnlockParcel(null, "1234"); + + PgpEditKeyResult result = op.createSecretKeyRing(parcel); + Assert.assertTrue("initial test key creation must succeed", result.success()); + Assert.assertNotNull("initial test key creation must succeed", result.getRing()); + + mStaticRing2 = result.getRing(); + } + + } + + @Before + public void setUp() throws Exception { + ProviderHelper providerHelper = new ProviderHelper(Robolectric.application); + + // don't log verbosely here, we're not here to test imports + ShadowLog.stream = oldShadowStream; + + providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler()); + providerHelper.savePublicKeyRing(mStaticRing2.extractPublicKeyRing(), new ProgressScaler()); + + // ok NOW log verbosely! + ShadowLog.stream = System.out; + } + + @Test + public void testSelfCertifyFlag() throws Exception { + + CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) + .getCanonicalizedPublicKeyRing(mStaticRing1.getMasterKeyId()); + Assert.assertEquals("secret key must be marked self-certified in database", + // TODO this should be more correctly be VERIFIED_SELF at some point! + Certs.VERIFIED_SECRET, ring.getVerified()); + + } + + @Test + public void testCertify() throws Exception { + CertifyOperation op = operationWithFakePassphraseCache( + mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); + + { + CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) + .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); + Assert.assertEquals("public key must not be marked verified prior to certification", + Certs.UNVERIFIED, ring.getVerified()); + } + + CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); + actions.add(new CertifyAction(mStaticRing2.getMasterKeyId())); + CertifyResult result = op.certify(actions, null); + + Assert.assertTrue("certification must succeed", result.success()); + + { + CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) + .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); + Assert.assertEquals("new key must be verified now", + Certs.VERIFIED_SECRET, ring.getVerified()); + } + + } + + @Test + public void testCertifySelf() throws Exception { + CertifyOperation op = operationWithFakePassphraseCache( + mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); + + CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); + actions.add(new CertifyAction(mStaticRing1.getMasterKeyId())); + + CertifyResult result = op.certify(actions, null); + + Assert.assertFalse("certification with itself must fail!", result.success()); + Assert.assertTrue("error msg must be about self certification", + result.getLog().containsType(LogType.MSG_CRT_ERROR_SELF)); + } + + @Test + public void testCertifyNonexistent() throws Exception { + + CertifyOperation op = operationWithFakePassphraseCache(null, null, mKeyPhrase1); + + { + CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); + actions.add(new CertifyAction(1234L)); + + CertifyResult result = op.certify(actions, null); + + Assert.assertFalse("certification of nonexistent key must fail", result.success()); + Assert.assertTrue("must contain error msg about not found", + result.getLog().containsType(LogType.MSG_CRT_WARN_NOT_FOUND)); + } + + { + CertifyActionsParcel actions = new CertifyActionsParcel(1234L); + actions.add(new CertifyAction(mStaticRing1.getMasterKeyId())); + + CertifyResult result = op.certify(actions, null); + + Assert.assertFalse("certification of nonexistent key must fail", result.success()); + Assert.assertTrue("must contain error msg about not found", + result.getLog().containsType(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND)); + } + + } + + private CertifyOperation operationWithFakePassphraseCache( + final Long checkMasterKeyId, final Long checkSubKeyId, final String passphrase) { + + return new CertifyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), + null, null) { + @Override + public String getCachedPassphrase(long masterKeyId, long subKeyId) + throws NoSecretKeyException { + if (checkMasterKeyId != null) { + Assert.assertEquals("requested passphrase should be for expected master key id", + (long) checkMasterKeyId, masterKeyId); + } + if (checkSubKeyId != null) { + Assert.assertEquals("requested passphrase should be for expected sub key id", + (long) checkSubKeyId, subKeyId); + } + if (passphrase == null) { + return null; + } + return passphrase; + } + }; + } + +}