introduce minimize extra to ACTION_GET_KEY

This commit is contained in:
Vincent Breitmoser
2016-11-21 15:37:24 +01:00
parent abd82cd88f
commit d4731f68bd
6 changed files with 207 additions and 8 deletions

View File

@@ -62,6 +62,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().getFingerprint(); return getRing().getPublicKey().getFingerprint();
} }
public byte[] getRawPrimaryUserId() throws PgpKeyNotFoundException {
return getPublicKey().getRawPrimaryUserId();
}
public String getPrimaryUserId() throws PgpKeyNotFoundException { public String getPrimaryUserId() throws PgpKeyNotFoundException {
return getPublicKey().getPrimaryUserId(); return getPublicKey().getPrimaryUserId();
} }
@@ -136,6 +140,15 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
} }
} }
public long getSigningId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canSign() && key.isValid()) {
return key.getKeyId();
}
}
throw new PgpKeyNotFoundException("No valid signing key found!");
}
public void encode(OutputStream stream) throws IOException { public void encode(OutputStream stream) throws IOException {
getRing().encode(stream); getRing().encode(stream);
} }

View File

@@ -18,6 +18,13 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import android.support.annotation.Nullable;
import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
@@ -27,8 +34,6 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
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;
import java.io.IOException;
import java.util.Iterator;
public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
@@ -84,6 +89,69 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
}); });
} }
/** Returns a minimized version of this key.
*
* The minimized version includes:
* - the master key
* - the current best signing key (if any)
* - one encryption key (if any)
* - the user id that matches the userIdToKeep parameter, or the primary user id if none matches
* each with their most recent binding certificates
*/
public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException, PgpKeyNotFoundException {
CanonicalizedPublicKey masterKey = getPublicKey();
PGPPublicKey masterPubKey = masterKey.getPublicKey();
boolean userIdStrippedOk = false;
if (userIdToKeep != null) {
try {
masterPubKey = PGPPublicKeyUtils.keepOnlyUserId(masterPubKey, userIdToKeep);
userIdStrippedOk = true;
} catch (NoSuchElementException e) {
// will be handled because userIdStrippedOk is false
}
}
if (!userIdStrippedOk) {
byte[] rawPrimaryUserId = getRawPrimaryUserId();
masterPubKey = PGPPublicKeyUtils.keepOnlyRawUserId(masterPubKey, rawPrimaryUserId);
}
masterPubKey = PGPPublicKeyUtils.keepOnlySelfCertsForUserIds(masterPubKey);
masterPubKey = PGPPublicKeyUtils.removeAllUserAttributes(masterPubKey);
masterPubKey = PGPPublicKeyUtils.removeAllDirectKeyCerts(masterPubKey);
PGPPublicKeyRing resultRing = new PGPPublicKeyRing(masterPubKey.getEncoded(), new JcaKeyFingerprintCalculator());
Long encryptId;
try {
encryptId = getEncryptId();
// only add if this key doesn't coincide with master key
if (encryptId != getMasterKeyId()) {
CanonicalizedPublicKey encryptKey = getPublicKey(encryptId);
PGPPublicKey encryptPubKey = encryptKey.getPublicKey();
resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, encryptPubKey);
}
} catch (PgpKeyNotFoundException e) {
// no encryption key: can't be reasonably minimized
return null;
}
try {
long signingId = getSigningId();
// only add if this key doesn't coincide with master or encryption key
if (signingId != encryptId && signingId != getMasterKeyId()) {
CanonicalizedPublicKey signingKey = getPublicKey(signingId);
PGPPublicKey signingPubKey = signingKey.getPublicKey();
resultRing = PGPPublicKeyRing.insertPublicKey(resultRing, signingPubKey);
}
} catch (PgpKeyNotFoundException e) {
// no signing key: can't be reasonably minimized
return null;
}
return new CanonicalizedPublicKeyRing(resultRing, getVerified());
}
/** Create a dummy secret ring from this key */ /** Create a dummy secret ring from this key */
public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) { public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid); PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);

View File

@@ -0,0 +1,99 @@
package org.sufficientlysecure.keychain.pgp;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.sufficientlysecure.keychain.util.Utf8Util;
@SuppressWarnings("unchecked") // BouncyCastle doesn't do generics here :(
class PGPPublicKeyUtils {
static PGPPublicKey keepOnlyRawUserId(PGPPublicKey masterPublicKey, byte[] rawUserIdToKeep) {
boolean elementToKeepFound = false;
Iterator<byte[]> it = masterPublicKey.getRawUserIDs();
while (it.hasNext()) {
byte[] rawUserId = it.next();
if (Arrays.equals(rawUserId, rawUserIdToKeep)) {
elementToKeepFound = true;
} else {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId);
}
}
if (!elementToKeepFound) {
throw new NoSuchElementException();
}
return masterPublicKey;
}
static PGPPublicKey keepOnlyUserId(PGPPublicKey masterPublicKey, String userIdToKeep) {
boolean elementToKeepFound = false;
Iterator<byte[]> it = masterPublicKey.getRawUserIDs();
while (it.hasNext()) {
byte[] rawUserId = it.next();
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
if (userId.contains(userIdToKeep)) {
elementToKeepFound = true;
} else {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, rawUserId);
}
}
if (!elementToKeepFound) {
throw new NoSuchElementException();
}
return masterPublicKey;
}
static PGPPublicKey keepOnlySelfCertsForUserIds(PGPPublicKey masterPubKey) {
long masterKeyId = masterPubKey.getKeyID();
Iterator<byte[]> it = masterPubKey.getRawUserIDs();
while (it.hasNext()) {
byte[] rawUserId = it.next();
masterPubKey = keepOnlySelfCertsForRawUserId(masterPubKey, masterKeyId, rawUserId);
}
return masterPubKey;
}
private static PGPPublicKey keepOnlySelfCertsForRawUserId(
PGPPublicKey masterPubKey, long masterKeyId, byte[] rawUserId) {
Iterator<PGPSignature> it = masterPubKey.getSignaturesForID(rawUserId);
while (it.hasNext()) {
PGPSignature sig = it.next();
if (sig.getKeyID() != masterKeyId) {
masterPubKey = PGPPublicKey.removeCertification(masterPubKey, rawUserId, sig);
}
}
return masterPubKey;
}
static PGPPublicKey removeAllUserAttributes(PGPPublicKey masterPubKey) {
Iterator<PGPUserAttributeSubpacketVector> it = masterPubKey.getUserAttributes();
while (it.hasNext()) {
masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next());
}
return masterPubKey;
}
static PGPPublicKey removeAllDirectKeyCerts(PGPPublicKey masterPubKey) {
Iterator<PGPSignature> it = masterPubKey.getSignaturesOfType(PGPSignature.DIRECT_KEY);
while (it.hasNext()) {
masterPubKey = PGPPublicKey.removeCertification(masterPubKey, it.next());
}
return masterPubKey;
}
}

View File

@@ -105,6 +105,24 @@ public class UncachedPublicKey {
* *
*/ */
public String getPrimaryUserId() { public String getPrimaryUserId() {
byte[] found = getRawPrimaryUserId();
if (found != null) {
return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found);
} else {
return null;
}
}
/** 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 byte[] getRawPrimaryUserId() {
byte[] found = null; byte[] found = null;
PGPSignature foundSig = null; PGPSignature foundSig = null;
// noinspection unchecked // noinspection unchecked
@@ -161,11 +179,7 @@ public class UncachedPublicKey {
} }
} }
} }
if (found != null) { return found;
return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found);
} else {
return null;
}
} }
/** /**

View File

@@ -544,6 +544,11 @@ public class OpenPgpService extends Service {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
if (data.getBooleanExtra(OpenPgpApi.EXTRA_MINIMIZE, false)) {
String userIdToKeep = data.getStringExtra(OpenPgpApi.EXTRA_MINIMIZE_USER_ID);
keyRing = keyRing.minimize(userIdToKeep);
}
boolean requestedKeyData = outputStream != null; boolean requestedKeyData = outputStream != null;
if (requestedKeyData) { if (requestedKeyData) {
boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false); boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false);