apply promote operation to specific subkeys present on yubikey only

This commit is contained in:
Vincent Breitmoser 2015-05-17 00:35:10 +02:00
parent f554cc9c93
commit c1e7fcf024
8 changed files with 108 additions and 11 deletions

View File

@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils; import org.sufficientlysecure.keychain.util.TestingUtils;
@ -104,7 +105,7 @@ public class PromoteKeyOperationTest {
PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application, PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application,
new ProviderHelper(Robolectric.application), null, null); 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()); Assert.assertTrue("promotion must succeed", result.success());
@ -130,7 +131,7 @@ public class PromoteKeyOperationTest {
byte[] aid = Hex.decode("D2760001240102000000012345670000"); 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()); 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());
}
}
}
}
} }

View File

@ -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.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; 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.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** An operation which promotes a public key ring to a secret one. /** 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); 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(); OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0); log.add(LogType.MSG_PR, 0);
@ -65,8 +67,24 @@ public class PromoteKeyOperation extends BaseOperation {
CanonicalizedPublicKeyRing pubRing = CanonicalizedPublicKeyRing pubRing =
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); 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 // create divert-to-card secret key from public key
promotedRing = pubRing.createDivertSecretRing(cardAid); promotedRing = pubRing.createDivertSecretRing(cardAid, subKeyIds);
} catch (NotFoundException e) { } catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); 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;
}
} }

View File

@ -566,8 +566,11 @@ public abstract class OperationResult implements Parcelable {
// promote key // promote key
MSG_PR (LogLevel.START, R.string.msg_pr), 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_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_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), MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
// messages used in UI code // messages used in UI code

View File

@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
@ -127,7 +128,11 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
} }
public CanonicalizedPublicKey getPublicKey(long id) { 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 { public byte[] getEncoded() throws IOException {

View File

@ -103,9 +103,22 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
} }
/** Create a dummy secret ring from this key */ /** 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); 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);
} }
} }

View File

@ -65,7 +65,6 @@ import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -185,6 +184,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// promote key // promote key
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; 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_CARD_AID = "promote_card_aid";
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
// consolidate // consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
@ -476,10 +476,12 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input // Input
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID); byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
// Operation // Operation
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); PromoteKeyOperation op = new PromoteKeyOperation(
PromoteKeyResult result = op.execute(keyRingId, cardAid); this, providerHelper, this, mActionCanceled);
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
// Result // Result
sendMessageToHandler(MessageStatus.OKAY, result); sendMessageToHandler(MessageStatus.OKAY, result);

View File

@ -45,6 +45,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
public class ViewKeyYubiKeyFragment extends Fragment public class ViewKeyYubiKeyFragment extends Fragment
implements LoaderCallbacks<Cursor> { implements LoaderCallbacks<Cursor> {
@ -154,6 +156,11 @@ public class ViewKeyYubiKeyFragment extends Fragment
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId); data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid); 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); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back // 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++) { for (int i = 0; i < haystack.length; i++) {
if (Arrays.equals(needle, haystack[i])) { if (Arrays.equals(needle, haystack[i])) {
return i; return i;

View File

@ -1005,8 +1005,11 @@
<!-- Promote key --> <!-- Promote key -->
<string name="msg_pr">"Promoting public key to secret key"</string> <string name="msg_pr">"Promoting public key to secret key"</string>
<string name="msg_pr_all">"Promoting all subkeysp"</string>
<string name="msg_pr_error_key_not_found">"Key not found!"</string> <string name="msg_pr_error_key_not_found">"Key not found!"</string>
<string name="msg_pr_fetching">"Fetching key to modify (%s)"</string> <string name="msg_pr_fetching">"Fetching key to modify (%s)"</string>
<string name="msg_pr_subkey_match">"Promoting subkey: %s"</string>
<string name="msg_pr_subkey_nomatch">"Subkey not on Yubikey: %s"</string>
<string name="msg_pr_success">"Key successfully promoted"</string> <string name="msg_pr_success">"Key successfully promoted"</string>
<!-- Other messages used in OperationLogs --> <!-- Other messages used in OperationLogs -->