wrapped-key-ring: redesign underlying CachedKeyRing

This commit is contained in:
Vincent Breitmoser 2014-05-04 12:55:22 +02:00
parent d0e3af505c
commit 411b4cfeb2
6 changed files with 157 additions and 195 deletions

View File

@ -3,45 +3,66 @@ package org.sufficientlysecure.keychain.pgp;
public abstract class CachedKeyRing { public abstract class CachedKeyRing {
private final long mMasterKeyId; private final long mMasterKeyId;
private final boolean mCanCertify;
private final byte[] mFingerprint;
private final String mUserId; private final String mUserId;
private final boolean mHasAnySecret;
private final boolean mIsRevoked;
private final boolean mCanCertify;
private final long mHasEncryptId;
private final long mHasSignId;
private final int mVerified; private final int mVerified;
private final boolean mHasSecret;
protected CachedKeyRing(long masterKeyId, boolean canCertify, protected CachedKeyRing(long masterKeyId, String userId, boolean hasAnySecret,
byte[] fingerprint, String userId, int verified, boolean hasSecret) boolean isRevoked, boolean canCertify, long hasEncryptId, long hasSignId,
int verified)
{ {
mMasterKeyId = masterKeyId; mMasterKeyId = masterKeyId;
mCanCertify = canCertify;
mFingerprint = fingerprint;
mUserId = userId; mUserId = userId;
mHasAnySecret = hasAnySecret;
mIsRevoked = isRevoked;
mCanCertify = canCertify;
mHasEncryptId = hasEncryptId;
mHasSignId = hasSignId;
mVerified = verified; mVerified = verified;
mHasSecret = hasSecret;
}
public byte[] getFingerprint() {
return mFingerprint;
}
public String getPrimaryUserId() {
return mUserId;
} }
public long getMasterKeyId() { public long getMasterKeyId() {
return mMasterKeyId; return mMasterKeyId;
} }
public int getVerified() { public String getPrimaryUserId() {
return mVerified; return mUserId;
}
public boolean hasAnySecret() {
return mHasAnySecret;
}
public boolean isRevoked() {
return mIsRevoked;
} }
public boolean canCertify() { public boolean canCertify() {
return mCanCertify; return mCanCertify;
} }
public boolean hasSecret() { public long getEncryptId() {
return mHasSecret; return mHasEncryptId;
}
public boolean hasEncrypt() {
return mHasEncryptId != 0;
}
public long getSignId() {
return mHasSignId;
}
public boolean hasSign() {
return mHasSignId != 0;
}
public int getVerified() {
return mVerified;
} }
} }

View File

@ -23,14 +23,14 @@ public class CachedPublicKeyRing extends CachedKeyRing {
private PGPPublicKeyRing mRing; private PGPPublicKeyRing mRing;
private final byte[] mPubKey; private final byte[] mPubKey;
public CachedPublicKeyRing(long masterKeyId, int keySize, boolean isRevoked, public CachedPublicKeyRing(long masterKeyId, String userId, boolean hasAnySecret,
boolean canCertify, long creation, long expiry, int algorithm, boolean isRevoked, boolean canCertify, long hasEncryptId, long hasSignId,
byte[] fingerprint, String userId, int verified, boolean hasSecret, int verified, byte[] blob)
byte[] pubkey)
{ {
super(masterKeyId, canCertify, fingerprint, userId, verified, hasSecret); super(masterKeyId, userId, hasAnySecret, isRevoked, canCertify,
hasEncryptId, hasSignId, verified);
mPubKey = pubkey; mPubKey = blob;
} }
PGPPublicKeyRing getRing() { PGPPublicKeyRing getRing() {
@ -52,46 +52,18 @@ public class CachedPublicKeyRing extends CachedKeyRing {
return new CachedPublicKey(this, getRing().getPublicKey(id)); return new CachedPublicKey(this, getRing().getPublicKey(id));
} }
public CachedPublicKey getFirstSignSubkey() throws PgpGeneralException { /** Getter that returns the subkey that should be used for signing. */
// only return master key if no other signing key is available CachedPublicKey getEncryptionSubKey() throws PgpGeneralException {
CachedPublicKey masterKey = null; PGPPublicKey key = getRing().getPublicKey(getEncryptId());
for (PGPPublicKey k : new IterableIterator<PGPPublicKey>(getRing().getPublicKeys())) { if(key != null) {
CachedPublicKey key = new CachedPublicKey(this, k); CachedPublicKey cKey = new CachedPublicKey(this, key);
if (key.isRevoked() || key.canSign() || key.isExpired()) { if(!cKey.canEncrypt()) {
continue; throw new PgpGeneralException("key error");
}
if (key.isMasterKey()) {
masterKey = key;
} else {
return key;
} }
return cKey;
} }
if(masterKey == null) { // TODO handle with proper exception
// TODO proper exception throw new PgpGeneralException("no encryption key available");
throw new PgpGeneralException("key not found");
}
return masterKey;
}
public CachedPublicKey getFirstEncryptSubkey() throws PgpGeneralException {
// only return master key if no other encryption key is available
CachedPublicKey masterKey = null;
for (PGPPublicKey k : new IterableIterator<PGPPublicKey>(getRing().getPublicKeys())) {
CachedPublicKey key = new CachedPublicKey(this, k);
if (key.isRevoked() || key.canEncrypt() || key.isExpired()) {
continue;
}
if (key.isMasterKey()) {
masterKey = key;
} else {
return key;
}
}
if(masterKey == null) {
// TODO proper exception
throw new PgpGeneralException("key not found");
}
return masterKey;
} }
public boolean verifySubkeyBinding(CachedPublicKey cachedSubkey) { public boolean verifySubkeyBinding(CachedPublicKey cachedSubkey) {
@ -189,4 +161,24 @@ public class CachedPublicKeyRing extends CachedKeyRing {
return validPrimaryKeyBinding; return validPrimaryKeyBinding;
} }
public IterableIterator<CachedPublicKey> iterator() {
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
return new IterableIterator<CachedPublicKey>(new Iterator<CachedPublicKey>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public CachedPublicKey next() {
return new CachedPublicKey(CachedPublicKeyRing.this, it.next());
}
@Override
public void remove() {
it.remove();
}
});
}
} }

View File

@ -1,18 +1,15 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import java.io.IOException; import java.io.IOException;
@ -23,12 +20,13 @@ public class CachedSecretKeyRing extends CachedKeyRing {
private PGPSecretKeyRing mRing; private PGPSecretKeyRing mRing;
public CachedSecretKeyRing(long masterKeyId, int keySize, boolean isRevoked, public CachedSecretKeyRing(long masterKeyId, String userId, boolean hasAnySecret,
boolean canCertify, long creation, long expiry, int algorithm, boolean isRevoked, boolean canCertify, long hasEncryptId, long hasSignId,
byte[] fingerprint, String userId, int verified, boolean hasSecret, int verified, byte[] blob)
byte[] blob)
{ {
super(masterKeyId, canCertify, fingerprint, userId, verified, hasSecret); super(masterKeyId, userId, hasAnySecret,
isRevoked, canCertify, hasEncryptId, hasSignId,
verified);
mRing = (PGPSecretKeyRing) PgpConversionHelper.BytesToPGPKeyRing(blob); mRing = (PGPSecretKeyRing) PgpConversionHelper.BytesToPGPKeyRing(blob);
} }
@ -45,8 +43,18 @@ public class CachedSecretKeyRing extends CachedKeyRing {
return new CachedSecretKey(this, mRing.getSecretKey(id)); return new CachedSecretKey(this, mRing.getSecretKey(id));
} }
public IterableIterator<CachedSecretKey> iterator() { /** Getter that returns the subkey that should be used for signing. */
return new IterableIterator<CachedSecretKey>(mRing.getSecretKeys()); CachedSecretKey getSigningSubKey() throws PgpGeneralException {
PGPSecretKey key = mRing.getSecretKey(getSignId());
if(key != null) {
CachedSecretKey cKey = new CachedSecretKey(this, key);
if(!cKey.canSign()) {
throw new PgpGeneralException("key error");
}
return cKey;
}
// TODO handle with proper exception
throw new PgpGeneralException("no signing key available");
} }
public boolean hasPassphrase() { public boolean hasPassphrase() {
@ -74,50 +82,6 @@ public class CachedSecretKeyRing extends CachedKeyRing {
} }
} }
/** This returns the subkey that should be used for signing.
* At this point, this is simply the first suitable subkey.
*/
CachedSecretKey getSigningSubKey() {
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mRing.getSecretKeys())) {
if (isSigningKey(key.getPublicKey())) {
return new CachedSecretKey(this, key);
}
}
// TODO exception
return null;
}
@SuppressWarnings("unchecked")
public static boolean isSigningKey(PGPPublicKey key) {
if (key.getVersion() <= 3) {
return true;
}
// special case
if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) {
return true;
}
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
continue;
}
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
return true;
}
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) {
return true;
}
}
return false;
}
public UncachedSecretKeyRing changeSecretKeyPassphrase(String oldPassphrase, public UncachedSecretKeyRing changeSecretKeyPassphrase(String oldPassphrase,
String newPassphrase) String newPassphrase)
throws IOException, PGPException, NoSuchProviderException { throws IOException, PGPException, NoSuchProviderException {
@ -141,4 +105,24 @@ public class CachedSecretKeyRing extends CachedKeyRing {
} }
public IterableIterator<CachedSecretKey> iterator() {
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
return new IterableIterator<CachedSecretKey>(new Iterator<CachedSecretKey>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public CachedSecretKey next() {
return new CachedSecretKey(CachedSecretKeyRing.this, it.next());
}
@Override
public void remove() {
it.remove();
}
});
}
} }

View File

@ -270,14 +270,15 @@ public class PgpSignEncrypt {
/* Get keys for signature generation for later usage */ /* Get keys for signature generation for later usage */
CachedSecretKey signingKey = null; CachedSecretKey signingKey = null;
if (enableSignature) { if (enableSignature) {
CachedSecretKeyRing signingKeyRing = null; CachedSecretKeyRing signingKeyRing;
try { try {
signingKeyRing = mProviderHelper.getCachedSecretKeyRing(mSignatureMasterKeyId); signingKeyRing = mProviderHelper.getCachedSecretKeyRing(mSignatureMasterKeyId);
} catch (ProviderHelper.NotFoundException e) { } catch (ProviderHelper.NotFoundException e) {
throw new NoSigningKeyException(); throw new NoSigningKeyException();
} }
signingKey = signingKeyRing.getSigningSubKey(); try {
if (signingKey == null) { signingKey = signingKeyRing.getSigningSubKey();
} catch(PgpGeneralException e) {
throw new NoSigningKeyException(); throw new NoSigningKeyException();
} }
@ -319,7 +320,7 @@ public class PgpSignEncrypt {
try { try {
CachedPublicKeyRing keyRing = mProviderHelper.getCachedPublicKeyRing( CachedPublicKeyRing keyRing = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(Long.toString(id))); KeyRings.buildUnifiedKeyRingUri(Long.toString(id)));
CachedPublicKey key = keyRing.getFirstEncryptSubkey(); CachedPublicKey key = keyRing.getEncryptionSubKey();
cPk.addMethod(key.getPubKeyEncryptionGenerator()); cPk.addMethod(key.getPubKeyEncryptionGenerator());
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
Log.e(Constants.TAG, "key not found!", e); Log.e(Constants.TAG, "key not found!", e);

View File

@ -38,6 +38,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.CachedKeyRing;
import org.sufficientlysecure.keychain.pgp.CachedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.CachedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.CachedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
@ -193,86 +194,55 @@ public class ProviderHelper {
} }
public CachedPublicKeyRing getCachedPublicKeyRing(long id) throws NotFoundException { public CachedPublicKeyRing getCachedPublicKeyRing(long id) throws NotFoundException {
return getCachedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(Long.toString(id))); return (CachedPublicKeyRing) getCachedKeyRing(
KeyRings.buildUnifiedKeyRingUri(Long.toString(id)), false);
} }
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws NotFoundException { public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws NotFoundException {
Cursor cursor = mContentResolver.query(queryUri, return (CachedPublicKeyRing) getCachedKeyRing(queryUri, false);
new String[] {
KeyRings.MASTER_KEY_ID, KeyRings.KEY_SIZE,
KeyRings.IS_REVOKED, KeyRings.CAN_CERTIFY,
KeyRings.CREATION, KeyRings.EXPIRY,
KeyRings.ALGORITHM, KeyRings.FINGERPRINT,
KeyRings.USER_ID, KeyRings.VERIFIED,
KeyRings.HAS_SECRET, KeyRings.PUBKEY_DATA
}, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long masterKeyId = cursor.getLong(0);
int keySize = cursor.getInt(1);
boolean isRevoked = cursor.getInt(2) > 0;
boolean canCertify = cursor.getInt(3) > 0;
long creation = cursor.getLong(4);
long expiry = cursor.getLong(5);
int algorithm = cursor.getInt(6);
byte[] fingerprint = cursor.getBlob(7);
String userId = cursor.getString(8);
int verified = cursor.getInt(9);
boolean hasSecret = cursor.getInt(10) > 0;
byte[] pubkey = cursor.getBlob(11);
return new CachedPublicKeyRing(
masterKeyId, keySize, isRevoked, canCertify,
creation, expiry, algorithm, fingerprint,
userId, verified, hasSecret, pubkey
);
} else {
throw new NotFoundException("Key not found!");
}
} finally {
if (cursor != null) {
cursor.close();
}
}
} }
public CachedSecretKeyRing getCachedSecretKeyRing(long id) throws NotFoundException { public CachedSecretKeyRing getCachedSecretKeyRing(long id) throws NotFoundException {
return getCachedSecretKeyRing(KeyRings.buildUnifiedKeyRingUri(Long.toString(id))); return (CachedSecretKeyRing) getCachedKeyRing(
KeyRings.buildUnifiedKeyRingUri(Long.toString(id)), true);
} }
public CachedSecretKeyRing getCachedSecretKeyRing(Uri queryUri) throws NotFoundException { public CachedSecretKeyRing getCachedSecretKeyRing(Uri queryUri) throws NotFoundException {
return (CachedSecretKeyRing) getCachedKeyRing(queryUri, true);
}
private CachedKeyRing getCachedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
Cursor cursor = mContentResolver.query(queryUri, Cursor cursor = mContentResolver.query(queryUri,
new String[] { new String[] {
KeyRings.MASTER_KEY_ID, KeyRings.KEY_SIZE, // we pick from cache:
KeyRings.IS_REVOKED, KeyRings.CAN_CERTIFY, // basic data, primary uid in particular because it's expensive
KeyRings.CREATION, KeyRings.EXPIRY, KeyRings.MASTER_KEY_ID, KeyRings.USER_ID, KeyRings.HAS_ANY_SECRET,
KeyRings.ALGORITHM, KeyRings.FINGERPRINT, // complex knowledge about subkeys
KeyRings.USER_ID, KeyRings.VERIFIED, KeyRings.IS_REVOKED, KeyRings.CAN_CERTIFY, KeyRings.HAS_ENCRYPT, KeyRings.HAS_SIGN,
KeyRings.HAS_SECRET, KeyRings.PRIVKEY_DATA // stuff only the db knows and of course, ring data
KeyRings.VERIFIED, secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA
}, null, null, null); }, null, null, null);
try { try {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
// check if a privkey is actually available
byte[] privkey = cursor.getBlob(11);
if(privkey == null) {
throw new NotFoundException("Key found, but no secret key available!");
}
long masterKeyId = cursor.getLong(0); long masterKeyId = cursor.getLong(0);
int keySize = cursor.getInt(1); String userId = cursor.getString(1);
boolean isRevoked = cursor.getInt(2) > 0; boolean hasAnySecret = cursor.getInt(2) > 0;
boolean canCertify = cursor.getInt(3) > 0; boolean isRevoked = cursor.getInt(3) > 0;
long creation = cursor.getLong(4); boolean canCertify = cursor.getInt(4) > 0;
long expiry = cursor.getLong(5); long hasEncryptId = cursor.getLong(5);
int algorithm = cursor.getInt(6); long hasSignId = cursor.getLong(6);
byte[] fingerprint = cursor.getBlob(7); int verified = cursor.getInt(7);
String userId = cursor.getString(8); byte[] blob = cursor.getBlob(8);
int verified = cursor.getInt(9); return secret
boolean hasSecret = cursor.getInt(10) > 0; ? new CachedSecretKeyRing(
return new CachedSecretKeyRing( masterKeyId, userId, hasAnySecret,
masterKeyId, keySize, isRevoked, canCertify, isRevoked, canCertify, hasEncryptId, hasSignId,
creation, expiry, algorithm, fingerprint, verified, blob)
userId, verified, hasSecret, privkey : new CachedPublicKeyRing(
); masterKeyId, userId, hasAnySecret,
isRevoked, canCertify, hasEncryptId, hasSignId,
verified, blob);
} else { } else {
throw new NotFoundException("Key not found!"); throw new NotFoundException("Key not found!");
} }

View File

@ -30,16 +30,10 @@ import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CachedPublicKey;
import org.sufficientlysecure.keychain.pgp.CachedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CachedSecretKey;
import org.sufficientlysecure.keychain.pgp.CachedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -155,7 +149,7 @@ public class EncryptAsymmetricFragment extends Fragment {
try { try {
CachedPublicKeyRing keyring = CachedPublicKeyRing keyring =
providerHelper.getCachedPublicKeyRing(preselectedSignatureKeyId); providerHelper.getCachedPublicKeyRing(preselectedSignatureKeyId);
if(keyring.hasSecret()) { if(keyring.hasAnySecret()) {
setSignatureKeyId(keyring.getMasterKeyId()); setSignatureKeyId(keyring.getMasterKeyId());
} }
} catch (ProviderHelper.NotFoundException e) { } catch (ProviderHelper.NotFoundException e) {