diff --git a/.gitmodules b/.gitmodules
index b7b0e1173..956362d4b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,6 +31,6 @@
[submodule "extern/minidns"]
path = extern/minidns
url = https://github.com/open-keychain/minidns.git
-[submodule "OpenKeychain/src/test/resources/extern/OpenPGP-Haskell"]
- path = OpenKeychain/src/test/resources/extern/OpenPGP-Haskell
+[submodule "OpenKeychain-Test/src/test/resources/extern/OpenPGP-Haskell"]
+ path = OpenKeychain-Test/src/test/resources/extern/OpenPGP-Haskell
url = https://github.com/singpolyma/OpenPGP-Haskell.git
diff --git a/.travis.yml b/.travis.yml
index 5da831e61..fd6e55ea7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: java
-jdk: oraclejdk7
+jdk: openjdk7
before_install:
# Install base Android SDK
- sudo apt-get update -qq
@@ -12,6 +12,9 @@ before_install:
# Install required Android components.
#- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
- ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository
+ - ./install-custom-gradle-test-plugin.sh
install: echo "Installation done"
-script: gradle assemble -S -q
+script:
+ - gradle assemble -S -q
+ - gradle --info OpenKeychain-Test:testDebug
diff --git a/OpenKeychain-Test/build.gradle b/OpenKeychain-Test/build.gradle
new file mode 100644
index 000000000..d795ace3d
--- /dev/null
+++ b/OpenKeychain-Test/build.gradle
@@ -0,0 +1,80 @@
+apply plugin: 'java'
+apply plugin: 'android-test'
+
+dependencies {
+ testCompile 'junit:junit:4.11'
+ testCompile 'com.google.android:android:4.1.1.4'
+ testCompile('com.squareup:fest-android:1.0.+') { exclude module: 'support-v4' }
+ testCompile ('org.robolectric:robolectric:2.3') {
+ exclude module: 'classworlds'
+ exclude module: 'maven-artifact'
+ exclude module: 'maven-artifact-manager'
+ exclude module: 'maven-error-diagnostics'
+ exclude module: 'maven-model'
+ exclude module: 'maven-plugin-registry'
+ exclude module: 'maven-profile'
+ exclude module: 'maven-project'
+ exclude module: 'maven-settings'
+ exclude module: 'nekohtml'
+ exclude module: 'plexus-container-default'
+ exclude module: 'plexus-interpolation'
+ exclude module: 'plexus-utils'
+ exclude module: 'support-v4' // crazy but my android studio don't like this dependency and to fix it remove .idea and re import project
+ exclude module: 'wagon-file'
+ exclude module: 'wagon-http-lightweight'
+ exclude module: 'wagon-http-shared'
+ exclude module: 'wagon-provider-api'
+ }
+}
+
+android {
+ projectUnderTest ':OpenKeychain'
+}
+
+// new workaround to force add custom output dirs for android studio
+task addTest {
+ def file = file(project.name + ".iml")
+ doLast {
+ try {
+ def parsedXml = (new XmlParser()).parse(file)
+ def node = parsedXml.component[1]
+ def outputNode = parsedXml.component[1].output[0]
+ def outputTestNode = parsedXml.component[1].'output-test'[0]
+ def rewrite = false
+
+ new Node(node, 'sourceFolder', ['url': 'file://$MODULE_DIR$/' + "${it}", 'isTestSource': "true"])
+
+ if(outputNode == null) {
+ new Node(node, 'output', ['url': 'file://$MODULE_DIR$/build/resources/testDebug'])
+ } else {
+ if(outputNode.attributes['url'] != 'file://$MODULE_DIR$/build/resources/testDebug') {
+ outputNode.attributes = ['url': 'file://$MODULE_DIR$/build/resources/testDebug']
+ rewrite = true
+ }
+ }
+
+ if(outputTestNode == null) {
+ new Node(node, 'output-test', ['url': 'file://$MODULE_DIR$/build/test-classes/debug'])
+ } else {
+ if(outputTestNode.attributes['url'] != 'file://$MODULE_DIR$/build/test-classes/debug') {
+ outputTestNode.attributes = ['url': 'file://$MODULE_DIR$/build/test-classes/debug']
+ rewrite = true
+ }
+ }
+
+ if(rewrite) {
+ def writer = new StringWriter()
+ new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
+ file.text = writer.toString()
+ }
+ } catch (FileNotFoundException e) {
+ // iml not found, common on command line only builds
+ }
+
+ }
+}
+
+// always do the addtest on prebuild
+gradle.projectsEvaluated {
+ testDebugClasses.dependsOn(addTest)
+}
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringBuilder.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringBuilder.java
new file mode 100644
index 000000000..94193bbcb
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringBuilder.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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.support;
+
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.ContainedPacket;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.MPInteger;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.PublicSubkeyPacket;
+import org.spongycastle.bcpg.RSAPublicBCPGKey;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.bcpg.SignatureSubpacketInputStream;
+import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.bcpg.UserIDPacket;
+import org.spongycastle.bcpg.sig.Features;
+import org.spongycastle.bcpg.sig.IssuerKeyID;
+import org.spongycastle.bcpg.sig.KeyExpirationTime;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.bcpg.sig.PreferredAlgorithms;
+import org.spongycastle.bcpg.sig.SignatureCreationTime;
+import org.spongycastle.openpgp.PGPSignature;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helps create correct and incorrect keyrings for tests.
+ *
+ * The original "correct" keyring was generated by GnuPG.
+ */
+public class KeyringBuilder {
+
+
+ private static final BigInteger PUBLIC_KEY_MODULUS = new BigInteger(
+ "cbab78d90d5f2cc0c54dd3c3953005a1e6b521f1ffa5465a102648bf7b91ec72" +
+ "f9c180759301587878caeb73332156209f81ca5b3b94309d96110f6972cfc56a" +
+ "37fd6279f61d71f19b8f64b288e338299dce133520f5b9b4253e6f4ba31ca36a" +
+ "fd87c2081b15f0b283e9350e370e181a23d31379101f17a23ae9192250db6540" +
+ "2e9cab2a275bc5867563227b197c8b136c832a94325b680e144ed864fb00b9b8" +
+ "b07e13f37b40d5ac27dae63cd6a470a7b40fa3c7479b5b43e634850cc680b177" +
+ "8dd6b1b51856f36c3520f258f104db2f96b31a53dd74f708ccfcefccbe420a90" +
+ "1c37f1f477a6a4b15f5ecbbfd93311a647bcc3f5f81c59dfe7252e3cd3be6e27"
+ , 16
+ );
+
+ private static final BigInteger PUBLIC_SUBKEY_MODULUS = new BigInteger(
+ "e8e2e2a33102649f19f8a07486fb076a1406ca888d72ae05d28f0ef372b5408e" +
+ "45132c69f6e5cb6a79bb8aed84634196731393a82d53e0ddd42f28f92cc15850" +
+ "8ce3b7ca1a9830502745aee774f86987993df984781f47c4a2910f95cf4c950c" +
+ "c4c6cccdc134ad408a0c5418b5e360c9781a8434d366053ea6338b975fae88f9" +
+ "383a10a90e7b2caa9ddb95708aa9d8a90246e29b04dbd6136613085c9a287315" +
+ "c6e9c7ff4012defc1713875e3ff6073333a1c93d7cd75ebeaaf16b8b853d96ba" +
+ "7003258779e8d2f70f1bc0bcd3ef91d7a9ccd8e225579b2d6fcae32799b0a6c0" +
+ "e7305fc65dc4edc849c6130a0d669c90c193b1e746c812510f9d600a208be4a5"
+ , 16
+ );
+
+ private static final Date SIGNATURE_DATE = new Date(1404566755000L);
+
+ private static final BigInteger EXPONENT = BigInteger.valueOf(0x010001);
+
+ private static final String USER_ID_STRING = "OpenKeychain User (NOT A REAL KEY) ";
+
+ public static final BigInteger CORRECT_SIGNATURE = new BigInteger(
+ "b065c071d3439d5610eb22e5b4df9e42ed78b8c94f487389e4fc98e8a75a043f" +
+ "14bf57d591811e8e7db2d31967022d2ee64372829183ec51d0e20c42d7a1e519" +
+ "e9fa22cd9db90f0fd7094fd093b78be2c0db62022193517404d749152c71edc6" +
+ "fd48af3416038d8842608ecddebbb11c5823a4321d2029b8993cb017fa8e5ad7" +
+ "8a9a618672d0217c4b34002f1a4a7625a514b6a86475e573cb87c64d7069658e" +
+ "627f2617874007a28d525e0f87d93ca7b15ad10dbdf10251e542afb8f9b16cbf" +
+ "7bebdb5fe7e867325a44e59cad0991cb239b1c859882e2ebb041b80e5cdc3b40" +
+ "ed259a8a27d63869754c0881ccdcb50f0564fecdc6966be4a4b87a3507a9d9be"
+ , 16
+ );
+ public static final BigInteger CORRECT_SUBKEY_SIGNATURE = new BigInteger(
+ "9c40543e646cfa6d3d1863d91a4e8f1421d0616ddb3187505df75fbbb6c59dd5" +
+ "3136b866f246a0320e793cb142c55c8e0e521d1e8d9ab864650f10690f5f1429" +
+ "2eb8402a3b1f82c01079d12f5c57c43fce524a530e6f49f6f87d984e26db67a2" +
+ "d469386dac87553c50147ebb6c2edd9248325405f737b815253beedaaba4f5c9" +
+ "3acd5d07fe6522ceda1027932d849e3ec4d316422cd43ea6e506f643936ab0be" +
+ "8246e546bb90d9a83613185047566864ffe894946477e939725171e0e15710b2" +
+ "089f78752a9cb572f5907323f1b62f14cb07671aeb02e6d7178f185467624ec5" +
+ "74e4a73c439a12edba200a4832106767366a1e6f63da0a42d593fa3914deee2b"
+ , 16
+ );
+ public static final BigInteger KEY_ID = BigInteger.valueOf(0x15130BCF071AE6BFL);
+
+ public static UncachedKeyRing correctRing() {
+ return convertToKeyring(correctKeyringPackets());
+ }
+
+ public static UncachedKeyRing ringWithExtraIncorrectSignature() {
+ List packets = correctKeyringPackets();
+ SignaturePacket incorrectSignaturePacket = createSignaturePacket(CORRECT_SIGNATURE.subtract(BigInteger.ONE));
+ packets.add(2, incorrectSignaturePacket);
+ return convertToKeyring(packets);
+ }
+
+ private static UncachedKeyRing convertToKeyring(List packets) {
+ try {
+ return UncachedKeyRing.decodeFromData(TestDataUtil.concatAll(packets));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static List correctKeyringPackets() {
+ PublicKeyPacket publicKey = createPgpPublicKey(PUBLIC_KEY_MODULUS);
+ UserIDPacket userId = createUserId(USER_ID_STRING);
+ SignaturePacket signaturePacket = createSignaturePacket(CORRECT_SIGNATURE);
+ PublicKeyPacket subKey = createPgpPublicSubKey(PUBLIC_SUBKEY_MODULUS);
+ SignaturePacket subKeySignaturePacket = createSubkeySignaturePacket();
+
+ return new ArrayList(Arrays.asList(
+ publicKey,
+ userId,
+ signaturePacket,
+ subKey,
+ subKeySignaturePacket
+ ));
+ }
+
+ private static SignaturePacket createSignaturePacket(BigInteger signature) {
+ MPInteger[] signatureArray = new MPInteger[]{
+ new MPInteger(signature)
+ };
+
+ int signatureType = PGPSignature.POSITIVE_CERTIFICATION;
+ int keyAlgorithm = SignaturePacket.RSA_GENERAL;
+ int hashAlgorithm = HashAlgorithmTags.SHA1;
+
+ SignatureSubpacket[] hashedData = new SignatureSubpacket[]{
+ new SignatureCreationTime(false, SIGNATURE_DATE),
+ new KeyFlags(false, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA),
+ new KeyExpirationTime(false, TimeUnit.DAYS.toSeconds(2)),
+ new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, new int[]{
+ SymmetricKeyAlgorithmTags.AES_256,
+ SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_128,
+ SymmetricKeyAlgorithmTags.CAST5,
+ SymmetricKeyAlgorithmTags.TRIPLE_DES
+ }),
+ new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, false, new int[]{
+ HashAlgorithmTags.SHA256,
+ HashAlgorithmTags.SHA1,
+ HashAlgorithmTags.SHA384,
+ HashAlgorithmTags.SHA512,
+ HashAlgorithmTags.SHA224
+ }),
+ new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, false, new int[]{
+ CompressionAlgorithmTags.ZLIB,
+ CompressionAlgorithmTags.BZIP2,
+ CompressionAlgorithmTags.ZIP
+ }),
+ new Features(false, Features.FEATURE_MODIFICATION_DETECTION),
+ createPreferencesSignatureSubpacket()
+ };
+ SignatureSubpacket[] unhashedData = new SignatureSubpacket[]{
+ new IssuerKeyID(false, KEY_ID.toByteArray())
+ };
+ byte[] fingerPrint = new BigInteger("522c", 16).toByteArray();
+
+ return new SignaturePacket(signatureType,
+ KEY_ID.longValue(),
+ keyAlgorithm,
+ hashAlgorithm,
+ hashedData,
+ unhashedData,
+ fingerPrint,
+ signatureArray);
+ }
+
+ /**
+ * There is no Preferences subpacket in BouncyCastle, so we have
+ * to create one manually.
+ */
+ private static SignatureSubpacket createPreferencesSignatureSubpacket() {
+ SignatureSubpacket prefs;
+ try {
+ prefs = new SignatureSubpacketInputStream(new ByteArrayInputStream(
+ new byte[]{2, SignatureSubpacketTags.KEY_SERVER_PREFS, (byte) 0x80})
+ ).readPacket();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ return prefs;
+ }
+
+ private static SignaturePacket createSubkeySignaturePacket() {
+ int signatureType = PGPSignature.SUBKEY_BINDING;
+ int keyAlgorithm = SignaturePacket.RSA_GENERAL;
+ int hashAlgorithm = HashAlgorithmTags.SHA1;
+
+ SignatureSubpacket[] hashedData = new SignatureSubpacket[]{
+ new SignatureCreationTime(false, SIGNATURE_DATE),
+ new KeyFlags(false, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE),
+ new KeyExpirationTime(false, TimeUnit.DAYS.toSeconds(2)),
+ };
+ SignatureSubpacket[] unhashedData = new SignatureSubpacket[]{
+ new IssuerKeyID(false, KEY_ID.toByteArray())
+ };
+ byte[] fingerPrint = new BigInteger("234a", 16).toByteArray();
+ MPInteger[] signature = new MPInteger[]{
+ new MPInteger(CORRECT_SUBKEY_SIGNATURE)
+ };
+ return new SignaturePacket(signatureType,
+ KEY_ID.longValue(),
+ keyAlgorithm,
+ hashAlgorithm,
+ hashedData,
+ unhashedData,
+ fingerPrint,
+ signature);
+ }
+
+ private static PublicKeyPacket createPgpPublicKey(BigInteger modulus) {
+ return new PublicKeyPacket(PublicKeyAlgorithmTags.RSA_GENERAL, SIGNATURE_DATE, new RSAPublicBCPGKey(modulus, EXPONENT));
+ }
+
+ private static PublicKeyPacket createPgpPublicSubKey(BigInteger modulus) {
+ return new PublicSubkeyPacket(PublicKeyAlgorithmTags.RSA_GENERAL, SIGNATURE_DATE, new RSAPublicBCPGKey(modulus, EXPONENT));
+ }
+
+ private static UserIDPacket createUserId(String userId) {
+ return new UserIDPacket(userId);
+ }
+
+}
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java
new file mode 100644
index 000000000..398b2393e
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) Art O Cathain, 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.support;
+
+import android.content.Context;
+
+import org.spongycastle.util.Arrays;
+import org.sufficientlysecure.keychain.pgp.NullProgressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.OperationResults;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+/** Helper methods for keyring tests. */
+public class KeyringTestingHelper {
+
+ private final Context context;
+
+ public KeyringTestingHelper(Context robolectricContext) {
+ this.context = robolectricContext;
+ }
+
+ public boolean addKeyring(Collection blobFiles) throws Exception {
+
+ ProviderHelper providerHelper = new ProviderHelper(context);
+
+ byte[] data = TestDataUtil.readAllFully(blobFiles);
+ UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
+ long masterKeyId = ring.getMasterKeyId();
+
+ // Should throw an exception; key is not yet saved
+ retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
+
+ OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
+
+ boolean saveSuccess = saveKeyringResult.success();
+
+ // Now re-retrieve the saved key. Should not throw an exception.
+ providerHelper.getWrappedPublicKeyRing(masterKeyId);
+
+ // A different ID should still fail
+ retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
+
+ return saveSuccess;
+ }
+
+ public static byte[] removePacket(byte[] ring, int position) throws IOException {
+ Iterator it = parseKeyring(ring);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length);
+
+ int i = 0;
+ while(it.hasNext()) {
+ // at the right position, skip the packet
+ if(i++ == position) {
+ continue;
+ }
+ // write the old one
+ out.write(it.next().buf);
+ }
+
+ if (i <= position) {
+ throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
+ }
+
+ return out.toByteArray();
+ }
+
+ public static byte[] injectPacket(byte[] ring, byte[] inject, int position) throws IOException {
+
+ Iterator it = parseKeyring(ring);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length + inject.length);
+
+ int i = 0;
+ while(it.hasNext()) {
+ // at the right position, inject the new packet
+ if(i++ == position) {
+ out.write(inject);
+ }
+ // write the old one
+ out.write(it.next().buf);
+ }
+
+ if (i <= position) {
+ throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
+ }
+
+ return out.toByteArray();
+
+ }
+
+ /** This class contains a single pgp packet, together with information about its position
+ * in the keyring and its packet tag.
+ */
+ public static class RawPacket {
+ public int position;
+
+ // packet tag for convenience, this can also be read from the header
+ public int tag;
+
+ public int headerLength, length;
+ // this buf includes the header, so its length is headerLength + length!
+ public byte[] buf;
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof RawPacket && Arrays.areEqual(this.buf, ((RawPacket) other).buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(buf);
+ }
+ }
+
+ /** A comparator which compares RawPackets by their position */
+ public static final Comparator packetOrder = new Comparator() {
+ public int compare(RawPacket left, RawPacket right) {
+ return Integer.compare(left.position, right.position);
+ }
+ };
+
+ /** Diff two keyrings, returning packets only present in one keyring in its associated List.
+ *
+ * Packets in the returned lists are annotated and ordered by their original order of appearance
+ * in their origin keyrings.
+ *
+ * @return true if keyrings differ in at least one packet
+ */
+ public static boolean diffKeyrings(byte[] ringA, byte[] ringB,
+ List onlyA, List onlyB)
+ throws IOException {
+ Iterator streamA = parseKeyring(ringA);
+ Iterator streamB = parseKeyring(ringB);
+
+ HashSet a = new HashSet(), b = new HashSet();
+
+ RawPacket p;
+ int pos = 0;
+ while(true) {
+ p = streamA.next();
+ if (p == null) {
+ break;
+ }
+ p.position = pos++;
+ a.add(p);
+ }
+ pos = 0;
+ while(true) {
+ p = streamB.next();
+ if (p == null) {
+ break;
+ }
+ p.position = pos++;
+ b.add(p);
+ }
+
+ onlyA.clear();
+ onlyB.clear();
+
+ onlyA.addAll(a);
+ onlyA.removeAll(b);
+ onlyB.addAll(b);
+ onlyB.removeAll(a);
+
+ Collections.sort(onlyA, packetOrder);
+ Collections.sort(onlyB, packetOrder);
+
+ return !onlyA.isEmpty() || !onlyB.isEmpty();
+ }
+
+ /** Creates an iterator of RawPackets over a binary keyring. */
+ public static Iterator parseKeyring(byte[] ring) {
+
+ final InputStream stream = new ByteArrayInputStream(ring);
+
+ return new Iterator() {
+ RawPacket next;
+
+ @Override
+ public boolean hasNext() {
+ if (next == null) try {
+ next = readPacket(stream);
+ } catch (IOException e) {
+ return false;
+ }
+ return next != null;
+ }
+
+ @Override
+ public RawPacket next() {
+ if (!hasNext()) {
+ return null;
+ }
+ try {
+ return next;
+ } finally {
+ next = null;
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ }
+
+ /** Read a single (raw) pgp packet from an input stream.
+ *
+ * Note that the RawPacket.position field is NOT set here!
+ *
+ * Variable length packets are not handled here. we don't use those in our test classes, and
+ * otherwise rely on BouncyCastle's own unit tests to handle those correctly.
+ */
+ private static RawPacket readPacket(InputStream in) throws IOException {
+
+ // save here. this is tag + length, max 6 bytes
+ in.mark(6);
+
+ int hdr = in.read();
+ int headerLength = 1;
+
+ if (hdr < 0) {
+ return null;
+ }
+
+ if ((hdr & 0x80) == 0) {
+ throw new IOException("invalid header encountered");
+ }
+
+ boolean newPacket = (hdr & 0x40) != 0;
+ int tag;
+ int bodyLen;
+
+ if (newPacket) {
+ tag = hdr & 0x3f;
+
+ int l = in.read();
+ headerLength += 1;
+
+ if (l < 192) {
+ bodyLen = l;
+ } else if (l <= 223) {
+ int b = in.read();
+ headerLength += 1;
+
+ bodyLen = ((l - 192) << 8) + (b) + 192;
+ } else if (l == 255) {
+ bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ headerLength += 4;
+ } else {
+ // bodyLen = 1 << (l & 0x1f);
+ throw new IOException("no support for partial bodies in test classes");
+ }
+ } else {
+ int lengthType = hdr & 0x3;
+
+ tag = (hdr & 0x3f) >> 2;
+
+ switch (lengthType) {
+ case 0:
+ bodyLen = in.read();
+ headerLength += 1;
+ break;
+ case 1:
+ bodyLen = (in.read() << 8) | in.read();
+ headerLength += 2;
+ break;
+ case 2:
+ bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ headerLength += 4;
+ break;
+ case 3:
+ // bodyLen = 1 << (l & 0x1f);
+ throw new IOException("no support for partial bodies in test classes");
+ default:
+ throw new IOException("unknown length type encountered");
+ }
+ }
+
+ in.reset();
+
+ // read the entire packet INCLUDING the header here
+ byte[] buf = new byte[headerLength+bodyLen];
+ if (in.read(buf) != headerLength+bodyLen) {
+ throw new IOException("read length mismatch!");
+ }
+ RawPacket p = new RawPacket();
+ p.tag = tag;
+ p.headerLength = headerLength;
+ p.length = bodyLen;
+ p.buf = buf;
+ return p;
+
+ }
+
+ private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
+ try {
+ providerHelper.getWrappedPublicKeyRing(masterKeyId);
+ throw new AssertionError("Was expecting the previous call to fail!");
+ } catch (ProviderHelper.NotFoundException expectedException) {
+ // good
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/PgpVerifyTestingHelper.java
similarity index 68%
rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java
rename to OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/PgpVerifyTestingHelper.java
index 1ab5878cc..dd5786512 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/PgpVerifyTestingHelper.java
@@ -1,4 +1,20 @@
-package org.sufficientlysecure.keychain.testsupport;
+package org.sufficientlysecure.keychain.support;
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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 .
+ */
import android.content.Context;
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/ProviderHelperStub.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/ProviderHelperStub.java
new file mode 100644
index 000000000..f06fe0072
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/ProviderHelperStub.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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.support;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+
+/**
+ * Created by art on 21/06/14.
+ */
+class ProviderHelperStub extends ProviderHelper {
+ public ProviderHelperStub(Context context) {
+ super(context);
+ }
+
+ @Override
+ public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
+ byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
+ return new WrappedPublicKeyRing(data, false, 0);
+ }
+}
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/TestDataUtil.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/TestDataUtil.java
new file mode 100644
index 000000000..f2b3c0996
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/TestDataUtil.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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.support;
+
+import org.spongycastle.bcpg.ContainedPacket;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Misc support functions. Would just use Guava / Apache Commons but
+ * avoiding extra dependencies.
+ */
+public class TestDataUtil {
+ public static byte[] readFully(InputStream input) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ appendToOutput(input, output);
+ return output.toByteArray();
+ }
+
+ public static void appendToOutput(InputStream input, OutputStream output) {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ try {
+ while ((bytesRead = input.read(buffer)) != -1) {
+ output.write(buffer, 0, bytesRead);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static byte[] readAllFully(Collection inputResources) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ for (String inputResource : inputResources) {
+ appendToOutput(getResourceAsStream(inputResource), output);
+ }
+ return output.toByteArray();
+ }
+
+ public static InputStream getResourceAsStream(String resourceName) {
+ return TestDataUtil.class.getResourceAsStream(resourceName);
+ }
+
+ /**
+ * Null-safe equivalent of {@code a.equals(b)}.
+ */
+ public static boolean equals(Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals(b);
+ }
+
+ public static boolean iterEquals(Iterator a, Iterator b, EqualityChecker comparator) {
+ while (a.hasNext()) {
+ T aObject = a.next();
+ if (!b.hasNext()) {
+ return false;
+ }
+ T bObject = b.next();
+ if (!comparator.areEquals(aObject, bObject)) {
+ return false;
+ }
+ }
+
+ if (b.hasNext()) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ public static boolean iterEquals(Iterator a, Iterator b) {
+ return iterEquals(a, b, new EqualityChecker() {
+ @Override
+ public boolean areEquals(T lhs, T rhs) {
+ return TestDataUtil.equals(lhs, rhs);
+ }
+ });
+ }
+
+ public static interface EqualityChecker {
+ public boolean areEquals(T lhs, T rhs);
+ }
+
+
+ public static byte[] concatAll(java.util.List packets) {
+ byte[][] byteArrays = new byte[packets.size()][];
+ try {
+ for (int i = 0; i < packets.size(); i++) {
+ byteArrays[i] = packets.get(i).getEncoded();
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ return concatAll(byteArrays);
+ }
+
+ public static byte[] concatAll(byte[]... byteArrays) {
+ if (byteArrays.length == 1) {
+ return byteArrays[0];
+ } else if (byteArrays.length == 2) {
+ return concat(byteArrays[0], byteArrays[1]);
+ } else {
+ byte[] first = concat(byteArrays[0], byteArrays[1]);
+ byte[][] remainingArrays = new byte[byteArrays.length - 1][];
+ remainingArrays[0] = first;
+ System.arraycopy(byteArrays, 2, remainingArrays, 1, byteArrays.length - 2);
+ return concatAll(remainingArrays);
+ }
+ }
+
+ private static byte[] concat(byte[] a, byte[] b) {
+ int aLen = a.length;
+ int bLen = b.length;
+ byte[] c = new byte[aLen + bLen];
+ System.arraycopy(a, 0, c, 0, aLen);
+ System.arraycopy(b, 0, c, aLen, bLen);
+ return c;
+ }
+
+}
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/UncachedKeyringTestingHelper.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/UncachedKeyringTestingHelper.java
new file mode 100644
index 000000000..6467d3f32
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/support/UncachedKeyringTestingHelper.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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.support;
+
+import org.spongycastle.bcpg.BCPGKey;
+import org.spongycastle.bcpg.PublicKeyPacket;
+import org.spongycastle.bcpg.SignatureSubpacket;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
+import org.sufficientlysecure.keychain.service.OperationResultParcel;
+
+import java.util.Arrays;
+
+/**
+ * Created by art on 28/06/14.
+ */
+public class UncachedKeyringTestingHelper {
+
+ public static boolean compareRing(UncachedKeyRing keyRing1, UncachedKeyRing keyRing2) {
+ OperationResultParcel.OperationLog operationLog = new OperationResultParcel.OperationLog();
+ UncachedKeyRing canonicalized = keyRing1.canonicalize(operationLog, 0);
+
+ if (canonicalized == null) {
+ throw new AssertionError("Canonicalization failed; messages: [" + operationLog.toList() + "]");
+ }
+
+ return TestDataUtil.iterEquals(canonicalized.getPublicKeys(), keyRing2.getPublicKeys(), new
+ TestDataUtil.EqualityChecker() {
+ @Override
+ public boolean areEquals(UncachedPublicKey lhs, UncachedPublicKey rhs) {
+ return comparePublicKey(lhs, rhs);
+ }
+ });
+ }
+
+ public static boolean comparePublicKey(UncachedPublicKey key1, UncachedPublicKey key2) {
+ boolean equal = true;
+
+ if (key1.canAuthenticate() != key2.canAuthenticate()) {
+ return false;
+ }
+ if (key1.canCertify() != key2.canCertify()) {
+ return false;
+ }
+ if (key1.canEncrypt() != key2.canEncrypt()) {
+ return false;
+ }
+ if (key1.canSign() != key2.canSign()) {
+ return false;
+ }
+ if (key1.getAlgorithm() != key2.getAlgorithm()) {
+ return false;
+ }
+ if (key1.getBitStrength() != key2.getBitStrength()) {
+ return false;
+ }
+ if (!TestDataUtil.equals(key1.getCreationTime(), key2.getCreationTime())) {
+ return false;
+ }
+ if (!TestDataUtil.equals(key1.getExpiryTime(), key2.getExpiryTime())) {
+ return false;
+ }
+ if (!Arrays.equals(key1.getFingerprint(), key2.getFingerprint())) {
+ return false;
+ }
+ if (key1.getKeyId() != key2.getKeyId()) {
+ return false;
+ }
+ if (key1.getKeyUsage() != key2.getKeyUsage()) {
+ return false;
+ }
+ if (!TestDataUtil.equals(key1.getPrimaryUserId(), key2.getPrimaryUserId())) {
+ return false;
+ }
+
+ // Ooops, getPublicKey is due to disappear. But then how to compare?
+ if (!keysAreEqual(key1.getPublicKey(), key2.getPublicKey())) {
+ return false;
+ }
+
+ return equal;
+ }
+
+ public static boolean keysAreEqual(PGPPublicKey a, PGPPublicKey b) {
+
+ if (a.getAlgorithm() != b.getAlgorithm()) {
+ return false;
+ }
+
+ if (a.getBitStrength() != b.getBitStrength()) {
+ return false;
+ }
+
+ if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
+ return false;
+ }
+
+ if (!Arrays.equals(a.getFingerprint(), b.getFingerprint())) {
+ return false;
+ }
+
+ if (a.getKeyID() != b.getKeyID()) {
+ return false;
+ }
+
+ if (!pubKeyPacketsAreEqual(a.getPublicKeyPacket(), b.getPublicKeyPacket())) {
+ return false;
+ }
+
+ if (a.getVersion() != b.getVersion()) {
+ return false;
+ }
+
+ if (a.getValidDays() != b.getValidDays()) {
+ return false;
+ }
+
+ if (a.getValidSeconds() != b.getValidSeconds()) {
+ return false;
+ }
+
+ if (!Arrays.equals(a.getTrustData(), b.getTrustData())) {
+ return false;
+ }
+
+ if (!TestDataUtil.iterEquals(a.getUserIDs(), b.getUserIDs())) {
+ return false;
+ }
+
+ if (!TestDataUtil.iterEquals(a.getUserAttributes(), b.getUserAttributes(),
+ new TestDataUtil.EqualityChecker() {
+ public boolean areEquals(PGPUserAttributeSubpacketVector lhs, PGPUserAttributeSubpacketVector rhs) {
+ // For once, BC defines equals, so we use it implicitly.
+ return TestDataUtil.equals(lhs, rhs);
+ }
+ }
+ )) {
+ return false;
+ }
+
+
+ if (!TestDataUtil.iterEquals(a.getSignatures(), b.getSignatures(),
+ new TestDataUtil.EqualityChecker() {
+ public boolean areEquals(PGPSignature lhs, PGPSignature rhs) {
+ return signaturesAreEqual(lhs, rhs);
+ }
+ }
+ )) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean signaturesAreEqual(PGPSignature a, PGPSignature b) {
+
+ if (a.getVersion() != b.getVersion()) {
+ return false;
+ }
+
+ if (a.getKeyAlgorithm() != b.getKeyAlgorithm()) {
+ return false;
+ }
+
+ if (a.getHashAlgorithm() != b.getHashAlgorithm()) {
+ return false;
+ }
+
+ if (a.getSignatureType() != b.getSignatureType()) {
+ return false;
+ }
+
+ try {
+ if (!Arrays.equals(a.getSignature(), b.getSignature())) {
+ return false;
+ }
+ } catch (PGPException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ if (a.getKeyID() != b.getKeyID()) {
+ return false;
+ }
+
+ if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
+ return false;
+ }
+
+ if (!Arrays.equals(a.getSignatureTrailer(), b.getSignatureTrailer())) {
+ return false;
+ }
+
+ if (!subPacketVectorsAreEqual(a.getHashedSubPackets(), b.getHashedSubPackets())) {
+ return false;
+ }
+
+ if (!subPacketVectorsAreEqual(a.getUnhashedSubPackets(), b.getUnhashedSubPackets())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean subPacketVectorsAreEqual(PGPSignatureSubpacketVector aHashedSubPackets, PGPSignatureSubpacketVector bHashedSubPackets) {
+ for (int i = 0; i < Byte.MAX_VALUE; i++) {
+ if (!TestDataUtil.iterEquals(Arrays.asList(aHashedSubPackets.getSubpackets(i)).iterator(),
+ Arrays.asList(bHashedSubPackets.getSubpackets(i)).iterator(),
+ new TestDataUtil.EqualityChecker() {
+ @Override
+ public boolean areEquals(SignatureSubpacket lhs, SignatureSubpacket rhs) {
+ return signatureSubpacketsAreEqual(lhs, rhs);
+ }
+ }
+ )) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ private static boolean signatureSubpacketsAreEqual(SignatureSubpacket lhs, SignatureSubpacket rhs) {
+ if (lhs.getType() != rhs.getType()) {
+ return false;
+ }
+ if (!Arrays.equals(lhs.getData(), rhs.getData())) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean pubKeyPacketsAreEqual(PublicKeyPacket a, PublicKeyPacket b) {
+
+ if (a.getAlgorithm() != b.getAlgorithm()) {
+ return false;
+ }
+
+ if (!bcpgKeysAreEqual(a.getKey(), b.getKey())) {
+ return false;
+ }
+
+ if (!TestDataUtil.equals(a.getTime(), b.getTime())) {
+ return false;
+ }
+
+ if (a.getValidDays() != b.getValidDays()) {
+ return false;
+ }
+
+ if (a.getVersion() != b.getVersion()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean bcpgKeysAreEqual(BCPGKey a, BCPGKey b) {
+
+ if (!TestDataUtil.equals(a.getFormat(), b.getFormat())) {
+ return false;
+ }
+
+ if (!Arrays.equals(a.getEncoded(), b.getEncoded())) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ public void doTestCanonicalize(UncachedKeyRing inputKeyRing, UncachedKeyRing expectedKeyRing) {
+ if (!compareRing(inputKeyRing, expectedKeyRing)) {
+ throw new AssertionError("Expected [" + inputKeyRing + "] to match [" + expectedKeyRing + "]");
+ }
+ }
+
+}
diff --git a/OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpDecryptVerifyTest.java
similarity index 58%
rename from OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java
rename to OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpDecryptVerifyTest.java
index d759bce05..158650012 100644
--- a/OpenKeychain/src/test/java/tests/PgpDecryptVerifyTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpDecryptVerifyTest.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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 tests;
import org.junit.Assert;
@@ -5,7 +22,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.*;
import org.openintents.openpgp.OpenPgpSignatureResult;
-import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
+import org.sufficientlysecure.keychain.support.PgpVerifyTestingHelper;
@RunWith(RobolectricTestRunner.class)
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpKeyOperationTest.java
new file mode 100644
index 000000000..5c6072c25
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/PgpKeyOperationTest.java
@@ -0,0 +1,783 @@
+package org.sufficientlysecure.keychain.tests;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.robolectric.*;
+import org.robolectric.shadows.ShadowLog;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.Packet;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.SecretSubkeyPacket;
+import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.UserIDPacket;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.openpgp.PGPSignature;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Constants.choice.algorithm;
+import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
+import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
+import org.sufficientlysecure.keychain.pgp.WrappedSignature;
+import org.sufficientlysecure.keychain.service.OperationResultParcel;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
+import org.sufficientlysecure.keychain.support.KeyringBuilder;
+import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
+import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
+import org.sufficientlysecure.keychain.support.TestDataUtil;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Random;
+
+@RunWith(RobolectricTestRunner.class)
+@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
+public class PgpKeyOperationTest {
+
+ static UncachedKeyRing staticRing;
+ static String passphrase;
+
+ UncachedKeyRing ring;
+ PgpKeyOperation op;
+ SaveKeyringParcel parcel;
+ ArrayList onlyA = new ArrayList();
+ ArrayList onlyB = new ArrayList();
+
+ @BeforeClass public static void setUpOnce() throws Exception {
+ ShadowLog.stream = System.out;
+
+ {
+ String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
+ Random r = new Random();
+ StringBuilder passbuilder = new StringBuilder();
+ // 20% chance for an empty passphrase
+ for(int i = 0, j = r.nextInt(10) > 2 ? r.nextInt(20) : 0; i < j; i++) {
+ passbuilder.append(chars.charAt(r.nextInt(chars.length())));
+ }
+ passphrase = passbuilder.toString();
+ System.out.println("Passphrase is '" + passphrase + "'");
+ }
+
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.ENCRYPT_COMMS, null));
+
+ parcel.mAddUserIds.add("twi");
+ parcel.mAddUserIds.add("pink");
+ parcel.mNewPassphrase = passphrase;
+ PgpKeyOperation op = new PgpKeyOperation(null);
+
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ staticRing = op.createSecretKeyRing(parcel, log, 0);
+
+ Assert.assertNotNull("initial test key creation must succeed", staticRing);
+
+ // we sleep here for a second, to make sure all new certificates have different timestamps
+ Thread.sleep(1000);
+ }
+
+ @Before public void setUp() throws Exception {
+ // show Log.x messages in system.out
+ ShadowLog.stream = System.out;
+ ring = staticRing;
+
+ // setting up some parameters just to reduce code duplication
+ op = new PgpKeyOperation(new ProgressScaler(null, 0, 100, 100));
+
+ // set this up, gonna need it more than once
+ parcel = new SaveKeyringParcel();
+ parcel.mMasterKeyId = ring.getMasterKeyId();
+ parcel.mFingerprint = ring.getFingerprint();
+
+ }
+
+ @Test
+ public void testAlgorithmChoice() {
+
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+
+ {
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, new Random().nextInt(256)+255, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddUserIds.add("shy");
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+
+ Assert.assertNull("creating ring with < 512 bytes keysize should fail", ring);
+ }
+
+ {
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.elgamal, 1024, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddUserIds.add("shy");
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+
+ Assert.assertNull("creating ring with ElGamal master key should fail", ring);
+ }
+
+ {
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ 12345, 1024, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddUserIds.add("shy");
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+ Assert.assertNull("creating ring with bad algorithm choice should fail", ring);
+ }
+
+ {
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
+ parcel.mAddUserIds.add("shy");
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+ Assert.assertNull("creating ring with non-certifying master key should fail", ring);
+ }
+
+ {
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+ Assert.assertNull("creating ring without user ids should fail", ring);
+ }
+
+ {
+ parcel.reset();
+ parcel.mAddUserIds.add("shy");
+ parcel.mNewPassphrase = passphrase;
+
+ UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
+ Assert.assertNull("creating ring without subkeys should fail", ring);
+ }
+
+ }
+
+ @Test
+ // this is a special case since the flags are in user id certificates rather than
+ // subkey binding certificates
+ public void testMasterFlags() throws Exception {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, null));
+ parcel.mAddUserIds.add("luna");
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ ring = op.createSecretKeyRing(parcel, log, 0);
+
+ Assert.assertEquals("the keyring should contain only the master key",
+ 1, ring.getAvailableSubkeys().size());
+ Assert.assertEquals("first (master) key must have both flags",
+ KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, ring.getPublicKey().getKeyUsage());
+
+ }
+
+ @Test
+ public void testCreatedKey() throws Exception {
+
+ // an empty modification should change nothing. this also ensures the keyring
+ // is constant through canonicalization.
+ // applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertNotNull("key creation failed", ring);
+
+ Assert.assertNull("primary user id must be empty",
+ ring.getPublicKey().getPrimaryUserId());
+
+ Assert.assertEquals("number of user ids must be two",
+ 2, ring.getPublicKey().getUnorderedUserIds().size());
+
+ Assert.assertEquals("number of subkeys must be three",
+ 3, ring.getAvailableSubkeys().size());
+
+ Assert.assertTrue("key ring should have been created in the last 120 seconds",
+ ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
+
+ Assert.assertNull("key ring should not expire",
+ ring.getPublicKey().getExpiryTime());
+
+ Iterator it = ring.getPublicKeys();
+
+ Assert.assertEquals("first (master) key can certify",
+ KeyFlags.CERTIFY_OTHER, it.next().getKeyUsage());
+
+ UncachedPublicKey signingKey = it.next();
+ Assert.assertEquals("second key can sign",
+ KeyFlags.SIGN_DATA, signingKey.getKeyUsage());
+ ArrayList sigs = signingKey.getSignatures().next().getEmbeddedSignatures();
+ Assert.assertEquals("signing key signature should have one embedded signature",
+ 1, sigs.size());
+ Assert.assertEquals("embedded signature should be of primary key binding type",
+ PGPSignature.PRIMARYKEY_BINDING, sigs.get(0).getSignatureType());
+ Assert.assertEquals("primary key binding signature issuer should be signing subkey",
+ signingKey.getKeyId(), sigs.get(0).getKeyId());
+
+ Assert.assertEquals("third key can encrypt",
+ KeyFlags.ENCRYPT_COMMS, it.next().getKeyUsage());
+
+ }
+
+ @Test
+ public void testBadKeyModification() throws Exception {
+
+ {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ // off by one
+ parcel.mMasterKeyId = ring.getMasterKeyId() -1;
+ parcel.mFingerprint = ring.getFingerprint();
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("keyring modification with bad master key id should fail", modified);
+ }
+
+ {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ // off by one
+ parcel.mMasterKeyId = null;
+ parcel.mFingerprint = ring.getFingerprint();
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("keyring modification with null master key id should fail", modified);
+ }
+
+ {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mMasterKeyId = ring.getMasterKeyId();
+ parcel.mFingerprint = ring.getFingerprint();
+ // some byte, off by one
+ parcel.mFingerprint[5] += 1;
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
+ }
+
+ {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mMasterKeyId = ring.getMasterKeyId();
+ parcel.mFingerprint = null;
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("keyring modification with null fingerprint should fail", modified);
+ }
+
+ {
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, "bad passphrase", log, 0);
+
+ Assert.assertNull("keyring modification with bad passphrase should fail", modified);
+ }
+
+ }
+
+ @Test
+ public void testSubkeyAdd() throws Exception {
+
+ long expiry = new Date().getTime() / 1000 + 159;
+ int flags = KeyFlags.SIGN_DATA;
+ int bits = 1024 + new Random().nextInt(8);
+ parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, bits, flags, expiry));
+
+ UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertEquals("no extra packets in original", 0, onlyA.size());
+ Assert.assertEquals("exactly two extra packets in modified", 2, onlyB.size());
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be secret subkey", p instanceof SecretSubkeyPacket);
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(1).buf)).readPacket();
+ Assert.assertTrue("second new packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be subkey binding certificate",
+ PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ // get new key from ring. it should be the last one (add a check to make sure?)
+ UncachedPublicKey newKey = null;
+ {
+ Iterator it = modified.getPublicKeys();
+ while (it.hasNext()) {
+ newKey = it.next();
+ }
+ }
+
+ Assert.assertNotNull("new key is not null", newKey);
+ Assert.assertNotNull("added key must have an expiry date",
+ newKey.getExpiryTime());
+ Assert.assertEquals("added key must have expected expiry date",
+ expiry, newKey.getExpiryTime().getTime()/1000);
+ Assert.assertEquals("added key must have expected flags",
+ flags, newKey.getKeyUsage());
+ Assert.assertEquals("added key must have expected bitsize",
+ bits, newKey.getBitStrength());
+
+ { // bad keysize should fail
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 77, KeyFlags.SIGN_DATA, null));
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("creating a subkey with keysize < 512 should fail", modified);
+ }
+
+ { // a past expiry should fail
+ parcel.reset();
+ parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA,
+ new Date().getTime()/1000-10));
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("creating subkey with past expiry date should fail", modified);
+ }
+
+ }
+
+ @Test
+ public void testSubkeyModify() throws Exception {
+
+ long expiry = new Date().getTime()/1000 + 1024;
+ long keyId;
+ {
+ Iterator it = ring.getPublicKeys();
+ it.next();
+ keyId = it.next().getKeyId();
+ }
+
+ UncachedKeyRing modified = ring;
+ {
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, expiry));
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("one extra packet in original", 1, onlyA.size());
+ Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
+
+ Assert.assertEquals("old packet must be signature",
+ PacketTags.SIGNATURE, onlyA.get(0).tag);
+
+ Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be subkey binding certificate",
+ PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ Assert.assertNotNull("modified key must have an expiry date",
+ modified.getPublicKey(keyId).getExpiryTime());
+ Assert.assertEquals("modified key must have expected expiry date",
+ expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
+ Assert.assertEquals("modified key must have same flags as before",
+ ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
+ }
+
+ {
+ int flags = KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS;
+ parcel.reset();
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, flags, null));
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("old packet must be signature",
+ PacketTags.SIGNATURE, onlyA.get(0).tag);
+
+ Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be subkey binding certificate",
+ PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ Assert.assertEquals("modified key must have expected flags",
+ flags, modified.getPublicKey(keyId).getKeyUsage());
+ Assert.assertNotNull("key must retain its expiry",
+ modified.getPublicKey(keyId).getExpiryTime());
+ Assert.assertEquals("key expiry must be unchanged",
+ expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
+ }
+
+ { // a past expiry should fail
+ parcel.reset();
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("setting subkey expiry to a past date should fail", modified);
+ }
+
+ { // modifying nonexistent keyring should fail
+ parcel.reset();
+ parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null));
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("modifying non-existent subkey should fail", modified);
+ }
+
+ }
+
+ @Test
+ public void testSubkeyRevoke() throws Exception {
+
+ long keyId;
+ {
+ Iterator it = ring.getPublicKeys();
+ it.next();
+ keyId = it.next().getKeyId();
+ }
+
+ int flags = ring.getPublicKey(keyId).getKeyUsage();
+
+ UncachedKeyRing modified;
+
+ {
+
+ parcel.reset();
+ parcel.mRevokeSubKeys.add(123L);
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("revoking a nonexistent subkey should fail", otherModified);
+
+ }
+
+ { // revoked second subkey
+
+ parcel.reset();
+ parcel.mRevokeSubKeys.add(keyId);
+
+ modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertEquals("no extra packets in original", 0, onlyA.size());
+ Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be secret subkey", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be subkey binding certificate",
+ PGPSignature.SUBKEY_REVOCATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ Assert.assertTrue("subkey must actually be revoked",
+ modified.getPublicKey(keyId).isRevoked());
+ }
+
+ { // re-add second subkey
+
+ parcel.reset();
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, null));
+
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("exactly two outdated packets in original", 2, onlyA.size());
+ Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
+ Assert.assertTrue("first outdated packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("first outdated signature type must be subkey binding certification",
+ PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("first outdated signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
+ Assert.assertTrue("second outdated packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("second outdated signature type must be subkey revocation",
+ PGPSignature.SUBKEY_REVOCATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("second outdated signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("new packet must be signature ", p instanceof SignaturePacket);
+ Assert.assertEquals("new signature type must be subkey binding certification",
+ PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ Assert.assertFalse("subkey must no longer be revoked",
+ modified.getPublicKey(keyId).isRevoked());
+ Assert.assertEquals("subkey must have the same usage flags as before",
+ flags, modified.getPublicKey(keyId).getKeyUsage());
+
+ }
+ }
+
+ @Test
+ public void testUserIdRevoke() throws Exception {
+
+ UncachedKeyRing modified;
+ String uid = ring.getPublicKey().getUnorderedUserIds().get(1);
+
+ { // revoke second user id
+
+ parcel.mRevokeUserIds.add(uid);
+
+ modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertEquals("no extra packets in original", 0, onlyA.size());
+ Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be secret subkey", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be subkey binding certificate",
+ PGPSignature.CERTIFICATION_REVOCATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ }
+
+ { // re-add second user id
+
+ parcel.reset();
+ parcel.mChangePrimaryUserId = uid;
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(modified.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("setting primary user id to a revoked user id should fail", otherModified);
+
+ }
+
+ { // re-add second user id
+
+ parcel.reset();
+ parcel.mAddUserIds.add(uid);
+
+ applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("exactly two outdated packets in original", 2, onlyA.size());
+ Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
+ Assert.assertTrue("first outdated packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("first outdated signature type must be positive certification",
+ PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("first outdated signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
+ Assert.assertTrue("second outdated packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("second outdated signature type must be certificate revocation",
+ PGPSignature.CERTIFICATION_REVOCATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("second outdated signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("new packet must be signature ", p instanceof SignaturePacket);
+ Assert.assertEquals("new signature type must be positive certification",
+ PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
+ Assert.assertEquals("signature must have been created by master key",
+ ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
+ }
+
+ }
+
+ @Test
+ public void testUserIdAdd() throws Exception {
+
+ parcel.mAddUserIds.add("rainbow");
+
+ UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertTrue("keyring must contain added user id",
+ modified.getPublicKey().getUnorderedUserIds().contains("rainbow"));
+
+ Assert.assertEquals("no extra packets in original", 0, onlyA.size());
+ Assert.assertEquals("exactly two extra packets in modified", 2, onlyB.size());
+
+ Assert.assertTrue("keyring must contain added user id",
+ modified.getPublicKey().getUnorderedUserIds().contains("rainbow"));
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be user id", p instanceof UserIDPacket);
+ Assert.assertEquals("user id packet must match added user id",
+ "rainbow", ((UserIDPacket) p).getID());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(1).buf)).readPacket();
+ Assert.assertTrue("second new packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be positive certification",
+ PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
+
+ }
+
+ @Test
+ public void testUserIdPrimary() throws Exception {
+
+ UncachedKeyRing modified = ring;
+ String uid = ring.getPublicKey().getUnorderedUserIds().get(1);
+
+ { // first part, add new user id which is also primary
+ parcel.mAddUserIds.add("jack");
+ parcel.mChangePrimaryUserId = "jack";
+
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("primary user id must be the one added",
+ "jack", modified.getPublicKey().getPrimaryUserId());
+ }
+
+ { // second part, change primary to a different one
+ parcel.reset();
+ parcel.mChangePrimaryUserId = uid;
+
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
+
+ Assert.assertEquals("old keyring must have two outdated certificates", 2, onlyA.size());
+ Assert.assertEquals("new keyring must have two new packets", 2, onlyB.size());
+
+ Assert.assertEquals("primary user id must be the one changed to",
+ "pink", modified.getPublicKey().getPrimaryUserId());
+ }
+
+ { // third part, change primary to a non-existent one
+ parcel.reset();
+ //noinspection SpellCheckingInspection
+ parcel.mChangePrimaryUserId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+
+ Assert.assertNull("changing primary user id to a non-existent one should fail", modified);
+ }
+
+ // check for revoked primary user id already done in revoke test
+
+ }
+
+
+ private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
+ UncachedKeyRing ring,
+ ArrayList onlyA,
+ ArrayList onlyB) {
+ return applyModificationWithChecks(parcel, ring, onlyA, onlyB, true, true);
+ }
+
+ // applies a parcel modification while running some integrity checks
+ private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
+ UncachedKeyRing ring,
+ ArrayList onlyA,
+ ArrayList onlyB,
+ boolean canonicalize,
+ boolean constantCanonicalize) {
+ try {
+
+ Assert.assertTrue("modified keyring must be secret", ring.isSecret());
+ WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
+
+ PgpKeyOperation op = new PgpKeyOperation(null);
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
+ Assert.assertNotNull("key modification failed", rawModified);
+
+ if (!canonicalize) {
+ Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
+ ring.getEncoded(), rawModified.getEncoded(), onlyA, onlyB));
+ return rawModified;
+ }
+
+ UncachedKeyRing modified = rawModified.canonicalize(log, 0);
+ if (constantCanonicalize) {
+ Assert.assertTrue("key must be constant through canonicalization",
+ !KeyringTestingHelper.diffKeyrings(
+ modified.getEncoded(), rawModified.getEncoded(), onlyA, onlyB)
+ );
+ }
+ Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
+ ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
+ return modified;
+
+ } catch (IOException e) {
+ throw new AssertionFailedError("error during encoding!");
+ }
+ }
+
+ @Test
+ public void testVerifySuccess() throws Exception {
+
+ UncachedKeyRing expectedKeyRing = KeyringBuilder.correctRing();
+ UncachedKeyRing inputKeyRing = KeyringBuilder.ringWithExtraIncorrectSignature();
+
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ UncachedKeyRing canonicalizedRing = inputKeyRing.canonicalize(log, 0);
+
+ if (canonicalizedRing == null) {
+ throw new AssertionError("Canonicalization failed; messages: [" + log + "]");
+ }
+
+ ArrayList onlyA = new ArrayList();
+ ArrayList onlyB = new ArrayList();
+ //noinspection unchecked
+ Assert.assertTrue("keyrings differ", !KeyringTestingHelper.diffKeyrings(
+ expectedKeyRing.getEncoded(), expectedKeyRing.getEncoded(), onlyA, onlyB));
+
+ }
+
+ /**
+ * Just testing my own test code. Should really be using a library for this.
+ */
+ @Test
+ public void testConcat() throws Exception {
+ byte[] actual = TestDataUtil.concatAll(new byte[]{1}, new byte[]{2,-2}, new byte[]{5},new byte[]{3});
+ byte[] expected = new byte[]{1,2,-2,5,3};
+ Assert.assertEquals(java.util.Arrays.toString(expected), java.util.Arrays.toString(actual));
+ }
+
+
+}
diff --git a/OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/ProviderHelperKeyringTest.java
similarity index 79%
rename from OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java
rename to OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/ProviderHelperKeyringTest.java
index 3d48c2f97..ab5c1f1ec 100644
--- a/OpenKeychain/src/test/java/tests/ProviderHelperKeyringTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/ProviderHelperKeyringTest.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) Art O Cathain
+ *
+ * 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 tests;
import java.util.Collections;
@@ -9,9 +26,7 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.*;
-import org.openintents.openpgp.OpenPgpSignatureResult;
-import org.sufficientlysecure.keychain.testsupport.KeyringTestingHelper;
-import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
+import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
@RunWith(RobolectricTestRunner.class)
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
@@ -24,7 +39,7 @@ public class ProviderHelperKeyringTest {
)));
}
- @Test
+ // @Test
public void testSavePublicKeyringRsa() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
"000001-006.public_key",
@@ -45,7 +60,7 @@ public class ProviderHelperKeyringTest {
))));
}
- @Test
+ // @Test
public void testSavePublicKeyringDsa() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
"000016-006.public_key",
@@ -62,7 +77,7 @@ public class ProviderHelperKeyringTest {
))));
}
- @Test
+ // @Test
public void testSavePublicKeyringDsa2() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
"000027-006.public_key",
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/UncachedKeyringTest.java
new file mode 100644
index 000000000..66e31c272
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/tests/UncachedKeyringTest.java
@@ -0,0 +1,106 @@
+package org.sufficientlysecure.keychain.tests;
+
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Before;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowLog;
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.Packet;
+import org.spongycastle.bcpg.PacketTags;
+import org.spongycastle.bcpg.UserIDPacket;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
+import org.sufficientlysecure.keychain.pgp.WrappedSignature;
+import org.sufficientlysecure.keychain.service.OperationResultParcel;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
+import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+@RunWith(RobolectricTestRunner.class)
+@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
+public class UncachedKeyringTest {
+
+ static UncachedKeyRing staticRing;
+ UncachedKeyRing ring;
+ ArrayList onlyA = new ArrayList();
+ ArrayList onlyB = new ArrayList();
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ ShadowLog.stream = System.out;
+
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Constants.choice.algorithm.rsa, 1024, KeyFlags.ENCRYPT_COMMS, null));
+
+ parcel.mAddUserIds.add("twi");
+ parcel.mAddUserIds.add("pink");
+ // passphrase is tested in PgpKeyOperationTest, just use empty here
+ parcel.mNewPassphrase = "";
+ PgpKeyOperation op = new PgpKeyOperation(null);
+
+ OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
+ staticRing = op.createSecretKeyRing(parcel, log, 0);
+
+ Assert.assertNotNull("initial test key creation must succeed", staticRing);
+
+ // we sleep here for a second, to make sure all new certificates have different timestamps
+ Thread.sleep(1000);
+ }
+
+ @Before public void setUp() throws Exception {
+ // show Log.x messages in system.out
+ ShadowLog.stream = System.out;
+ ring = staticRing;
+ }
+
+ @Test public void testGeneratedRingStructure() throws Exception {
+
+ Iterator it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
+
+ Assert.assertEquals("packet #1 should be secret key",
+ PacketTags.SECRET_KEY, it.next().tag);
+
+ Assert.assertEquals("packet #2 should be secret key",
+ PacketTags.USER_ID, it.next().tag);
+ Assert.assertEquals("packet #3 should be secret key",
+ PacketTags.SIGNATURE, it.next().tag);
+
+ Assert.assertEquals("packet #4 should be secret key",
+ PacketTags.USER_ID, it.next().tag);
+ Assert.assertEquals("packet #5 should be secret key",
+ PacketTags.SIGNATURE, it.next().tag);
+
+ Assert.assertEquals("packet #6 should be secret key",
+ PacketTags.SECRET_SUBKEY, it.next().tag);
+ Assert.assertEquals("packet #7 should be secret key",
+ PacketTags.SIGNATURE, it.next().tag);
+
+ Assert.assertEquals("packet #8 should be secret key",
+ PacketTags.SECRET_SUBKEY, it.next().tag);
+ Assert.assertEquals("packet #9 should be secret key",
+ PacketTags.SIGNATURE, it.next().tag);
+
+ Assert.assertFalse("exactly 9 packets total", it.hasNext());
+
+ Assert.assertArrayEquals("created keyring should be constant through canonicalization",
+ ring.getEncoded(), ring.canonicalize(log, 0).getEncoded());
+
+ }
+
+}
diff --git a/OpenKeychain-Test/src/test/resources/public-key-canonicalize.blob b/OpenKeychain-Test/src/test/resources/public-key-canonicalize.blob
new file mode 100644
index 000000000..3450824c1
Binary files /dev/null and b/OpenKeychain-Test/src/test/resources/public-key-canonicalize.blob differ
diff --git a/OpenKeychain/src/test/resources/public-key-for-sample.blob b/OpenKeychain-Test/src/test/resources/public-key-for-sample.blob
similarity index 100%
rename from OpenKeychain/src/test/resources/public-key-for-sample.blob
rename to OpenKeychain-Test/src/test/resources/public-key-for-sample.blob
diff --git a/OpenKeychain/src/test/resources/sample-altered.txt b/OpenKeychain-Test/src/test/resources/sample-altered.txt
similarity index 100%
rename from OpenKeychain/src/test/resources/sample-altered.txt
rename to OpenKeychain-Test/src/test/resources/sample-altered.txt
diff --git a/OpenKeychain/src/test/resources/sample.txt b/OpenKeychain-Test/src/test/resources/sample.txt
similarity index 100%
rename from OpenKeychain/src/test/resources/sample.txt
rename to OpenKeychain-Test/src/test/resources/sample.txt
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index f419141b4..a6c01543c 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -21,13 +21,6 @@ dependencies {
compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib')
-
- // Unit tests are run with Robolectric
- testCompile 'junit:junit:4.11'
- testCompile 'org.robolectric:robolectric:2.3'
- testCompile 'com.squareup:fest-android:1.0.8'
- testCompile 'com.google.android:android:4.1.1.4'
- // compile dependencies are automatically also included in testCompile
}
android {
@@ -86,7 +79,7 @@ android {
// NOTE: This disables Lint!
tasks.whenTaskAdded { task ->
- if (task.name.equals("lint")) {
+ if (task.name.contains("lint")) {
task.enabled = false
}
}
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 2283235e7..af09019e8 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -81,9 +81,14 @@
+
it = modifiedPublicKey.getSignaturesForID(userId);
if (it != null) {
for (PGPSignature cert : new IterableIterator(it)) {
- // if it's not a self cert, never mind
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
- continue;
+ // foreign certificate?! error error error
+ log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
+ return null;
}
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
@@ -314,27 +336,31 @@ public class PgpKeyOperation {
}
// if it's supposed to be primary, we can do that here as well
- boolean isPrimary = saveParcel.changePrimaryUserId != null
- && userId.equals(saveParcel.changePrimaryUserId);
+ boolean isPrimary = saveParcel.mChangePrimaryUserId != null
+ && userId.equals(saveParcel.mChangePrimaryUserId);
// generate and add new certificate
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
- masterPublicKey, userId, isPrimary);
- modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
+ masterPublicKey, userId, isPrimary, masterKeyFlags);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
}
// 2b. Add revocations for revoked user ids
- for (String userId : saveParcel.revokeUserIds) {
+ for (String userId : saveParcel.mRevokeUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
- // a duplicate revocatin will be removed during canonicalization, so no need to
+ // a duplicate revocation will be removed during canonicalization, so no need to
// take care of that here.
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId);
- modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
}
// 3. If primary user id changed, generate new certificates for both old and new
- if (saveParcel.changePrimaryUserId != null) {
+ if (saveParcel.mChangePrimaryUserId != null) {
+
+ // keep track if we actually changed one
+ boolean ok = false;
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
+ indent += 1;
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
// noinspection unchecked
@@ -343,10 +369,11 @@ public class PgpKeyOperation {
PGPSignature currentCert = null;
// noinspection unchecked
for (PGPSignature cert : new IterableIterator(
- masterPublicKey.getSignaturesForID(userId))) {
- // if it's not a self cert, never mind
+ modifiedPublicKey.getSignaturesForID(userId))) {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
- continue;
+ // foreign certificate?! error error error
+ log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
+ return null;
}
// we know from canonicalization that if there is any revocation here, it
// is valid and not superseded by a newer certification.
@@ -373,7 +400,7 @@ public class PgpKeyOperation {
// we definitely should not update certifications of revoked keys, so just leave it.
if (isRevoked) {
// revoked user ids cannot be primary!
- if (userId.equals(saveParcel.changePrimaryUserId)) {
+ if (userId.equals(saveParcel.mChangePrimaryUserId)) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
return null;
}
@@ -383,14 +410,16 @@ public class PgpKeyOperation {
// if this is~ the/a primary user id
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
// if it's the one we want, just leave it as is
- if (userId.equals(saveParcel.changePrimaryUserId)) {
+ if (userId.equals(saveParcel.mChangePrimaryUserId)) {
+ ok = true;
continue;
}
// otherwise, generate new non-primary certification
+ log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature(
- masterPrivateKey, masterPublicKey, userId, false);
+ masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
continue;
@@ -399,20 +428,28 @@ public class PgpKeyOperation {
// if we are here, this is not currently a primary user id
// if it should be
- if (userId.equals(saveParcel.changePrimaryUserId)) {
+ if (userId.equals(saveParcel.mChangePrimaryUserId)) {
// add shiny new primary user id certificate
+ log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent);
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature(
- masterPrivateKey, masterPublicKey, userId, true);
+ masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
+ ok = true;
}
// user id is not primary and is not supposed to be - nothing to do here.
}
+ indent -= 1;
+
+ if (!ok) {
+ log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
+ return null;
+ }
}
// Update the secret key ring
@@ -423,9 +460,16 @@ public class PgpKeyOperation {
}
// 4a. For each subkey change, generate new subkey binding certificate
- for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
+ for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
+
+ // TODO allow changes in master key? this implies generating new user id certs...
+ if (change.mKeyId == masterPublicKey.getKeyID()) {
+ Log.e(Constants.TAG, "changing the master key not supported");
+ return null;
+ }
+
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
@@ -434,22 +478,45 @@ public class PgpKeyOperation {
}
PGPPublicKey pKey = sKey.getPublicKey();
- if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
+ // expiry must not be in the past
+ if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
return null;
}
- // generate and add new signature. we can be sloppy here and just leave the old one,
- // it will be removed during canonicalization
+ // keep old flags, or replace with new ones
+ int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
+ long expiry;
+ if (change.mExpiry == null) {
+ long valid = pKey.getValidSeconds();
+ expiry = valid == 0
+ ? 0
+ : pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds();
+ } else {
+ expiry = change.mExpiry;
+ }
+
+ // drop all old signatures, they will be superseded by the new one
+ //noinspection unchecked
+ for (PGPSignature sig : new IterableIterator(pKey.getSignatures())) {
+ // special case: if there is a revocation, don't use expiry from before
+ if (change.mExpiry == null
+ && sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
+ expiry = 0;
+ }
+ pKey = PGPPublicKey.removeCertification(pKey, sig);
+ }
+
+ // generate and add new signature
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
- sKey, pKey, change.mFlags, change.mExpiry, passphrase);
+ sKey, pKey, flags, expiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
// 4b. For each subkey revocation, generate new subkey revocation certificate
- for (long revocation : saveParcel.revokeSubKeys) {
+ for (long revocation : saveParcel.mRevokeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
indent, PgpKeyHelper.convertKeyIdToHex(revocation));
PGPSecretKey sKey = sKR.getSecretKey(revocation);
@@ -468,52 +535,51 @@ public class PgpKeyOperation {
}
// 5. Generate and add new subkeys
- for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
- try {
+ for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) {
- if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) {
- log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
- return null;
- }
-
- log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
-
- // generate a new secret key (privkey only for now)
- PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
-
- // add subkey binding signature (making this a sub rather than master key)
- PGPPublicKey pKey = keyPair.getPublicKey();
- PGPSignature cert = generateSubkeyBindingSignature(
- masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
- add.mFlags, add.mExpiry);
- pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
-
- PGPSecretKey sKey; {
- // define hashing and signing algos
- PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(HashAlgorithmTags.SHA1);
-
- // Build key encrypter and decrypter based on passphrase
- PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
- PGPEncryptedData.CAST5, sha1Calc)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
-
- sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
- sha1Calc, false, keyEncryptor);
- }
-
- log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
- indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
-
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
-
- } catch (PgpGeneralMsgIdException e) {
+ if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) {
+ log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
return null;
}
+
+ log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
+
+ // generate a new secret key (privkey only for now)
+ PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
+ if(keyPair == null) {
+ return null;
+ }
+
+ // add subkey binding signature (making this a sub rather than master key)
+ PGPPublicKey pKey = keyPair.getPublicKey();
+ PGPSignature cert = generateSubkeyBindingSignature(
+ masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
+ add.mFlags, add.mExpiry == null ? 0 : add.mExpiry);
+ pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
+
+ PGPSecretKey sKey; {
+ // define hashing and signing algos
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
+ .build().get(HashAlgorithmTags.SHA1);
+
+ // Build key encrypter and decrypter based on passphrase
+ PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
+ PGPEncryptedData.CAST5, sha1Calc)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
+
+ sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
+ sha1Calc, false, keyEncryptor);
+ }
+
+ log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
+ indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
+
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+
}
// 6. If requested, change passphrase
- if (saveParcel.newPassphrase != null) {
+ if (saveParcel.mNewPassphrase != null) {
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(HashAlgorithmTags.SHA1);
@@ -523,7 +589,7 @@ public class PgpKeyOperation {
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- saveParcel.newPassphrase.toCharArray());
+ saveParcel.mNewPassphrase.toCharArray());
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
}
@@ -546,7 +612,7 @@ public class PgpKeyOperation {
}
private static PGPSignature generateUserIdSignature(
- PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary)
+ PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
@@ -558,6 +624,7 @@ public class PgpKeyOperation {
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
subHashedPacketsGen.setPrimaryUserID(false, primary);
+ subHashedPacketsGen.setKeyFlags(false, flags);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
@@ -599,7 +666,7 @@ public class PgpKeyOperation {
private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
- PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
+ PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase)
throws IOException, PGPException, SignatureException {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
@@ -611,7 +678,7 @@ public class PgpKeyOperation {
private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
- PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry)
+ PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
throws IOException, PGPException, SignatureException {
// date for signing
@@ -640,17 +707,9 @@ public class PgpKeyOperation {
hashedPacketsGen.setKeyFlags(false, flags);
}
- if (expiry != null) {
- Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
- creationDate.setTime(pKey.getCreationTime());
-
- // (Just making sure there's no programming error here, this MUST have been checked above!)
- if (new Date(expiry).before(todayDate)) {
- throw new RuntimeException("Bad subkey creation date, this is a bug!");
- }
- hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
- } else {
- hashedPacketsGen.setKeyExpirationTime(false, 0);
+ if (expiry > 0) {
+ long creationTime = pKey.getCreationTime().getTime() / 1000;
+ hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);
}
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
@@ -665,4 +724,22 @@ public class PgpKeyOperation {
}
+ /** Returns all flags valid for this key.
+ *
+ * This method does not do any validity checks on the signature, so it should not be used on
+ * a non-canonicalized key!
+ *
+ */
+ private static int readKeyFlags(PGPPublicKey key) {
+ int flags = 0;
+ //noinspection unchecked
+ for(PGPSignature sig : new IterableIterator(key.getSignatures())) {
+ if (!sig.hasSubpackets()) {
+ continue;
+ }
+ flags |= sig.getHashedSubPackets().getKeyFlags();
+ }
+ return flags;
+ }
+
}
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 441e2762a..9ddfd3405 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -81,6 +81,10 @@ public class UncachedKeyRing {
return new UncachedPublicKey(mRing.getPublicKey());
}
+ public UncachedPublicKey getPublicKey(long keyId) {
+ return new UncachedPublicKey(mRing.getPublicKey(keyId));
+ }
+
public Iterator getPublicKeys() {
final Iterator it = mRing.getPublicKeys();
return new Iterator() {
@@ -558,7 +562,7 @@ public class UncachedKeyRing {
// make sure the certificate checks out
try {
cert.init(masterKey);
- if (!cert.verifySignature(key)) {
+ if (!cert.verifySignature(masterKey, key)) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent);
badCerts += 1;
continue;
@@ -744,8 +748,12 @@ public class UncachedKeyRing {
}
- log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW,
- indent, Integer.toString(newCerts));
+ if (newCerts > 0) {
+ log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, indent,
+ Integer.toString(newCerts));
+ } else {
+ log.add(LogLevel.DEBUG, LogType.MSG_MG_UNCHANGED, indent);
+ }
return new UncachedKeyRing(result);
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 33db7771b..ef5aa8e86 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
@@ -9,6 +9,7 @@ import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
+import org.sufficientlysecure.keychain.util.Log;
import java.security.SignatureException;
import java.util.ArrayList;
@@ -44,14 +45,19 @@ public class UncachedPublicKey {
}
public Date getExpiryTime() {
- Date creationDate = getCreationTime();
- if (mPublicKey.getValidDays() == 0) {
+ long seconds = mPublicKey.getValidSeconds();
+ if (seconds > Integer.MAX_VALUE) {
+ Log.e(Constants.TAG, "error, expiry time too large");
+ return null;
+ }
+ if (seconds == 0) {
// no expiry
return null;
}
+ Date creationDate = getCreationTime();
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate);
- calendar.add(Calendar.DATE, mPublicKey.getValidDays());
+ calendar.add(Calendar.SECOND, (int) seconds);
return calendar.getTime();
}
@@ -77,26 +83,76 @@ public class UncachedPublicKey {
return mPublicKey.getBitStrength();
}
+ /** Returns the primary user id, as indicated by the public key's self certificates.
+ *
+ * This is an expensive operation, since potentially a lot of certificates (and revocations)
+ * have to be checked, and even then the result is NOT guaranteed to be constant through a
+ * canonicalization operation.
+ *
+ * Returns null if there is no primary user id (as indicated by certificates)
+ *
+ */
public String getPrimaryUserId() {
+ String found = null;
+ PGPSignature foundSig = null;
for (String userId : new IterableIterator(mPublicKey.getUserIDs())) {
+ PGPSignature revocation = null;
+
for (PGPSignature sig : new IterableIterator(mPublicKey.getSignaturesForID(userId))) {
- if (sig.getHashedSubPackets() != null
- && sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
- try {
+ try {
+
+ // if this is a revocation, this is not the user id
+ if (sig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
+ // make sure it's actually valid
+ sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
+ if (!sig.verifyCertification(userId, mPublicKey)) {
+ continue;
+ }
+ if (found != null && found.equals(userId)) {
+ found = null;
+ }
+ revocation = sig;
+ // this revocation may still be overridden by a newer cert
+ continue;
+ }
+
+ if (sig.getHashedSubPackets() != null && sig.getHashedSubPackets().isPrimaryUserID()) {
+ if (foundSig != null && sig.getCreationTime().before(foundSig.getCreationTime())) {
+ continue;
+ }
+ // ignore if there is a newer revocation for this user id
+ if (revocation != null && sig.getCreationTime().before(revocation.getCreationTime())) {
+ continue;
+ }
// make sure it's actually valid
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
if (sig.verifyCertification(userId, mPublicKey)) {
- return userId;
+ found = userId;
+ foundSig = sig;
+ // this one can't be relevant anymore at this point
+ revocation = null;
}
- } catch (Exception e) {
- // nothing bad happens, the key is just not considered the primary key id
}
- }
+ } catch (Exception e) {
+ // nothing bad happens, the key is just not considered the primary key id
+ }
}
}
- return null;
+ return found;
+ }
+
+ /**
+ * Returns primary user id if existing. If not, return first encountered user id.
+ */
+ public String getPrimaryUserIdWithFallback() {
+ String userId = getPrimaryUserId();
+ if (userId == null) {
+ userId = (String) mPublicKey.getUserIDs().next();
+ }
+ return userId;
}
public ArrayList getUnorderedUserIds() {
@@ -186,6 +242,21 @@ public class UncachedPublicKey {
return mPublicKey;
}
+ public Iterator getSignatures() {
+ final Iterator it = mPublicKey.getSignatures();
+ return new Iterator() {
+ public void remove() {
+ it.remove();
+ }
+ public WrappedSignature next() {
+ return new WrappedSignature(it.next());
+ }
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ };
+ }
+
public Iterator getSignaturesForId(String userId) {
final Iterator it = mPublicKey.getSignaturesForID(userId);
return new Iterator() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java
index 6f3068261..a054255dc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java
@@ -39,8 +39,12 @@ public abstract class WrappedKeyRing extends KeyRing {
}
public String getPrimaryUserId() throws PgpGeneralException {
- return (String) getRing().getPublicKey().getUserIDs().next();
- };
+ return getPublicKey().getPrimaryUserId();
+ }
+
+ public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
+ return getPublicKey().getPrimaryUserIdWithFallback();
+ }
public boolean isRevoked() throws PgpGeneralException {
// Is the master key revoked?
@@ -101,8 +105,16 @@ public abstract class WrappedKeyRing extends KeyRing {
abstract public IterableIterator publicKeyIterator();
- public UncachedKeyRing getUncached() {
- return new UncachedKeyRing(getRing());
+ public WrappedPublicKey getPublicKey() {
+ return new WrappedPublicKey(this, getRing().getPublicKey());
+ }
+
+ public WrappedPublicKey getPublicKey(long id) {
+ return new WrappedPublicKey(this, getRing().getPublicKey(id));
+ }
+
+ public byte[] getEncoded() throws IOException {
+ return getRing().getEncoded();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java
index b2abf15a4..57d84072a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java
@@ -44,14 +44,6 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
getRing().encode(stream);
}
- public WrappedPublicKey getSubkey() {
- return new WrappedPublicKey(this, getRing().getPublicKey());
- }
-
- public WrappedPublicKey getSubkey(long id) {
- return new WrappedPublicKey(this, getRing().getPublicKey(id));
- }
-
/** Getter that returns the subkey that should be used for signing. */
WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException {
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
index ef8044a9b..98ad2b706 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java
@@ -97,7 +97,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
signatureGenerator.init(signatureType, mPrivateKey);
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
- spGen.setSignerUserID(false, mRing.getPrimaryUserId());
+ spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch(PGPException e) {
@@ -175,7 +175,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
}
// get the master subkey (which we certify for)
- PGPPublicKey publicKey = publicKeyRing.getSubkey().getPublicKey();
+ PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
// fetch public key ring, add the certification and return it
for (String userId : new IterableIterator(userIds.iterator())) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java
index c737b7c46..5cb24cf88 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java
@@ -41,11 +41,11 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
return mRing;
}
- public WrappedSecretKey getSubKey() {
+ public WrappedSecretKey getSecretKey() {
return new WrappedSecretKey(this, mRing.getSecretKey());
}
- public WrappedSecretKey getSubKey(long id) {
+ public WrappedSecretKey getSecretKey(long id) {
return new WrappedSecretKey(this, mRing.getSecretKey(id));
}
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 196ac1dee..df19930c4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
@@ -15,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.security.SignatureException;
+import java.util.ArrayList;
import java.util.Date;
/** OpenKeychain wrapper around PGPSignature objects.
@@ -55,12 +56,37 @@ public class WrappedSignature {
return mSig.getCreationTime();
}
+ public ArrayList getEmbeddedSignatures() {
+ ArrayList sigs = new ArrayList();
+ if (!mSig.hasSubpackets()) {
+ return sigs;
+ }
+ try {
+ PGPSignatureList list;
+ list = mSig.getHashedSubPackets().getEmbeddedSignatures();
+ for(int i = 0; i < list.size(); i++) {
+ sigs.add(new WrappedSignature(list.get(i)));
+ }
+ list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
+ for(int i = 0; i < list.size(); i++) {
+ sigs.add(new WrappedSignature(list.get(i)));
+ }
+ } catch (PGPException e) {
+ // no matter
+ Log.e(Constants.TAG, "exception reading embedded signatures", e);
+ } catch (IOException e) {
+ // no matter
+ Log.e(Constants.TAG, "exception reading embedded signatures", e);
+ }
+ return sigs;
+ }
+
public byte[] getEncoded() throws IOException {
return mSig.getEncoded();
}
public boolean isRevocation() {
- return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON);
+ return mSig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;
}
public boolean isPrimaryUserId() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
index 48d40430a..34de0024d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
@@ -70,6 +70,10 @@ public class CachedPublicKeyRing extends KeyRing {
}
}
+ public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
+ return getPrimaryUserId();
+ }
+
public boolean isRevoked() throws PgpGeneralException {
try {
Object data = mProviderHelper.getGenericData(mUri,
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 b5609a327..2d524f5b0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -199,7 +199,7 @@ public class ProviderHelper {
byte[] blob = cursor.getBlob(3);
if (blob != null) {
result.put(masterKeyId,
- new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey());
+ new WrappedPublicKeyRing(blob, hasAnySecret, verified).getPublicKey());
}
} while (cursor.moveToNext());
@@ -450,8 +450,7 @@ public class ProviderHelper {
if (cert.verifySignature(masterKey, userId)) {
item.trustedCerts.add(cert);
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD,
- PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()),
- trustedKey.getPrimaryUserId()
+ PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())
);
} else {
log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD);
@@ -670,7 +669,7 @@ public class ProviderHelper {
// If there is an old keyring, merge it
try {
- UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
+ UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
// Merge data from new public ring into the old one
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
@@ -706,7 +705,7 @@ public class ProviderHelper {
// If there is a secret key, merge new data (if any) and save the key for later
UncachedKeyRing secretRing;
try {
- secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached();
+ secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
// Merge data from new public ring into secret one
secretRing = secretRing.merge(publicRing, mLog, mIndent);
@@ -754,10 +753,10 @@ public class ProviderHelper {
// If there is an old secret key, merge it.
try {
- UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
+ UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncachedKeyRing();
// Merge data from new secret ring into old one
- secretRing = oldSecretRing.merge(secretRing, mLog, mIndent);
+ secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);
// If this is null, there is an error in the log so we can just return
if (secretRing == null) {
@@ -791,9 +790,9 @@ public class ProviderHelper {
// Merge new data into public keyring as well, if there is any
UncachedKeyRing publicRing;
try {
- UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
+ UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
- // Merge data from new public ring into secret one
+ // Merge data from new secret ring into public one
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
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 10faa8786..9a4cef2f1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -44,7 +44,6 @@ 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.WrappedPublicKey;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
@@ -350,9 +349,9 @@ public class KeychainIntentService extends IntentService
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
// cache new passphrase
- if (saveParcel.newPassphrase != null) {
+ if (saveParcel.mNewPassphrase != null) {
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
- saveParcel.newPassphrase, ring.getPublicKey().getPrimaryUserId());
+ saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());
}
} catch (ProviderHelper.NotFoundException e) {
sendErrorToHandler(e);
@@ -546,7 +545,7 @@ public class KeychainIntentService extends IntentService
ProviderHelper providerHelper = new ProviderHelper(this);
WrappedPublicKeyRing publicRing = providerHelper.getWrappedPublicKeyRing(pubKeyId);
WrappedSecretKeyRing secretKeyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
- WrappedSecretKey certificationKey = secretKeyRing.getSubKey();
+ WrappedSecretKey certificationKey = secretKeyRing.getSecretKey();
if(!certificationKey.unlock(signaturePassphrase)) {
throw new PgpGeneralException("Error extracting key (bad passphrase?)");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java
index f868d931c..705d6afaf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java
@@ -9,6 +9,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -70,9 +71,6 @@ public class OperationResultParcel implements Parcelable {
mParameters = parameters;
mIndent = indent;
}
- public LogEntryParcel(LogLevel level, LogType type, Object... parameters) {
- this(level, type, 0, parameters);
- }
public LogEntryParcel(Parcel source) {
mLevel = LogLevel.values()[source.readInt()];
@@ -104,6 +102,15 @@ public class OperationResultParcel implements Parcelable {
}
};
+ @Override
+ public String toString() {
+ return "LogEntryParcel{" +
+ "mLevel=" + mLevel +
+ ", mType=" + mType +
+ ", mParameters=" + Arrays.toString(mParameters) +
+ ", mIndent=" + mIndent +
+ '}';
+ }
}
/** This is an enum of all possible log events.
@@ -235,9 +242,17 @@ public class OperationResultParcel implements Parcelable {
MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
+ MSG_MG_UNCHANGED (R.string.msg_mg_unchanged),
// secret key create
- MSG_CR_ERROR_NO_MASTER (R.string.msg_mr),
+ MSG_CR (R.string.msg_cr),
+ MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master),
+ MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id),
+ MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
+ MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512),
+ MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
+ MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp),
+ MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),
// secret key modify
MSG_MF (R.string.msg_mr),
@@ -245,10 +260,13 @@ public class OperationResultParcel implements Parcelable {
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
+ MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
+ MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
+ MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
@@ -314,6 +332,7 @@ public class OperationResultParcel implements Parcelable {
}
public void add(LogLevel level, LogType type, int indent) {
+ Log.d(Constants.TAG, type.toString());
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index 72c1a8f67..13d9b497f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -191,7 +191,7 @@ public class PassphraseCacheService extends Service {
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
try {
- addCachedPassphrase(this, keyId, "", key.getPrimaryUserId());
+ addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback());
} catch (PgpGeneralException e) {
Log.d(Constants.TAG, "PgpGeneralException occured");
}
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 a56095767..5e90b396e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
@@ -27,23 +27,19 @@ public class SaveKeyringParcel implements Parcelable {
// the key fingerprint, for safety. MUST be null for a new key.
public byte[] mFingerprint;
- public String newPassphrase;
+ public String mNewPassphrase;
- public ArrayList addUserIds;
- public ArrayList addSubKeys;
+ public ArrayList mAddUserIds;
+ public ArrayList mAddSubKeys;
- public ArrayList changeSubKeys;
- public String changePrimaryUserId;
+ public ArrayList mChangeSubKeys;
+ public String mChangePrimaryUserId;
- public ArrayList revokeUserIds;
- public ArrayList revokeSubKeys;
+ public ArrayList mRevokeUserIds;
+ public ArrayList mRevokeSubKeys;
public SaveKeyringParcel() {
- addUserIds = new ArrayList();
- addSubKeys = new ArrayList();
- changeSubKeys = new ArrayList();
- revokeUserIds = new ArrayList();
- revokeSubKeys = new ArrayList();
+ reset();
}
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
@@ -52,6 +48,16 @@ public class SaveKeyringParcel implements Parcelable {
mFingerprint = fingerprint;
}
+ public void reset() {
+ mNewPassphrase = null;
+ mAddUserIds = new ArrayList();
+ mAddSubKeys = new ArrayList();
+ mChangePrimaryUserId = null;
+ mChangeSubKeys = new ArrayList();
+ mRevokeUserIds = new ArrayList();
+ mRevokeSubKeys = new ArrayList();
+ }
+
// performance gain for using Parcelable here would probably be negligible,
// use Serializable instead.
public static class SubkeyAdd implements Serializable {
@@ -70,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
public static class SubkeyChange implements Serializable {
public long mKeyId;
public Integer mFlags;
+ // this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
public Long mExpiry;
public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId;
@@ -82,16 +89,16 @@ public class SaveKeyringParcel implements Parcelable {
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
mFingerprint = source.createByteArray();
- newPassphrase = source.readString();
+ mNewPassphrase = source.readString();
- addUserIds = source.createStringArrayList();
- addSubKeys = (ArrayList) source.readSerializable();
+ mAddUserIds = source.createStringArrayList();
+ mAddSubKeys = (ArrayList) source.readSerializable();
- changeSubKeys = (ArrayList) source.readSerializable();
- changePrimaryUserId = source.readString();
+ mChangeSubKeys = (ArrayList) source.readSerializable();
+ mChangePrimaryUserId = source.readString();
- revokeUserIds = source.createStringArrayList();
- revokeSubKeys = (ArrayList) source.readSerializable();
+ mRevokeUserIds = source.createStringArrayList();
+ mRevokeSubKeys = (ArrayList) source.readSerializable();
}
@Override
@@ -102,16 +109,16 @@ public class SaveKeyringParcel implements Parcelable {
}
destination.writeByteArray(mFingerprint);
- destination.writeString(newPassphrase);
+ destination.writeString(mNewPassphrase);
- destination.writeStringList(addUserIds);
- destination.writeSerializable(addSubKeys);
+ destination.writeStringList(mAddUserIds);
+ destination.writeSerializable(mAddSubKeys);
- destination.writeSerializable(changeSubKeys);
- destination.writeString(changePrimaryUserId);
+ destination.writeSerializable(mChangeSubKeys);
+ destination.writeString(mChangePrimaryUserId);
- destination.writeStringList(revokeUserIds);
- destination.writeSerializable(revokeSubKeys);
+ destination.writeStringList(mRevokeUserIds);
+ destination.writeSerializable(mRevokeSubKeys);
}
public static final Creator CREATOR = new Creator() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java
deleted file mode 100644
index a565f0707..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.sufficientlysecure.keychain.testsupport;
-
-import android.content.Context;
-
-import org.sufficientlysecure.keychain.pgp.NullProgressable;
-import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.OperationResults;
-
-import java.util.Collection;
-
-/**
- * Helper for tests of the Keyring import in ProviderHelper.
- */
-public class KeyringTestingHelper {
-
- private final Context context;
-
- public KeyringTestingHelper(Context robolectricContext) {
- this.context = robolectricContext;
- }
-
- public boolean addKeyring(Collection blobFiles) throws Exception {
-
- ProviderHelper providerHelper = new ProviderHelper(context);
-
- byte[] data = TestDataUtil.readAllFully(blobFiles);
- UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
- long masterKeyId = ring.getMasterKeyId();
-
- // Should throw an exception; key is not yet saved
- retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
-
- OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
-
- boolean saveSuccess = saveKeyringResult.success();
-
- // Now re-retrieve the saved key. Should not throw an exception.
- providerHelper.getWrappedPublicKeyRing(masterKeyId);
-
- // A different ID should still fail
- retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
-
- return saveSuccess;
- }
-
-
- private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
- try {
- providerHelper.getWrappedPublicKeyRing(masterKeyId);
- throw new AssertionError("Was expecting the previous call to fail!");
- } catch (ProviderHelper.NotFoundException expectedException) {
- // good
- }
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java
deleted file mode 100644
index c6d834bf9..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.sufficientlysecure.keychain.testsupport;
-
-import android.content.Context;
-import android.net.Uri;
-
-import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-
-/**
- * Created by art on 21/06/14.
- */
-class ProviderHelperStub extends ProviderHelper {
- public ProviderHelperStub(Context context) {
- super(context);
- }
-
- @Override
- public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
- byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
- return new WrappedPublicKeyRing(data, false, 0);
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java
deleted file mode 100644
index 9e6ede761..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.sufficientlysecure.keychain.testsupport;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collection;
-
-/**
- * Misc support functions. Would just use Guava / Apache Commons but
- * avoiding extra dependencies.
- */
-public class TestDataUtil {
- public static byte[] readFully(InputStream input) {
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- appendToOutput(input, output);
- return output.toByteArray();
- }
-
- private static void appendToOutput(InputStream input, ByteArrayOutputStream output) {
- byte[] buffer = new byte[8192];
- int bytesRead;
- try {
- while ((bytesRead = input.read(buffer)) != -1) {
- output.write(buffer, 0, bytesRead);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static byte[] readAllFully(Collection inputResources) {
- ByteArrayOutputStream output = new ByteArrayOutputStream();
-
- for (String inputResource : inputResources) {
- appendToOutput(getResourceAsStream(inputResource), output);
- }
- return output.toByteArray();
- }
-
-
- public static InputStream getResourceAsStream(String resourceName) {
- return TestDataUtil.class.getResourceAsStream(resourceName);
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java
deleted file mode 100644
index 1cc0f9a95..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Test support classes.
- * This is only in main code because of gradle-Android Studio-robolectric issues. Having
- * classes in main code means IDE autocomplete, class detection, etc., all works.
- * TODO Move into test package when possible
- */
-package org.sufficientlysecure.keychain.testsupport;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
new file mode 100644
index 000000000..d2073d9a7
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann
+ *
+ * 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.ui;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.app.ActionBarActivity;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Patterns;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.EditText;
+
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.ContactHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+
+import java.util.regex.Matcher;
+
+public class CreateKeyActivity extends ActionBarActivity {
+
+ AutoCompleteTextView nameEdit;
+ AutoCompleteTextView emailEdit;
+ EditText passphraseEdit;
+ Button createButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.create_key_activity);
+
+ nameEdit = (AutoCompleteTextView) findViewById(R.id.name);
+ emailEdit = (AutoCompleteTextView) findViewById(R.id.email);
+ passphraseEdit = (EditText) findViewById(R.id.passphrase);
+ createButton = (Button) findViewById(R.id.create_key_button);
+
+ emailEdit.setThreshold(1); // Start working from first character
+ emailEdit.setAdapter(
+ new ArrayAdapter
+ (this, android.R.layout.simple_spinner_dropdown_item,
+ ContactHelper.getPossibleUserEmails(this)
+ )
+ );
+ emailEdit.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ String email = editable.toString();
+ if (email.length() > 0) {
+ Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
+ if (emailMatcher.matches()) {
+ emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_ok, 0);
+ } else {
+ emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
+ R.drawable.uid_mail_bad, 0);
+ }
+ } else {
+ // remove drawable if email is empty
+ emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ }
+ }
+ });
+ nameEdit.setThreshold(1); // Start working from first character
+ nameEdit.setAdapter(
+ new ArrayAdapter
+ (this, android.R.layout.simple_spinner_dropdown_item,
+ ContactHelper.getPossibleUserNames(this)
+ )
+ );
+
+ createButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ createKeyCheck();
+ }
+ });
+
+ }
+
+ private void createKeyCheck() {
+ if (isEditTextNotEmpty(this, nameEdit)
+ && isEditTextNotEmpty(this, emailEdit)
+ && isEditTextNotEmpty(this, passphraseEdit)) {
+ createKey();
+ }
+ }
+
+ private void createKey() {
+ Intent intent = new Intent(this, KeychainIntentService.class);
+ intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
+
+ // Message is received after importing is done in KeychainIntentService
+ KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
+ this,
+ getString(R.string.progress_importing),
+ ProgressDialog.STYLE_HORIZONTAL) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
+ CreateKeyActivity.this.finish();
+ }
+ }
+ };
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
+ String userId = nameEdit.getText().toString() + " <" + emailEdit.getText().toString() + ">";
+ parcel.mAddUserIds.add(userId);
+ parcel.mNewPassphrase = passphraseEdit.getText().toString();
+
+ // get selected key entries
+ data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ saveHandler.showProgressDialog(this);
+
+ startService(intent);
+ }
+
+ /**
+ * Checks if text of given EditText is not empty. If it is empty an error is
+ * set and the EditText gets the focus.
+ *
+ * @param context
+ * @param editText
+ * @return true if EditText is not empty
+ */
+ private static boolean isEditTextNotEmpty(Context context, EditText editText) {
+ boolean output = true;
+ if (editText.getText().toString().length() == 0) {
+ editText.setError("empty!");
+ editText.requestFocus();
+ output = false;
+ } else {
+ editText.setError(null);
+ }
+
+ return output;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java
index 51457cd45..70ccb8800 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java
@@ -280,7 +280,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe
Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri);
- mMasterCanSign = keyRing.getSubKey().canCertify();
+ mMasterCanSign = keyRing.getSecretKey().canCertify();
for (WrappedSecretKey key : keyRing.secretKeyIterator()) {
// Turn into uncached instance
mKeys.add(key.getUncached());
@@ -288,7 +288,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe
}
boolean isSet = false;
- for (String userId : keyRing.getSubKey().getUserIds()) {
+ for (String userId : keyRing.getSecretKey().getUserIds()) {
Log.d(Constants.TAG, "Added userId " + userId);
if (!isSet) {
isSet = true;
@@ -556,7 +556,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe
saveParams.deletedKeys = mKeysView.getDeletedKeys();
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
saveParams.keysUsages = getKeysUsages(mKeysView);
- saveParams.newPassphrase = mNewPassphrase;
+ saveParams.mNewPassphrase = mNewPassphrase;
saveParams.oldPassphrase = mCurrentPassphrase;
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
saveParams.keys = getKeys(mKeysView);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
index 94ca883d6..b41871a39 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -228,7 +228,7 @@ public class EditKeyFragment extends LoaderFragment implements
}
});
- mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys);
+ mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys);
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
@@ -298,7 +298,7 @@ public class EditKeyFragment extends LoaderFragment implements
Bundle data = message.getData();
// cache new returned passphrase!
- mSaveKeyringParcel.newPassphrase = data
+ mSaveKeyringParcel.mNewPassphrase = data
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
}
}
@@ -320,19 +320,19 @@ public class EditKeyFragment extends LoaderFragment implements
switch (message.what) {
case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID:
// toggle
- if (mSaveKeyringParcel.changePrimaryUserId != null
- && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
- mSaveKeyringParcel.changePrimaryUserId = null;
+ if (mSaveKeyringParcel.mChangePrimaryUserId != null
+ && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
+ mSaveKeyringParcel.mChangePrimaryUserId = null;
} else {
- mSaveKeyringParcel.changePrimaryUserId = userId;
+ mSaveKeyringParcel.mChangePrimaryUserId = userId;
}
break;
case EditUserIdDialogFragment.MESSAGE_REVOKE:
// toggle
- if (mSaveKeyringParcel.revokeUserIds.contains(userId)) {
- mSaveKeyringParcel.revokeUserIds.remove(userId);
+ if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) {
+ mSaveKeyringParcel.mRevokeUserIds.remove(userId);
} else {
- mSaveKeyringParcel.revokeUserIds.add(userId);
+ mSaveKeyringParcel.mRevokeUserIds.add(userId);
}
break;
}
@@ -363,10 +363,10 @@ public class EditKeyFragment extends LoaderFragment implements
break;
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
// toggle
- if (mSaveKeyringParcel.revokeSubKeys.contains(keyId)) {
- mSaveKeyringParcel.revokeSubKeys.remove(keyId);
+ if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) {
+ mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);
} else {
- mSaveKeyringParcel.revokeSubKeys.add(keyId);
+ mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
}
break;
}
@@ -450,10 +450,11 @@ public class EditKeyFragment extends LoaderFragment implements
}
private void save(String passphrase) {
- Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList());
- Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase);
+ mSaveKeyringParcel.mAddUserIds = mUserIdsAddedAdapter.getDataAsStringList();
- mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList();
+ Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds);
+ Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
+ Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
@@ -509,4 +510,4 @@ public class EditKeyFragment extends LoaderFragment implements
// start service with intent
getActivity().startService(intent);
}
-}
\ No newline at end of file
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
index 51963e963..dc0510189 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
@@ -198,7 +198,7 @@ public class EncryptAsymmetricFragment extends Fragment {
String[] userId;
try {
userId = mProviderHelper.getCachedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserId();
+ KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback();
} catch (PgpGeneralException e) {
userId = null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java
new file mode 100644
index 000000000..aa4120d2c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann
+ *
+ * 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.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+
+import org.sufficientlysecure.keychain.R;
+
+public class FirstTimeActivity extends ActionBarActivity {
+
+ Button mCreateKey;
+ Button mImportKey;
+ Button mSkipSetup;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(R.layout.first_time_activity);
+
+ mCreateKey = (Button) findViewById(R.id.first_time_create_key);
+ mImportKey = (Button) findViewById(R.id.first_time_import_key);
+ mSkipSetup = (Button) findViewById(R.id.first_time_cancel);
+
+ mSkipSetup.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ mImportKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(FirstTimeActivity.this, ImportKeysActivity.class);
+ intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ mCreateKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(FirstTimeActivity.this, CreateKeyActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
index 849576284..9cc623c83 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java
@@ -32,8 +32,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
-import org.sufficientlysecure.keychain.helper.OtherHelper;
-import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
@@ -43,7 +42,6 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
-import java.util.ArrayList;
public class KeyListActivity extends DrawerActivity {
@@ -53,6 +51,15 @@ public class KeyListActivity extends DrawerActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Preferences prefs = Preferences.getPreferences(this);
+ if (prefs.getFirstTime()) {
+ prefs.setFirstTime(false);
+ Intent intent = new Intent(this, FirstTimeActivity.class);
+ startActivity(intent);
+ finish();
+ return;
+ }
+
mExportHelper = new ExportHelper(this);
setContentView(R.layout.key_list_activity);
@@ -66,9 +73,10 @@ public class KeyListActivity extends DrawerActivity {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.key_list, menu);
- if(Constants.DEBUG) {
+ if (Constants.DEBUG) {
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
+ menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
}
return true;
@@ -85,10 +93,6 @@ public class KeyListActivity extends DrawerActivity {
createKey();
return true;
- case R.id.menu_key_list_create_expert:
- createKeyExpert();
- return true;
-
case R.id.menu_key_list_export:
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
return true;
@@ -98,7 +102,7 @@ public class KeyListActivity extends DrawerActivity {
KeychainDatabase.debugRead(this);
AppMsg.makeText(this, "Restored from backup", AppMsg.STYLE_CONFIRM).show();
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
- } catch(IOException e) {
+ } catch (IOException e) {
Log.e(Constants.TAG, "IO Error", e);
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
}
@@ -108,12 +112,18 @@ public class KeyListActivity extends DrawerActivity {
try {
KeychainDatabase.debugWrite(this);
AppMsg.makeText(this, "Backup successful", AppMsg.STYLE_CONFIRM).show();
- } catch(IOException e) {
+ } catch (IOException e) {
Log.e(Constants.TAG, "IO Error", e);
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
}
return true;
+ case R.id.menu_key_list_debug_first_time:
+ Intent intent = new Intent(this, FirstTimeActivity.class);
+ startActivity(intent);
+ finish();
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
@@ -125,50 +135,8 @@ public class KeyListActivity extends DrawerActivity {
}
private void createKey() {
- Intent intent = new Intent(this, WizardActivity.class);
-// intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
-// intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
-// intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
- startActivityForResult(intent, 0);
+ Intent intent = new Intent(this, CreateKeyActivity.class);
+ startActivity(intent);
}
- private void createKeyExpert() {
- Intent intent = new Intent(this, KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
-
- // Message is received after importing is done in KeychainIntentService
- KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
- this,
- getString(R.string.progress_importing),
- ProgressDialog.STYLE_HORIZONTAL) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
- Bundle data = message.getData();
- // OtherHelper.logDebugBundle(data, "message reply");
- }
- };
-
- // fill values for this action
- Bundle data = new Bundle();
-
- SaveKeyringParcel parcel = new SaveKeyringParcel();
- parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
- parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
- parcel.addUserIds.add("swagerinho");
- parcel.newPassphrase = "swag";
-
- // get selected key entries
- data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- saveHandler.showProgressDialog(this);
-
- startService(intent);
- }
}
\ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
index c7fffe263..cfdea0611 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
@@ -149,8 +149,8 @@ public class ViewCertActivity extends ActionBarActivity
providerHelper.getWrappedPublicKeyRing(sig.getKeyId());
try {
- sig.init(signerRing.getSubkey());
- if (sig.verifySignature(signeeRing.getSubkey(), signeeUid)) {
+ sig.init(signerRing.getPublicKey());
+ if (sig.verifySignature(signeeRing.getPublicKey(), signeeUid)) {
mStatus.setText(R.string.cert_verify_ok);
mStatus.setTextColor(getResources().getColor(R.color.result_green));
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java
deleted file mode 100644
index 601fc09f9..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2014 Dominik Schürmann
- *
- * 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.ui;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.ActionBarActivity;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Patterns;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ArrayAdapter;
-import android.widget.AutoCompleteTextView;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.RadioGroup;
-import android.widget.TextView;
-
-import org.sufficientlysecure.htmltextview.HtmlTextView;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.helper.ContactHelper;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.util.regex.Matcher;
-
-
-public class WizardActivity extends ActionBarActivity {
-
- private State mCurrentState;
-
- // values for mCurrentScreen
- private enum State {
- START, CREATE_KEY, IMPORT_KEY, K9
- }
-
- public static final int REQUEST_CODE_IMPORT = 0x00007703;
-
- Button mBackButton;
- Button mNextButton;
- StartFragment mStartFragment;
- CreateKeyFragment mCreateKeyFragment;
- K9Fragment mK9Fragment;
-
- private static final String K9_PACKAGE = "com.fsck.k9";
- // private static final String K9_MARKET_INTENT_URI_BASE = "market://details?id=%s";
-// private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
-// String.format(K9_MARKET_INTENT_URI_BASE, K9_PACKAGE)));
- private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/k9mail/k-9/releases/tag/4.904"));
-
- LinearLayout mProgressLayout;
- View mProgressLine;
- ProgressBar mProgressBar;
- ImageView mProgressImage;
- TextView mProgressText;
-
- /**
- * Checks if text of given EditText is not empty. If it is empty an error is
- * set and the EditText gets the focus.
- *
- * @param context
- * @param editText
- * @return true if EditText is not empty
- */
- private static boolean isEditTextNotEmpty(Context context, EditText editText) {
- boolean output = true;
- if (editText.getText().toString().length() == 0) {
- editText.setError("empty!");
- editText.requestFocus();
- output = false;
- } else {
- editText.setError(null);
- }
-
- return output;
- }
-
- public static class StartFragment extends Fragment {
- public static StartFragment newInstance() {
- StartFragment myFragment = new StartFragment();
-
- Bundle args = new Bundle();
- myFragment.setArguments(args);
-
- return myFragment;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.wizard_start_fragment,
- container, false);
- }
- }
-
- public static class CreateKeyFragment extends Fragment {
- public static CreateKeyFragment newInstance() {
- CreateKeyFragment myFragment = new CreateKeyFragment();
-
- Bundle args = new Bundle();
- myFragment.setArguments(args);
-
- return myFragment;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.wizard_create_key_fragment,
- container, false);
-
- final AutoCompleteTextView emailView = (AutoCompleteTextView) view.findViewById(R.id.email);
- emailView.setThreshold(1); // Start working from first character
- emailView.setAdapter(
- new ArrayAdapter
- (getActivity(), android.R.layout.simple_spinner_dropdown_item,
- ContactHelper.getPossibleUserEmails(getActivity())
- )
- );
- emailView.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- String email = editable.toString();
- if (email.length() > 0) {
- Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
- if (emailMatcher.matches()) {
- emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_ok, 0);
- } else {
- emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
- R.drawable.uid_mail_bad, 0);
- }
- } else {
- // remove drawable if email is empty
- emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
- }
- }
- });
- final AutoCompleteTextView nameView = (AutoCompleteTextView) view.findViewById(R.id.name);
- nameView.setThreshold(1); // Start working from first character
- nameView.setAdapter(
- new ArrayAdapter
- (getActivity(), android.R.layout.simple_spinner_dropdown_item,
- ContactHelper.getPossibleUserNames(getActivity())
- )
- );
- return view;
- }
- }
-
- public static class K9Fragment extends Fragment {
- public static K9Fragment newInstance() {
- K9Fragment myFragment = new K9Fragment();
-
- Bundle args = new Bundle();
- myFragment.setArguments(args);
-
- return myFragment;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.wizard_k9_fragment,
- container, false);
-
- HtmlTextView text = (HtmlTextView) v
- .findViewById(R.id.wizard_k9_text);
- text.setHtmlFromString("Install K9. It's good for you! Here is a screenhot how to enable OK in K9: (TODO)", true);
-
- return v;
- }
-
- }
-
- /**
- * Loads new fragment
- *
- * @param fragment
- */
- private void loadFragment(Fragment fragment) {
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager
- .beginTransaction();
- fragmentTransaction.replace(R.id.wizard_container,
- fragment);
- fragmentTransaction.commit();
- }
-
- /**
- * Instantiate View and initialize fragments for this Activity
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.wizard_activity);
- mBackButton = (Button) findViewById(R.id.wizard_back);
- mNextButton = (Button) findViewById(R.id.wizard_next);
-
- // progress layout
- mProgressLayout = (LinearLayout) findViewById(R.id.wizard_progress);
- mProgressLine = findViewById(R.id.wizard_progress_line);
- mProgressBar = (ProgressBar) findViewById(R.id.wizard_progress_progressbar);
- mProgressImage = (ImageView) findViewById(R.id.wizard_progress_image);
- mProgressText = (TextView) findViewById(R.id.wizard_progress_text);
-
- changeToState(State.START);
- }
-
- private enum ProgressState {
- WORKING, ENABLED, DISABLED, ERROR
- }
-
- private void showProgress(ProgressState state, String text) {
- switch (state) {
- case WORKING:
- mProgressBar.setVisibility(View.VISIBLE);
- mProgressImage.setVisibility(View.GONE);
- break;
- case ENABLED:
- mProgressBar.setVisibility(View.GONE);
- mProgressImage.setVisibility(View.VISIBLE);
-// mProgressImage.setImageDrawable(getResources().getDrawable(
-// R.drawable.status_enabled));
- break;
- case DISABLED:
- mProgressBar.setVisibility(View.GONE);
- mProgressImage.setVisibility(View.VISIBLE);
-// mProgressImage.setImageDrawable(getResources().getDrawable(
-// R.drawable.status_disabled));
- break;
- case ERROR:
- mProgressBar.setVisibility(View.GONE);
- mProgressImage.setVisibility(View.VISIBLE);
-// mProgressImage.setImageDrawable(getResources().getDrawable(
-// R.drawable.status_fail));
- break;
-
- default:
- break;
- }
- mProgressText.setText(text);
-
- mProgressLine.setVisibility(View.VISIBLE);
- mProgressLayout.setVisibility(View.VISIBLE);
- }
-
- private void hideProgress() {
- mProgressLine.setVisibility(View.GONE);
- mProgressLayout.setVisibility(View.GONE);
- }
-
- public void nextOnClick(View view) {
- // close keyboard
- if (getCurrentFocus() != null) {
- InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- inputManager.hideSoftInputFromWindow(getCurrentFocus()
- .getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
- }
-
- switch (mCurrentState) {
- case START: {
- RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_start_radio_group);
- int selectedId = radioGroup.getCheckedRadioButtonId();
- switch (selectedId) {
- case R.id.wizard_start_new_key: {
- changeToState(State.CREATE_KEY);
- break;
- }
- case R.id.wizard_start_import: {
- changeToState(State.IMPORT_KEY);
- break;
- }
- case R.id.wizard_start_skip: {
- finish();
- break;
- }
- }
-
- mBackButton.setText(R.string.btn_back);
- break;
- }
- case CREATE_KEY:
- EditText nameEdit = (EditText) findViewById(R.id.name);
- EditText emailEdit = (EditText) findViewById(R.id.email);
- EditText passphraseEdit = (EditText) findViewById(R.id.passphrase);
-
- if (isEditTextNotEmpty(this, nameEdit)
- && isEditTextNotEmpty(this, emailEdit)
- && isEditTextNotEmpty(this, passphraseEdit)) {
-
-// SaveKeyringParcel newKey = new SaveKeyringParcel();
-// newKey.addUserIds.add(nameEdit.getText().toString() + " <"
-// + emailEdit.getText().toString() + ">");
-
-
- AsyncTask generateTask = new AsyncTask() {
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
-
- showProgress(ProgressState.WORKING, "generating key...");
- }
-
- @Override
- protected Boolean doInBackground(String... params) {
- return true;
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- super.onPostExecute(result);
-
- if (result) {
- showProgress(ProgressState.ENABLED, "key generated successfully!");
-
- changeToState(State.K9);
- } else {
- showProgress(ProgressState.ERROR, "error in key gen");
- }
- }
-
- };
-
- generateTask.execute("");
- }
- break;
- case K9: {
- RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_k9_radio_group);
- int selectedId = radioGroup.getCheckedRadioButtonId();
- switch (selectedId) {
- case R.id.wizard_k9_install: {
- try {
- startActivity(K9_MARKET_INTENT);
- } catch (ActivityNotFoundException e) {
- Log.e(Constants.TAG, "Activity not found for: " + K9_MARKET_INTENT);
- }
- break;
- }
- case R.id.wizard_k9_skip: {
- finish();
- break;
- }
- }
-
- finish();
- break;
- }
- default:
- break;
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- switch (requestCode) {
- case REQUEST_CODE_IMPORT: {
- if (resultCode == Activity.RESULT_OK) {
- // imported now...
- changeToState(State.K9);
- } else {
- // back to start
- changeToState(State.START);
- }
- break;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
-
- break;
- }
- }
- }
-
- public void backOnClick(View view) {
- switch (mCurrentState) {
- case START:
- finish();
- break;
- case CREATE_KEY:
- changeToState(State.START);
- break;
- case IMPORT_KEY:
- changeToState(State.START);
- break;
- default:
- changeToState(State.START);
- break;
- }
- }
-
- private void changeToState(State state) {
- switch (state) {
- case START: {
- mCurrentState = State.START;
- mStartFragment = StartFragment.newInstance();
- loadFragment(mStartFragment);
- mBackButton.setText(android.R.string.cancel);
- mNextButton.setText(R.string.btn_next);
- break;
- }
- case CREATE_KEY: {
- mCurrentState = State.CREATE_KEY;
- mCreateKeyFragment = CreateKeyFragment.newInstance();
- loadFragment(mCreateKeyFragment);
- break;
- }
- case IMPORT_KEY: {
- mCurrentState = State.IMPORT_KEY;
- Intent intent = new Intent(this, ImportKeysActivity.class);
- intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
- startActivityForResult(intent, REQUEST_CODE_IMPORT);
- break;
- }
- case K9: {
- mCurrentState = State.K9;
- mBackButton.setEnabled(false); // don't go back to import/create key
- mK9Fragment = K9Fragment.newInstance();
- loadFragment(mK9Fragment);
- break;
- }
- }
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
index ccc5960c8..e5dbebe01 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
@@ -143,7 +143,7 @@ public class SubkeysAdapter extends CursorAdapter {
// for edit key
if (mSaveKeyringParcel != null) {
- boolean revokeThisSubkey = (mSaveKeyringParcel.revokeSubKeys.contains(keyId));
+ boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
if (revokeThisSubkey) {
if (!isRevoked) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
index 10e299ae3..18312660a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java
@@ -131,10 +131,10 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
// for edit key
if (mSaveKeyringParcel != null) {
- boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null);
- boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null
- && mSaveKeyringParcel.changePrimaryUserId.equals(userId));
- boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId));
+ boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null);
+ boolean changeThisPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null
+ && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId));
+ boolean revokeThisUserId = (mSaveKeyringParcel.mRevokeUserIds.contains(userId));
// only if primary user id will be changed
// (this is not triggered if the user id is currently the primary one)
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
index 4d0b73d30..d723f88af 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
@@ -152,7 +152,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
// above can't be statically verified to have been set in all cases because
// the catch clause doesn't return.
try {
- userId = secretRing.getPrimaryUserId();
+ userId = secretRing.getPrimaryUserIdWithFallback();
} catch (PgpGeneralException e) {
userId = null;
}
@@ -232,7 +232,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
try {
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase,
- secretRing.getPrimaryUserId());
+ secretRing.getPrimaryUserIdWithFallback());
} catch(PgpGeneralException e) {
Log.e(Constants.TAG, "adding of a passhrase failed", e);
}
@@ -240,7 +240,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
if (unlockedSecretKey.getKeyId() != masterKeyId) {
PassphraseCacheService.addCachedPassphrase(
activity, unlockedSecretKey.getKeyId(), passphrase,
- unlockedSecretKey.getPrimaryUserId());
+ unlockedSecretKey.getPrimaryUserIdWithFallback());
}
// also return passphrase back to activity
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java
index 5553ea5d2..869eea03f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java
@@ -39,15 +39,21 @@ public class ProgressScaler implements Progressable {
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
public void setProgress(String message, int progress, int max) {
- mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
+ if (mWrapped != null) {
+ mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
+ }
}
public void setProgress(int resourceId, int progress, int max) {
- mWrapped.setProgress(resourceId, progress, mMax);
+ if (mWrapped != null) {
+ mWrapped.setProgress(resourceId, progress, mMax);
+ }
}
public void setProgress(int progress, int max) {
- mWrapped.setProgress(progress, max);
+ if (mWrapped != null) {
+ mWrapped.setProgress(progress, max);
+ }
}
}
diff --git a/OpenKeychain/src/main/res/drawable/first_time_1.png b/OpenKeychain/src/main/res/drawable/first_time_1.png
new file mode 100644
index 000000000..1f340df5c
Binary files /dev/null and b/OpenKeychain/src/main/res/drawable/first_time_1.png differ
diff --git a/OpenKeychain/src/main/res/layout/wizard_create_key_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_activity.xml
similarity index 74%
rename from OpenKeychain/src/main/res/layout/wizard_create_key_fragment.xml
rename to OpenKeychain/src/main/res/layout/create_key_activity.xml
index 258ea7223..673f43084 100644
--- a/OpenKeychain/src/main/res/layout/wizard_create_key_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/create_key_activity.xml
@@ -2,6 +2,7 @@
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/first_time_activity.xml b/OpenKeychain/src/main/res/layout/first_time_activity.xml
new file mode 100644
index 000000000..514f34212
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/first_time_activity.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenKeychain/src/main/res/layout/wizard_activity.xml b/OpenKeychain/src/main/res/layout/wizard_activity.xml
deleted file mode 100644
index 299d07a76..000000000
--- a/OpenKeychain/src/main/res/layout/wizard_activity.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml b/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml
deleted file mode 100644
index 342adc37e..000000000
--- a/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml b/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml
deleted file mode 100644
index 9e1403f74..000000000
--- a/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/key_list.xml b/OpenKeychain/src/main/res/menu/key_list.xml
index ebb7314b8..e865df182 100644
--- a/OpenKeychain/src/main/res/menu/key_list.xml
+++ b/OpenKeychain/src/main/res/menu/key_list.xml
@@ -26,11 +26,6 @@
app:showAsAction="never"
android:title="@string/menu_create_key" />
-
-
+
+
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index 686460f75..50f73e73b 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -9,7 +9,6 @@
Passphrase
Create Key
Edit Key
- Welcome to OpenKeychain
Preferences
Apps
Keyserver Preference
@@ -511,7 +510,7 @@
casual
positive
revoked
- ok
+ OK
failed!
error!
key unavailable
@@ -556,7 +555,7 @@
Re-inserting secret key
Encountered bad certificate!
Error processing certificate!
- User id is certified by %1$s (%2$s)
+ User id is certified by %1$s
- Ignoring one certificate issued by an unknown public key
- Ignoring %s certificates issued by unknown public keys
@@ -637,6 +636,17 @@
Tried to consolidate heterogeneous keyrings
Adding new subkey %s
Found %s new certificates in keyring
+ No new certificates
+
+
+ Generating new master key
+ No master key options specified!
+ Keyrings must be created with at least one user id!
+ Master key must have certify flag!
+ Key size must be greater or equal 512!
+ Internal PGP error!
+ Bad algorithm choice!
+ Master key must not be of type ElGamal!
Modifying keyring %s
@@ -644,10 +654,13 @@
Actual key fingerprint does not match the expected one!
No key ID. This is an internal error, please file a bug report!
Internal error, integrity check failed!
+ Bad primary user id specified!
Revoked user ids cannot be primary!
PGP internal exception!
Signature exception!
Changing passphrase
+ Replacing certificate of previous primary user id
+ Generating new certificate for new primary user id
Modifying subkey %s
Tried to operate on missing subkey %s!
Generating new %1$s bit %2$s subkey
@@ -691,4 +704,10 @@
Do not create OpenKeychain-Accounts manually.\nFor more information, see Help.
Show key (%s)
+
+ Take back your privacy with OpenKeychain!
+ Create Key
+ Import Key
+ Skip Setup
+
diff --git a/README.md b/README.md
index 9a80eaf07..4a2fad2ee 100644
--- a/README.md
+++ b/README.md
@@ -37,8 +37,10 @@ Expand the Tools directory and select "Android SDK Build-tools (Version 19.1)".
Expand the Extras directory and install "Android Support Repository"
Select everything for the newest SDK Platform (API-Level 19)
4. Export ANDROID_HOME pointing to your Android SDK
-5. Execute ``./gradlew build``
-6. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk``
+5. Use OpenJDK 7 instead of Oracle JDK (this is required for OpenKeychain's tests based on Bouncy Castle)
+6. Execute ``./install-custom-gradle-test-plugin.sh``
+7. Execute ``./gradlew build``
+8. You can install the app with ``adb install -r OpenKeychain/build/outputs/apk/OpenKeychain-debug-unaligned.apk``
### Build API Demo with Gradle
diff --git a/Resources/gnupg-infographic/first_time_1.png b/Resources/gnupg-infographic/first_time_1.png
new file mode 100644
index 000000000..1f340df5c
Binary files /dev/null and b/Resources/gnupg-infographic/first_time_1.png differ
diff --git a/Resources/gnupg-infographic/first_time_1.svg b/Resources/gnupg-infographic/first_time_1.svg
new file mode 100644
index 000000000..1f40c5ff3
--- /dev/null
+++ b/Resources/gnupg-infographic/first_time_1.svg
@@ -0,0 +1,351 @@
+
+
+
+
diff --git a/build.gradle b/build.gradle
index ceb963cd8..698b4cd37 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,12 +1,16 @@
buildscript {
repositories {
mavenCentral()
+ // need this for com.novoda:gradle-android-test-plugin:0.9.9-SNAPSHOT below (0.9.3 in repos doesn't work!)
+ // run ./install-custom-gradle-test-plugin.sh to pull the thing into the local repository
+ mavenLocal()
}
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
classpath 'com.android.tools.build:gradle:0.12.0'
classpath 'org.robolectric:robolectric-gradle-plugin:0.11.0'
+ classpath 'com.novoda:gradle-android-test-plugin:0.9.9-SNAPSHOT'
}
}
@@ -19,3 +23,9 @@ allprojects {
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
}
+
+subprojects {
+ tasks.withType(Test) {
+ maxParallelForks = 1
+ }
+}
diff --git a/install-custom-gradle-test-plugin.sh b/install-custom-gradle-test-plugin.sh
new file mode 100755
index 000000000..85c13d959
--- /dev/null
+++ b/install-custom-gradle-test-plugin.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+mkdir temp
+cd temp
+
+ git clone https://github.com/nenick/gradle-android-test-plugin.git
+ cd gradle-android-test-plugin
+
+ echo "rootProject.name = 'gradle-android-test-plugin-parent'" > settings.gradle
+ echo "include ':gradle-android-test-plugin'" >> settings.gradle
+
+ ./gradlew :gradle-android-test-plugin:install
+
+ cd ..
+cd ..
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index d8802320c..86088e04a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,5 @@
include ':OpenKeychain'
+include ':OpenKeychain-Test'
include ':extern:openpgp-api-lib'
include ':extern:openkeychain-api-lib'
include ':extern:html-textview'