diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java index 617b5762c..2ca904656 100644 --- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java +++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.TestingUtils; @@ -104,7 +105,7 @@ public class PromoteKeyOperationTest { PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application, new ProviderHelper(Robolectric.application), null, null); - PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), null); + PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), null, null); Assert.assertTrue("promotion must succeed", result.success()); @@ -130,7 +131,7 @@ public class PromoteKeyOperationTest { byte[] aid = Hex.decode("D2760001240102000000012345670000"); - PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid); + PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid, null); Assert.assertTrue("promotion must succeed", result.success()); @@ -147,4 +148,40 @@ public class PromoteKeyOperationTest { } } + + @Test + public void testPromoteDivertSpecific() throws Exception { + PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application, + new ProviderHelper(Robolectric.application), null, null); + + byte[] aid = Hex.decode("D2760001240102000000012345670000"); + + // only promote the first, rest stays dummy + long keyId = KeyringTestingHelper.getSubkeyId(mStaticRing, 1); + + PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid, new long[] { + keyId + }); + + Assert.assertTrue("promotion must succeed", result.success()); + + { + CanonicalizedSecretKeyRing ring = new ProviderHelper(Robolectric.application) + .getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId()); + + for (CanonicalizedSecretKey key : ring.secretKeyIterator()) { + if (key.getKeyId() == keyId) { + Assert.assertEquals("subkey must be divert-to-card", + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + Assert.assertArrayEquals("subkey must have correct iv", + aid, key.getIv()); + } else { + Assert.assertEquals("some subkeys must be gnu dummy", + SecretKeyType.GNU_DUMMY, key.getSecretKeyType()); + } + } + + } + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java index ef08b0b77..558756378 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -25,6 +25,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ProgressScaler; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; /** An operation which promotes a public key ring to a secret one. @@ -50,7 +52,7 @@ public class PromoteKeyOperation extends BaseOperation { super(context, providerHelper, progressable, cancelled); } - public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) { + public PromoteKeyResult execute(long masterKeyId, byte[] cardAid, long[] subKeyIds) { OperationLog log = new OperationLog(); log.add(LogType.MSG_PR, 0); @@ -65,8 +67,24 @@ public class PromoteKeyOperation extends BaseOperation { CanonicalizedPublicKeyRing pubRing = mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); + if (subKeyIds == null) { + log.add(LogType.MSG_PR_ALL, 1); + } else { + // sort for binary search + for (CanonicalizedPublicKey key : pubRing.publicKeyIterator()) { + long subKeyId = key.getKeyId(); + if (naiveIndexOf(subKeyIds, subKeyId) != null) { + log.add(LogType.MSG_PR_SUBKEY_MATCH, 1, + KeyFormattingUtils.convertKeyIdToHex(subKeyId)); + } else { + log.add(LogType.MSG_PR_SUBKEY_NOMATCH, 1, + KeyFormattingUtils.convertKeyIdToHex(subKeyId)); + } + } + } + // create divert-to-card secret key from public key - promotedRing = pubRing.createDivertSecretRing(cardAid); + promotedRing = pubRing.createDivertSecretRing(cardAid, subKeyIds); } catch (NotFoundException e) { log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); @@ -106,4 +124,13 @@ public class PromoteKeyOperation extends BaseOperation { } + static private Integer naiveIndexOf(long[] haystack, long needle) { + for (int i = 0; i < haystack.length; i++) { + if (needle == haystack[i]) { + return i; + } + } + return null; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 094afd4a5..d39ab3695 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -558,8 +558,11 @@ public abstract class OperationResult implements Parcelable { // promote key MSG_PR (LogLevel.START, R.string.msg_pr), + MSG_PR_ALL (LogLevel.DEBUG, R.string.msg_pr_all), MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found), MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching), + MSG_PR_SUBKEY_MATCH (LogLevel.DEBUG, R.string.msg_pr_subkey_match), + MSG_PR_SUBKEY_NOMATCH (LogLevel.WARN, R.string.msg_pr_subkey_nomatch), MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success), // messages used in UI code diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index 4adacaf23..432ba23e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -127,7 +128,11 @@ public abstract class CanonicalizedKeyRing extends KeyRing { } public CanonicalizedPublicKey getPublicKey(long id) { - return new CanonicalizedPublicKey(this, getRing().getPublicKey(id)); + PGPPublicKey pubKey = getRing().getPublicKey(id); + if (pubKey == null) { + return null; + } + return new CanonicalizedPublicKey(this, pubKey); } public byte[] getEncoded() throws IOException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 8432b8f9f..68fd4a428 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -103,9 +103,22 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { } /** Create a dummy secret ring from this key */ - public UncachedKeyRing createDivertSecretRing (byte[] cardAid) { + public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) { PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid); - return new UncachedKeyRing(secRing); + + if (subKeyIds == null) { + return new UncachedKeyRing(secRing); + } + + // if only specific subkeys should be promoted, construct a + // stripped dummy, then move divert-to-card keys over + PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing()); + for (long subKeyId : subKeyIds) { + newRing = PGPSecretKeyRing.insertSecretKey(newRing, secRing.getSecretKey(subKeyId)); + } + + return new UncachedKeyRing(newRing); + } } \ No newline at end of file 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 e0509ac9b..71e149672 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -65,7 +65,6 @@ import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.Passphrase; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -185,6 +184,7 @@ public class KeychainIntentService extends IntentService implements Progressable // promote key public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; public static final String PROMOTE_CARD_AID = "promote_card_aid"; + public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints"; // consolidate public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; @@ -476,10 +476,12 @@ public class KeychainIntentService extends IntentService implements Progressable // Input long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID); + long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS); // Operation - PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); - PromoteKeyResult result = op.execute(keyRingId, cardAid); + PromoteKeyOperation op = new PromoteKeyOperation( + this, providerHelper, this, mActionCanceled); + PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds); // Result sendMessageToHandler(MessageStatus.OKAY, result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java index 99ac73800..ecd351965 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java @@ -45,6 +45,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.ServiceProgressHandler; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; + public class ViewKeyYubiKeyFragment extends Fragment implements LoaderCallbacks { @@ -154,6 +156,11 @@ public class ViewKeyYubiKeyFragment extends Fragment Bundle data = new Bundle(); data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId); data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid); + long[] subKeyIds = new long[mFingerprints.length]; + for (int i = 0; i < subKeyIds.length; i++) { + subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]); + } + data.putLongArray(KeychainIntentService.PROMOTE_SUBKEY_IDS, subKeyIds); intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Create a new Messenger for the communication back @@ -219,7 +226,7 @@ public class ViewKeyYubiKeyFragment extends Fragment } - public Integer naiveIndexOf(byte[][] haystack, byte[] needle) { + static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) { for (int i = 0; i < haystack.length; i++) { if (Arrays.equals(needle, haystack[i])) { return i; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 45b1dc26b..504c9497c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -996,8 +996,11 @@ "Promoting public key to secret key" + "Promoting all subkeysp" "Key not found!" "Fetching key to modify (%s)" + "Promoting subkey: %s" + "Subkey not on Yubikey: %s" "Key successfully promoted"