extract subkey loading from KeychainProvider

This commit is contained in:
Vincent Breitmoser
2018-06-22 19:23:00 +02:00
parent 500c219fa0
commit 6cd065a3bd
17 changed files with 248 additions and 421 deletions

View File

@@ -9,8 +9,8 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import com.squareup.sqldelight.SqlDelightQuery; import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.model.Key; import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
@@ -28,11 +28,11 @@ public class KeyRingDao {
} }
public List<UnifiedKeyInfo> getUnifiedKeyInfo() { public List<UnifiedKeyInfo> getUnifiedKeyInfo() {
SqlDelightQuery query = Key.FACTORY.selectAllUnifiedKeyInfo(); SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
List<UnifiedKeyInfo> result = new ArrayList<>(); List<UnifiedKeyInfo> result = new ArrayList<>();
try (Cursor cursor = db.query(query)) { try (Cursor cursor = db.query(query)) {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
UnifiedKeyInfo unifiedKeyInfo = Key.UNIFIED_KEY_INFO_MAPPER.map(cursor); UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor);
result.add(unifiedKeyInfo); result.add(unifiedKeyInfo);
} }
} }

View File

@@ -7,6 +7,7 @@ import android.support.annotation.NonNull;
import com.squareup.sqldelight.ColumnAdapter; import com.squareup.sqldelight.ColumnAdapter;
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
@@ -50,4 +51,17 @@ public final class CustomColumnAdapters {
} }
} }
}; };
public static final ColumnAdapter<SecretKeyType,Long> SECRET_KEY_TYPE_ADAPTER = new ColumnAdapter<SecretKeyType, Long>() {
@NonNull
@Override
public SecretKeyType decode(Long databaseValue) {
return databaseValue == null ? SecretKeyType.UNAVAILABLE : SecretKeyType.fromNum(databaseValue.intValue());
}
@Override
public Long encode(@NonNull SecretKeyType value) {
return (long) value.getNum();
}
};
} }

View File

@@ -6,14 +6,23 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.squareup.sqldelight.RowMapper;
import org.sufficientlysecure.keychain.KeysModel; import org.sufficientlysecure.keychain.KeysModel;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
@AutoValue @AutoValue
public abstract class Key implements KeysModel { public abstract class SubKey implements KeysModel {
public static final Factory<Key> FACTORY = new Factory<>(AutoValue_Key::new); public static final Factory<SubKey> FACTORY =
new Factory<>(AutoValue_SubKey::new, CustomColumnAdapters.SECRET_KEY_TYPE_ADAPTER);
public static final SelectAllUnifiedKeyInfoMapper<UnifiedKeyInfo> UNIFIED_KEY_INFO_MAPPER = public static final SelectAllUnifiedKeyInfoMapper<UnifiedKeyInfo> UNIFIED_KEY_INFO_MAPPER =
FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_Key_UnifiedKeyInfo::new); FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_SubKey_UnifiedKeyInfo::new);
public static Mapper<SubKey> SUBKEY_MAPPER = new Mapper<>(FACTORY);
public static RowMapper<SecretKeyType> SKT_MAPPER = FACTORY.selectSecretKeyTypeMapper();
public boolean expires() {
return expiry() != null;
}
@AutoValue @AutoValue
public static abstract class UnifiedKeyInfo implements SelectAllUnifiedKeyInfoModel { public static abstract class UnifiedKeyInfo implements SelectAllUnifiedKeyInfoModel {
@@ -45,6 +54,5 @@ public abstract class Key implements KeysModel {
} }
return autocryptPackageNames; return autocryptPackageNames;
} }
} }
} }

View File

@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult; import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult;
@@ -27,7 +28,6 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.openintents.openpgp.util.OpenPgpUtils.UserId;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import timber.log.Timber; import timber.log.Timber;
@@ -41,8 +41,8 @@ public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult // OpenPgpSignatureResult
private String mPrimaryUserId; private String mPrimaryUserId;
private ArrayList<String> mUserIds = new ArrayList<>(); private List<String> mUserIds = new ArrayList<>();
private ArrayList<String> mConfirmedUserIds; private List<String> mConfirmedUserIds;
private long mKeyId; private long mKeyId;
private SenderStatusResult mSenderStatusResult; private SenderStatusResult mSenderStatusResult;
@@ -101,7 +101,7 @@ public class OpenPgpSignatureResultBuilder {
this.mIsKeyExpired = keyExpired; this.mIsKeyExpired = keyExpired;
} }
public void setUserIds(ArrayList<String> userIds, ArrayList<String> confirmedUserIds) { public void setUserIds(List<String> userIds, List<String> confirmedUserIds) {
this.mUserIds = userIds; this.mUserIds = userIds;
this.mConfirmedUserIds = confirmedUserIds; this.mConfirmedUserIds = confirmedUserIds;
} }
@@ -125,9 +125,8 @@ public class OpenPgpSignatureResultBuilder {
} }
setSignatureKeyCertified(signingRing.getVerified() > 0); setSignatureKeyCertified(signingRing.getVerified() > 0);
ArrayList<String> allUserIds = signingRing.getUnorderedUserIds(); List<String> allUserIds = signingRing.getUnorderedUserIds();
ArrayList<String> confirmedUserIds; List<String> confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId());
confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId());
setUserIds(allUserIds, confirmedUserIds); setUserIds(allUserIds, confirmedUserIds);
mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds); mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds);
@@ -138,7 +137,7 @@ public class OpenPgpSignatureResultBuilder {
} }
private SenderStatusResult processSenderStatusResult( private SenderStatusResult processSenderStatusResult(
ArrayList<String> allUserIds, ArrayList<String> confirmedUserIds) { List<String> allUserIds, List<String> confirmedUserIds) {
if (mSenderAddress == null) { if (mSenderAddress == null) {
return SenderStatusResult.UNKNOWN; return SenderStatusResult.UNKNOWN;
} }
@@ -152,7 +151,7 @@ public class OpenPgpSignatureResultBuilder {
} }
} }
private static boolean userIdListContainsAddress(String senderAddress, ArrayList<String> confirmedUserIds) { private static boolean userIdListContainsAddress(String senderAddress, List<String> confirmedUserIds) {
for (String rawUserId : confirmedUserIds) { for (String rawUserId : confirmedUserIds) {
UserId userId = OpenPgpUtils.splitUserId(rawUserId); UserId userId = OpenPgpUtils.splitUserId(rawUserId);
if (senderAddress.equalsIgnoreCase(userId.email)) { if (senderAddress.equalsIgnoreCase(userId.email)) {

View File

@@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import timber.log.Timber; import timber.log.Timber;
@@ -227,10 +226,6 @@ public class CachedPublicKeyRing extends KeyRing {
} }
} }
public boolean hasSecretAuthentication() throws PgpKeyNotFoundException {
return getSecretAuthenticationId() != 0;
}
public long getAuthenticationId() throws PgpKeyNotFoundException { public long getAuthenticationId() throws PgpKeyNotFoundException {
try { try {
Object data = mKeyRepository.getGenericData(mUri, Object data = mKeyRepository.getGenericData(mUri,
@@ -242,10 +237,6 @@ public class CachedPublicKeyRing extends KeyRing {
} }
} }
public boolean hasAuthentication() throws PgpKeyNotFoundException {
return getAuthenticationId() != 0;
}
@Override @Override
public int getVerified() throws PgpKeyNotFoundException { public int getVerified() throws PgpKeyNotFoundException {
try { try {
@@ -270,11 +261,11 @@ public class CachedPublicKeyRing extends KeyRing {
} }
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
Object data = mKeyRepository.getGenericData(Keys.buildKeysUri(mUri), SecretKeyType secretKeyType = mKeyRepository.getSecretKeyType(keyId);
KeyRings.HAS_SECRET, if (secretKeyType == null) {
KeyRepository.FIELD_TYPE_INTEGER, throw new NotFoundException();
KeyRings.KEY_ID + " = " + Long.toString(keyId)); }
return SecretKeyType.fromNum(((Long) data).intValue()); return secretKeyType;
} }
public byte[] getEncoded() throws PgpKeyNotFoundException { public byte[] getEncoded() throws PgpKeyNotFoundException {

View File

@@ -32,11 +32,13 @@ import android.net.Uri;
import com.squareup.sqldelight.SqlDelightQuery; import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket;
import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.model.UserPacket.UserId;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -119,11 +121,6 @@ public class KeyRepository extends AbstractDao {
return result; return result;
} }
Object getGenericData(Uri uri, String column, int type, String selection)
throws NotFoundException {
return getGenericData(uri, new String[]{column}, new int[]{type}, selection).get(column);
}
private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types) private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types)
throws NotFoundException { throws NotFoundException {
return getGenericData(uri, proj, types, null); return getGenericData(uri, proj, types, null);
@@ -246,7 +243,7 @@ public class KeyRepository extends AbstractDao {
return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); return mapAllRows(query, UserPacket.USER_ID_MAPPER::map);
} }
public ArrayList<String> getConfirmedUserIds(long masterKeyId) { public List<String> getConfirmedUserIds(long masterKeyId) {
ArrayList<String> userIds = new ArrayList<>(); ArrayList<String> userIds = new ArrayList<>();
SqlDelightQuery query = SqlDelightQuery query =
UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(masterKeyId, Certs.VERIFIED_SECRET); UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(masterKeyId, Certs.VERIFIED_SECRET);
@@ -256,6 +253,21 @@ public class KeyRepository extends AbstractDao {
return userIds; return userIds;
} }
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
return mapAllRows(query, SubKey.SUBKEY_MAPPER::map);
}
public SecretKeyType getSecretKeyType(long keyId) {
SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId);
try (Cursor cursor = getReadableDb().query(query)) {
if (cursor.moveToFirst()) {
return SubKey.SKT_MAPPER.map(cursor);
}
return null;
}
}
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException { private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos); ArmoredOutputStream aos = new ArmoredOutputStream(bos);

View File

@@ -130,11 +130,6 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_rings";
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.key_rings";
public static Uri buildUnifiedKeyRingsUri() { public static Uri buildUnifiedKeyRingsUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build();
} }
@@ -194,34 +189,15 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys";
public static Uri buildKeysUri(long masterKeyId) { public static Uri buildKeysUri(long masterKeyId) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build(); return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build();
} }
public static Uri buildKeysUri(Uri uri) {
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_KEYS).build();
}
} }
public static class KeySignatures implements KeySignaturesColumns, BaseColumns { public static class KeySignatures implements KeySignaturesColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_SIGNATURES).build(); .appendPath(BASE_KEY_SIGNATURES).build();
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_signatures";
} }
public static class UserPackets implements UserPacketsColumns, BaseColumns { public static class UserPackets implements UserPacketsColumns, BaseColumns {
@@ -235,11 +211,6 @@ public class KeychainContract {
} }
public static class Certs implements CertsColumns, BaseColumns { public static class Certs implements CertsColumns, BaseColumns {
public static final String USER_ID = UserPacketsColumns.USER_ID;
public static final String NAME = UserPacketsColumns.NAME;
public static final String EMAIL = UserPacketsColumns.EMAIL;
public static final String COMMENT = UserPacketsColumns.COMMENT;
public static final int UNVERIFIED = 0; public static final int UNVERIFIED = 0;
public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SECRET = 1;
public static final int VERIFIED_SELF = 2; public static final int VERIFIED_SELF = 2;

View File

@@ -168,17 +168,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
*/ */
@Override @Override
public String getType(@NonNull Uri uri) { public String getType(@NonNull Uri uri) {
final int match = mUriMatcher.match(uri); throw new UnsupportedOperationException();
switch (match) {
case KEY_RING_KEYS:
return Keys.CONTENT_TYPE;
case KEY_SIGNATURES:
return KeySignatures.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
} }
/** /**
@@ -194,7 +184,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
int match = mUriMatcher.match(uri); int match = mUriMatcher.match(uri);
// all query() parameters, for good measure // all query() parameters, for good measure
String groupBy = null, having = null; String groupBy;
switch (match) { switch (match) {
case KEY_RING_UNIFIED: case KEY_RING_UNIFIED:
@@ -431,34 +421,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
break; break;
} }
case KEY_RING_KEYS: {
HashMap<String, String> projectionMap = new HashMap<>();
projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id");
projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
projectionMap.put(Keys.KEY_ID, Keys.KEY_ID);
projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE);
projectionMap.put(Keys.KEY_CURVE_OID, Keys.KEY_CURVE_OID);
projectionMap.put(Keys.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED);
projectionMap.put(Keys.IS_SECURE, Tables.KEYS + "." + Keys.IS_SECURE);
projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY);
projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT);
projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN);
projectionMap.put(Keys.CAN_AUTHENTICATE, Keys.CAN_AUTHENTICATE);
projectionMap.put(Keys.HAS_SECRET, Keys.HAS_SECRET);
projectionMap.put(Keys.CREATION, Keys.CREATION);
projectionMap.put(Keys.EXPIRY, Keys.EXPIRY);
projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM);
projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT);
qb.setProjectionMap(projectionMap);
qb.setTables(Tables.KEYS);
qb.appendWhere(Keys.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
}
default: { default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
} }
@@ -489,7 +451,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
} }
if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) { if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) {
String rawQuery = qb.buildQuery(projection, selection, groupBy, having, orderBy, null); String rawQuery = qb.buildQuery(projection, selection, groupBy, null, orderBy, null);
DatabaseUtil.explainQuery(db, rawQuery); DatabaseUtil.explainQuery(db, rawQuery);
} }

View File

@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
@@ -36,10 +37,10 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.FileHelper;
@@ -138,32 +139,19 @@ public class BackupRestoreFragment extends Fragment {
} }
private Long getFirstSubKeyWithPassphrase(long masterKeyId, ContentResolver resolver) { private Long getFirstSubKeyWithPassphrase(long masterKeyId, ContentResolver resolver) {
Cursor cursor = resolver.query( KeyRepository keyRepository = KeyRepository.create(requireContext());
KeychainContract.Keys.buildKeysUri(masterKeyId), new String[]{ for (SubKey subKey : keyRepository.getSubKeysByMasterKeyId(masterKeyId)) {
Keys.KEY_ID, switch (subKey.has_secret()) {
Keys.HAS_SECRET, case PASSPHRASE_EMPTY:
}, Keys.HAS_SECRET + " != 0", null, null); case DIVERT_TO_CARD:
try { case UNAVAILABLE:
if (cursor != null) { return null;
while(cursor.moveToNext()) { case GNU_DUMMY:
SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); continue;
switch (secretKeyType) { default: {
case PASSPHRASE_EMPTY: return subKey.key_id();
case DIVERT_TO_CARD:
case UNAVAILABLE:
return null;
case GNU_DUMMY:
continue;
default: {
return cursor.getLong(0);
}
}
} }
} }
} finally {
if (cursor != null) {
cursor.close();
}
} }
return null; return null;
} }

View File

@@ -20,11 +20,11 @@ package org.sufficientlysecure.keychain.ui;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@@ -43,6 +43,7 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.operations.results.UploadResult;
@@ -50,7 +51,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
@@ -410,31 +410,20 @@ public class CreateKeyFinalFragment extends Fragment {
private void moveToCard(final EditKeyResult saveKeyResult) { private void moveToCard(final EditKeyResult saveKeyResult) {
CreateKeyActivity activity = (CreateKeyActivity) getActivity(); CreateKeyActivity activity = (CreateKeyActivity) getActivity();
KeyRepository keyRepository = KeyRepository.create(getContext());
SaveKeyringParcel.Builder builder; SaveKeyringParcel.Builder builder;
CachedPublicKeyRing key = (KeyRepository.create(getContext())) CachedPublicKeyRing key = keyRepository.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId);
.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId);
try { try {
builder = SaveKeyringParcel.buildChangeKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); builder = SaveKeyringParcel.buildChangeKeyringParcel(saveKeyResult.mMasterKeyId, key.getFingerprint());
} catch (PgpKeyNotFoundException e) { } catch (PgpKeyNotFoundException e) {
Timber.e("Key that should be moved to Security Token not found in database!"); Timber.e("Key that should be moved to Security Token not found in database!");
return; return;
} }
// define subkeys that should be moved to the card List<SubKey> subKeys = keyRepository.getSubKeysByMasterKeyId(saveKeyResult.mMasterKeyId);
Cursor cursor = activity.getContentResolver().query( for (SubKey subKey : subKeys) {
KeychainContract.Keys.buildKeysUri(builder.getMasterKeyId()), builder.addOrReplaceSubkeyChange(SubkeyChange.createMoveToSecurityTokenChange(subKey.key_id()));
new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null
);
try {
while (cursor != null && cursor.moveToNext()) {
long subkeyId = cursor.getLong(0);
builder.addOrReplaceSubkeyChange(SubkeyChange.createMoveToSecurityTokenChange(subkeyId));
}
} finally {
if (cursor != null) {
cursor.close();
}
} }
// define new PIN and Admin PIN for the card // define new PIN and Admin PIN for the card

View File

@@ -59,7 +59,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
import org.sufficientlysecure.keychain.livedata.KeyRingDao; import org.sufficientlysecure.keychain.livedata.KeyRingDao;
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;

View File

@@ -18,6 +18,9 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.List;
import android.arch.lifecycle.LiveData;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@@ -41,8 +44,11 @@ import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
@@ -62,7 +68,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
public static final String ARG_DATA_URI = "data_uri"; public static final String ARG_DATA_URI = "data_uri";
private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_UNIFIED = 0;
private static final int LOADER_ID_SUBKEYS = 1;
private ListView mSubkeysList; private ListView mSubkeysList;
private ListView mSubkeysAddedList; private ListView mSubkeysAddedList;
@@ -78,7 +83,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
private long mMasterKeyId; private long mMasterKeyId;
private byte[] mFingerprint; private byte[] mFingerprint;
private boolean mHasSecret;
private SaveKeyringParcel.Builder mEditModeSkpBuilder; private SaveKeyringParcel.Builder mEditModeSkpBuilder;
@Override @Override
@@ -148,13 +152,14 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
mDataUri = dataUri; mDataUri = dataUri;
// Create an empty adapter we will use to display the loaded data. // Create an empty adapter we will use to display the loaded data.
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); mSubkeysAdapter = new SubkeysAdapter(requireContext());
mSubkeysList.setAdapter(mSubkeysAdapter); mSubkeysList.setAdapter(mSubkeysAdapter);
// Prepare the loaders. Either re-connect with an existing ones, // Prepare the loaders. Either re-connect with an existing ones,
// or start new ones. // or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this);
setContentShown(false);
} }
// These are the rows that we will retrieve. // These are the rows that we will retrieve.
@@ -178,14 +183,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
PROJECTION, null, null, null); PROJECTION, null, null, null);
} }
case LOADER_ID_SUBKEYS: {
setContentShown(false);
Uri subkeysUri = KeychainContract.Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), subkeysUri,
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
}
default: default:
return null; return null;
} }
@@ -202,29 +199,29 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
data.moveToFirst(); data.moveToFirst();
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
mFingerprint = data.getBlob(INDEX_FINGERPRINT); mFingerprint = data.getBlob(INDEX_FINGERPRINT);
break;
}
case LOADER_ID_SUBKEYS: {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mSubkeysAdapter.swapCursor(data);
// TODO: maybe show not before both are loaded! KeyRepository keyRepository = KeyRepository.create(requireContext());
setContentShown(true); LiveData<List<SubKey>> subKeyLiveData = new GenericLiveData<>(requireContext(), null,
() -> keyRepository.getSubKeysByMasterKeyId(mMasterKeyId));
subKeyLiveData.observe(this, this::onLoadSubKeys);
break; break;
} }
} }
} }
private void onLoadSubKeys(List<SubKey> subKeys) {
mSubkeysAdapter.setData(subKeys);
setContentShown(true);
}
/** /**
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
* We need to make sure we are no longer using it. * We need to make sure we are no longer using it.
*/ */
public void onLoaderReset(Loader<Cursor> loader) { public void onLoaderReset(Loader<Cursor> loader) {
mSubkeysAdapter.swapCursor(null);
} }
@Override @Override
@@ -256,7 +253,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
mSubkeyAddFabLayout.setDisplayedChild(1); mSubkeyAddFabLayout.setDisplayedChild(1);
mSubkeysAdapter.setEditMode(mEditModeSkpBuilder); mSubkeysAdapter.setEditMode(mEditModeSkpBuilder);
getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this);
mode.setTitle(R.string.title_edit_subkeys); mode.setTitle(R.string.title_edit_subkeys);
mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu);
@@ -281,7 +277,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
mSubkeysAdapter.setEditMode(null); mSubkeysAdapter.setEditMode(null);
mSubkeysAddedLayout.setVisibility(View.GONE); mSubkeysAddedLayout.setVisibility(View.GONE);
mSubkeyAddFabLayout.setDisplayedChild(0); mSubkeyAddFabLayout.setDisplayedChild(0);
getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this);
} }
}); });
} }
@@ -309,7 +304,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
} }
private void editSubkey(final int position) { private void editSubkey(final int position) {
final long keyId = mSubkeysAdapter.getKeyId(position); final SubKey subKey = mSubkeysAdapter.getItem(position);
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
@@ -320,29 +315,28 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
break; break;
case EditSubkeyDialogFragment.MESSAGE_REVOKE: case EditSubkeyDialogFragment.MESSAGE_REVOKE:
// toggle // toggle
if (mEditModeSkpBuilder.getMutableRevokeSubKeys().contains(keyId)) { if (mEditModeSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id())) {
mEditModeSkpBuilder.removeRevokeSubkey(keyId); mEditModeSkpBuilder.removeRevokeSubkey(subKey.key_id());
} else { } else {
mEditModeSkpBuilder.addRevokeSubkey(keyId); mEditModeSkpBuilder.addRevokeSubkey(subKey.key_id());
} }
break; break;
case EditSubkeyDialogFragment.MESSAGE_STRIP: { case EditSubkeyDialogFragment.MESSAGE_STRIP: {
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); if (subKey.has_secret() == SecretKeyType.GNU_DUMMY) {
if (secretKeyType == SecretKeyType.GNU_DUMMY) {
// Key is already stripped; this is a no-op. // Key is already stripped; this is a no-op.
break; break;
} }
SubkeyChange change = mEditModeSkpBuilder.getSubkeyChange(keyId); SubkeyChange change = mEditModeSkpBuilder.getSubkeyChange(subKey.key_id());
if (change == null || !change.getDummyStrip()) { if (change == null || !change.getDummyStrip()) {
mEditModeSkpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(keyId)); mEditModeSkpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(subKey.key_id()));
} else { } else {
mEditModeSkpBuilder.removeSubkeyChange(change); mEditModeSkpBuilder.removeSubkeyChange(change);
} }
break; break;
} }
} }
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); mSubkeysAdapter.notifyDataSetChanged();
} }
}; };
@@ -360,9 +354,10 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
} }
private void editSubkeyExpiry(final int position) { private void editSubkeyExpiry(final int position) {
final long keyId = mSubkeysAdapter.getKeyId(position); SubKey subKey = mSubkeysAdapter.getItem(position);
final Long creationDate = mSubkeysAdapter.getCreationDate(position); final long keyId = subKey.key_id();
final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); final Long creationDate = subKey.creation();
final Long expiryDate = subKey.expiry();
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
@@ -375,7 +370,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
SubkeyChange.createFlagsOrExpiryChange(keyId, null, expiry)); SubkeyChange.createFlagsOrExpiryChange(keyId, null, expiry));
break; break;
} }
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); mSubkeysAdapter.notifyDataSetChanged();
} }
}; };

View File

@@ -17,7 +17,7 @@ import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.flexibleadapter.items.IFlexible;
import eu.davidea.viewholders.FlexibleViewHolder; import eu.davidea.viewholders.FlexibleViewHolder;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem.FlexibleKeyItemViewHolder; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem.FlexibleKeyItemViewHolder;
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.FormattingUtils;

View File

@@ -10,7 +10,7 @@ import android.content.res.Resources;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
public class FlexibleKeyItemFactory { public class FlexibleKeyItemFactory {

View File

@@ -20,15 +20,14 @@ package org.sufficientlysecure.keychain.ui.adapter;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.widget.CursorAdapter;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
@@ -37,121 +36,64 @@ import android.text.style.StyleSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
public class SubkeysAdapter extends CursorAdapter { public class SubkeysAdapter extends BaseAdapter {
private LayoutInflater mInflater; private final Context context;
private final LayoutInflater layoutInflater;
private List<SubKey> data;
private SaveKeyringParcel.Builder mSkpBuilder; private SaveKeyringParcel.Builder mSkpBuilder;
private boolean mHasAnySecret;
private ColorStateList mDefaultTextColor; private ColorStateList mDefaultTextColor;
public static final String[] SUBKEYS_PROJECTION = new String[]{ public SubkeysAdapter(Context context) {
Keys._ID, this.context = context;
Keys.KEY_ID, layoutInflater = LayoutInflater.from(context);
Keys.RANK,
Keys.ALGORITHM,
Keys.KEY_SIZE,
Keys.KEY_CURVE_OID,
Keys.HAS_SECRET,
Keys.CAN_CERTIFY,
Keys.CAN_ENCRYPT,
Keys.CAN_SIGN,
Keys.CAN_AUTHENTICATE,
Keys.IS_REVOKED,
Keys.IS_SECURE,
Keys.CREATION,
Keys.EXPIRY,
Keys.FINGERPRINT
};
private static final int INDEX_ID = 0;
private static final int INDEX_KEY_ID = 1;
private static final int INDEX_RANK = 2;
private static final int INDEX_ALGORITHM = 3;
private static final int INDEX_KEY_SIZE = 4;
private static final int INDEX_KEY_CURVE_OID = 5;
private static final int INDEX_HAS_SECRET = 6;
private static final int INDEX_CAN_CERTIFY = 7;
private static final int INDEX_CAN_ENCRYPT = 8;
private static final int INDEX_CAN_SIGN = 9;
private static final int INDEX_CAN_AUTHENTICATE = 10;
private static final int INDEX_IS_REVOKED = 11;
private static final int INDEX_IS_SECURE = 12;
private static final int INDEX_CREATION = 13;
private static final int INDEX_EXPIRY = 14;
private static final int INDEX_FINGERPRINT = 15;
public SubkeysAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
} }
public long getKeyId(int position) { public void setData(List<SubKey> data) {
mCursor.moveToPosition(position); this.data = data;
return mCursor.getLong(INDEX_KEY_ID); notifyDataSetChanged();
} }
public long getCreationDate(int position) { @Override
mCursor.moveToPosition(position); public int getCount() {
return mCursor.getLong(INDEX_CREATION); return data != null ? data.size() : 0;
} }
public Long getExpiryDate(int position) { @Override
mCursor.moveToPosition(position); public SubKey getItem(int position) {
if (mCursor.isNull(INDEX_EXPIRY)) { return data.get(position);
return null; }
@Override
public long getItemId(int position) {
return data.get(position).key_id();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else { } else {
return mCursor.getLong(INDEX_EXPIRY); view = layoutInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false);
}
}
public int getAlgorithm(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_ALGORITHM);
}
public int getKeySize(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_KEY_SIZE);
}
public String getCurveOid(int position) {
mCursor.moveToPosition(position);
return mCursor.getString(INDEX_KEY_CURVE_OID);
}
public SecretKeyType getSecretKeyType(int position) {
mCursor.moveToPosition(position);
return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET));
}
@Override
public Cursor swapCursor(Cursor newCursor) {
mHasAnySecret = false;
if (newCursor != null && newCursor.moveToFirst()) {
do {
SecretKeyType hasSecret = SecretKeyType.fromNum(newCursor.getInt(INDEX_HAS_SECRET));
if (hasSecret.isUsable()) {
mHasAnySecret = true;
break;
}
} while (newCursor.moveToNext());
} }
return super.swapCursor(newCursor); if (mDefaultTextColor == null) {
} TextView keyId = view.findViewById(R.id.subkey_item_key_id);
mDefaultTextColor = keyId.getTextColors();
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView vKeyId = view.findViewById(R.id.subkey_item_key_id); TextView vKeyId = view.findViewById(R.id.subkey_item_key_id);
TextView vKeyDetails = view.findViewById(R.id.subkey_item_details); TextView vKeyDetails = view.findViewById(R.id.subkey_item_details);
TextView vKeyExpiry = view.findViewById(R.id.subkey_item_expiry); TextView vKeyExpiry = view.findViewById(R.id.subkey_item_expiry);
@@ -166,19 +108,20 @@ public class SubkeysAdapter extends CursorAdapter {
ImageView deleteImage = view.findViewById(R.id.subkey_item_delete_button); ImageView deleteImage = view.findViewById(R.id.subkey_item_delete_button);
deleteImage.setVisibility(View.GONE); deleteImage.setVisibility(View.GONE);
long keyId = cursor.getLong(INDEX_KEY_ID); SubKey subKey = getItem(position);
vKeyId.setText(KeyFormattingUtils.beautifyKeyId(keyId));
vKeyId.setText(KeyFormattingUtils.beautifyKeyId(subKey.key_id()));
// may be set with additional "stripped" later on // may be set with additional "stripped" later on
SpannableStringBuilder algorithmStr = new SpannableStringBuilder(); SpannableStringBuilder algorithmStr = new SpannableStringBuilder();
algorithmStr.append(KeyFormattingUtils.getAlgorithmInfo( algorithmStr.append(KeyFormattingUtils.getAlgorithmInfo(
context, context,
cursor.getInt(INDEX_ALGORITHM), subKey.algorithm(),
cursor.getInt(INDEX_KEY_SIZE), subKey.key_size(),
cursor.getString(INDEX_KEY_CURVE_OID) subKey.key_curve_oid()
)); ));
SubkeyChange change = mSkpBuilder != null ? mSkpBuilder.getSubkeyChange(keyId) : null; SubkeyChange change = mSkpBuilder != null ? mSkpBuilder.getSubkeyChange(subKey.key_id()) : null;
if (change != null && (change.getDummyStrip() || change.getMoveKeyToSecurityToken())) { if (change != null && (change.getDummyStrip() || change.getMoveKeyToSecurityToken())) {
if (change.getDummyStrip()) { if (change.getDummyStrip()) {
algorithmStr.append(", "); algorithmStr.append(", ");
@@ -197,7 +140,7 @@ public class SubkeysAdapter extends CursorAdapter {
algorithmStr.append(boldDivert); algorithmStr.append(boldDivert);
} }
} else { } else {
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) { switch (subKey.has_secret()) {
case GNU_DUMMY: case GNU_DUMMY:
algorithmStr.append(", "); algorithmStr.append(", ");
algorithmStr.append(context.getString(R.string.key_stripped)); algorithmStr.append(context.getString(R.string.key_stripped));
@@ -218,7 +161,7 @@ public class SubkeysAdapter extends CursorAdapter {
} }
vKeyDetails.setText(algorithmStr, TextView.BufferType.SPANNABLE); vKeyDetails.setText(algorithmStr, TextView.BufferType.SPANNABLE);
boolean isMasterKey = cursor.getInt(INDEX_RANK) == 0; boolean isMasterKey = subKey.rank() == 0;
if (isMasterKey) { if (isMasterKey) {
vKeyId.setTypeface(null, Typeface.BOLD); vKeyId.setTypeface(null, Typeface.BOLD);
} else { } else {
@@ -226,22 +169,21 @@ public class SubkeysAdapter extends CursorAdapter {
} }
// Set icons according to properties // Set icons according to properties
vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE); vCertifyIcon.setVisibility(subKey.can_certify() ? View.VISIBLE : View.GONE);
vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE); vEncryptIcon.setVisibility(subKey.can_encrypt() ? View.VISIBLE : View.GONE);
vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE); vSignIcon.setVisibility(subKey.can_sign() ? View.VISIBLE : View.GONE);
vAuthenticateIcon.setVisibility(cursor.getInt(INDEX_CAN_AUTHENTICATE) != 0 ? View.VISIBLE : View.GONE); vAuthenticateIcon.setVisibility(subKey.can_authenticate() ? View.VISIBLE : View.GONE);
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; boolean isRevoked = subKey.is_revoked();
boolean isSecure = cursor.getInt(INDEX_IS_SECURE) > 0;
Date expiryDate = null; Date expiryDate = null;
if (!cursor.isNull(INDEX_EXPIRY)) { if (subKey.expires()) {
expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000); expiryDate = new Date(subKey.expiry() * 1000);
} }
// for edit key // for edit key
if (mSkpBuilder != null) { if (mSkpBuilder != null) {
boolean revokeThisSubkey = (mSkpBuilder.getMutableRevokeSubKeys().contains(keyId)); boolean revokeThisSubkey = (mSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id()));
if (revokeThisSubkey) { if (revokeThisSubkey) {
if (!isRevoked) { if (!isRevoked) {
@@ -249,7 +191,7 @@ public class SubkeysAdapter extends CursorAdapter {
} }
} }
SaveKeyringParcel.SubkeyChange subkeyChange = mSkpBuilder.getSubkeyChange(keyId); SaveKeyringParcel.SubkeyChange subkeyChange = mSkpBuilder.getSubkeyChange(subKey.key_id());
if (subkeyChange != null) { if (subkeyChange != null) {
if (subkeyChange.getExpiry() == null || subkeyChange.getExpiry() == 0L) { if (subkeyChange.getExpiry() == null || subkeyChange.getExpiry() == 0L) {
expiryDate = null; expiryDate = null;
@@ -280,37 +222,37 @@ public class SubkeysAdapter extends CursorAdapter {
} }
// if key is expired or revoked... // if key is expired or revoked...
boolean isInvalid = isRevoked || isExpired || !isSecure; boolean isInvalid = isRevoked || isExpired || !subKey.is_secure();
if (isInvalid) { if (isInvalid) {
vStatus.setVisibility(View.VISIBLE); vStatus.setVisibility(View.VISIBLE);
vCertifyIcon.setColorFilter( vCertifyIcon.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
vSignIcon.setColorFilter( vSignIcon.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
vEncryptIcon.setColorFilter( vEncryptIcon.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
vAuthenticateIcon.setColorFilter( vAuthenticateIcon.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
if (isRevoked) { if (isRevoked) {
vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp);
vStatus.setColorFilter( vStatus.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
} else if (isExpired) { } else if (isExpired) {
vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp);
vStatus.setColorFilter( vStatus.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
} else if (!isSecure) { } else if (!subKey.is_secure()) {
vStatus.setImageResource(R.drawable.status_signature_invalid_cutout_24dp); vStatus.setImageResource(R.drawable.status_signature_invalid_cutout_24dp);
vStatus.setColorFilter( vStatus.setColorFilter(
mContext.getResources().getColor(R.color.key_flag_gray), context.getResources().getColor(R.color.key_flag_gray),
PorterDuff.Mode.SRC_IN); PorterDuff.Mode.SRC_IN);
} }
} else { } else {
@@ -328,36 +270,20 @@ public class SubkeysAdapter extends CursorAdapter {
vKeyId.setEnabled(!isInvalid); vKeyId.setEnabled(!isInvalid);
vKeyDetails.setEnabled(!isInvalid); vKeyDetails.setEnabled(!isInvalid);
vKeyExpiry.setEnabled(!isInvalid); vKeyExpiry.setEnabled(!isInvalid);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.view_key_adv_subkey_item, null);
if (mDefaultTextColor == null) {
TextView keyId = view.findViewById(R.id.subkey_item_key_id);
mDefaultTextColor = keyId.getTextColors();
}
return view; return view;
} }
// Disable selection of items, http://stackoverflow.com/a/4075045 // Disable selection of items, http://stackoverflow.com/a/4075045
@Override @Override
public boolean areAllItemsEnabled() { public boolean areAllItemsEnabled() {
if (mSkpBuilder == null) { return mSkpBuilder != null && super.areAllItemsEnabled();
return false;
} else {
return super.areAllItemsEnabled();
}
} }
// Disable selection of items, http://stackoverflow.com/a/4075045 // Disable selection of items, http://stackoverflow.com/a/4075045
@Override @Override
public boolean isEnabled(int position) { public boolean isEnabled(int position) {
if (mSkpBuilder == null) { return mSkpBuilder != null && super.isEnabled(position);
return false;
} else {
return super.isEnabled(position);
}
} }
/** Set this adapter into edit mode. This mode displays additional info for /** Set this adapter into edit mode. This mode displays additional info for
@@ -372,6 +298,7 @@ public class SubkeysAdapter extends CursorAdapter {
*/ */
public void setEditMode(@Nullable SaveKeyringParcel.Builder builder) { public void setEditMode(@Nullable SaveKeyringParcel.Builder builder) {
mSkpBuilder = builder; mSkpBuilder = builder;
notifyDataSetChanged();
} }
} }

View File

@@ -24,97 +24,59 @@ import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.model.SubKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeyRepository;
import timber.log.Timber;
public class SubkeyStatusDao { public class SubkeyStatusDao {
public static final String[] PROJECTION = new String[] { private final KeyRepository keyRepository;
Keys.KEY_ID,
Keys.CREATION,
Keys.CAN_CERTIFY,
Keys.CAN_SIGN,
Keys.CAN_ENCRYPT,
Keys.HAS_SECRET,
Keys.EXPIRY,
Keys.IS_REVOKED,
Keys.ALGORITHM,
Keys.KEY_SIZE,
Keys.KEY_CURVE_OID
};
private static final int INDEX_KEY_ID = 0;
private static final int INDEX_CREATION = 1;
private static final int INDEX_CAN_CERTIFY = 2;
private static final int INDEX_CAN_SIGN = 3;
private static final int INDEX_CAN_ENCRYPT = 4;
private static final int INDEX_HAS_SECRET = 5;
private static final int INDEX_EXPIRY = 6;
private static final int INDEX_IS_REVOKED = 7;
private static final int INDEX_ALGORITHM = 8;
private static final int INDEX_KEY_SIZE = 9;
private static final int INDEX_KEY_CURVE_OID = 10;
private final ContentResolver contentResolver;
public static SubkeyStatusDao getInstance(Context context) { public static SubkeyStatusDao getInstance(Context context) {
ContentResolver contentResolver = context.getContentResolver(); KeyRepository keyRepository = KeyRepository.create(context);
return new SubkeyStatusDao(contentResolver); return new SubkeyStatusDao(keyRepository);
} }
private SubkeyStatusDao(ContentResolver contentResolver) { private SubkeyStatusDao(KeyRepository keyRepository) {
this.contentResolver = contentResolver; this.keyRepository = keyRepository;
} }
KeySubkeyStatus getSubkeyStatus(long masterKeyId, Comparator<SubKeyItem> comparator) { KeySubkeyStatus getSubkeyStatus(long masterKeyId, Comparator<SubKeyItem> comparator) {
Cursor cursor = contentResolver.query(Keys.buildKeysUri(masterKeyId), PROJECTION, null, null, null); SubKeyItem keyCertify = null;
if (cursor == null) { ArrayList<SubKeyItem> keysSign = new ArrayList<>();
Timber.e("Error loading key items!"); ArrayList<SubKeyItem> keysEncrypt = new ArrayList<>();
for (SubKey subKey : keyRepository.getSubKeysByMasterKeyId(masterKeyId)) {
SubKeyItem ski = new SubKeyItem(masterKeyId, subKey);
if (ski.mKeyId == masterKeyId) {
keyCertify = ski;
}
if (ski.mCanSign) {
keysSign.add(ski);
}
if (ski.mCanEncrypt) {
keysEncrypt.add(ski);
}
}
if (keyCertify == null) {
if (!keysSign.isEmpty() || !keysEncrypt.isEmpty()) {
throw new IllegalStateException("Certification key can't be missing for a key that hasn't been deleted!");
}
return null; return null;
} }
try { Collections.sort(keysSign, comparator);
SubKeyItem keyCertify = null; Collections.sort(keysEncrypt, comparator);
ArrayList<SubKeyItem> keysSign = new ArrayList<>();
ArrayList<SubKeyItem> keysEncrypt = new ArrayList<>();
while (cursor.moveToNext()) {
SubKeyItem ski = new SubKeyItem(masterKeyId, cursor);
if (ski.mKeyId == masterKeyId) { return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt);
keyCertify = ski;
}
if (ski.mCanSign) {
keysSign.add(ski);
}
if (ski.mCanEncrypt) {
keysEncrypt.add(ski);
}
}
if (keyCertify == null) {
if (!keysSign.isEmpty() || !keysEncrypt.isEmpty()) {
throw new IllegalStateException("Certification key can't be missing for a key that hasn't been deleted!");
}
return null;
}
Collections.sort(keysSign, comparator);
Collections.sort(keysEncrypt, comparator);
return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt);
} finally {
cursor.close();
}
} }
public static class KeySubkeyStatus { public static class KeySubkeyStatus {
@@ -131,7 +93,6 @@ public class SubkeyStatusDao {
} }
public static class SubKeyItem { public static class SubKeyItem {
final int mPosition;
final long mKeyId; final long mKeyId;
final Date mCreation; final Date mCreation;
public final SecretKeyType mSecretKeyType; public final SecretKeyType mSecretKeyType;
@@ -140,25 +101,23 @@ public class SubkeyStatusDao {
final boolean mCanCertify, mCanSign, mCanEncrypt; final boolean mCanCertify, mCanSign, mCanEncrypt;
public final KeySecurityProblem mSecurityProblem; public final KeySecurityProblem mSecurityProblem;
SubKeyItem(long masterKeyId, Cursor cursor) { SubKeyItem(long masterKeyId, SubKey subKey) {
mPosition = cursor.getPosition(); mKeyId = subKey.key_id();
mCreation = new Date(subKey.creation() * 1000);
mKeyId = cursor.getLong(INDEX_KEY_ID); mSecretKeyType = subKey.has_secret();
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mSecretKeyType = SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET)); mIsRevoked = subKey.is_revoked();
mExpiry = subKey.expiry() == null ? null : new Date(subKey.expiry() * 1000);
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
mExpiry = cursor.isNull(INDEX_EXPIRY) ? null : new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
mIsExpired = mExpiry != null && mExpiry.before(new Date()); mIsExpired = mExpiry != null && mExpiry.before(new Date());
mCanCertify = cursor.getInt(INDEX_CAN_CERTIFY) > 0; mCanCertify = subKey.can_certify();
mCanSign = cursor.getInt(INDEX_CAN_SIGN) > 0; mCanSign = subKey.can_sign();
mCanEncrypt = cursor.getInt(INDEX_CAN_ENCRYPT) > 0; mCanEncrypt = subKey.can_encrypt();
int algorithm = cursor.getInt(INDEX_ALGORITHM); int algorithm = subKey.algorithm();
Integer bitStrength = cursor.isNull(INDEX_KEY_SIZE) ? null : cursor.getInt(INDEX_KEY_SIZE); Integer bitStrength = subKey.key_size();
String curveOid = cursor.getString(INDEX_KEY_CURVE_OID); String curveOid = subKey.key_curve_oid();
mSecurityProblem = PgpSecurityConstants.getKeySecurityProblem( mSecurityProblem = PgpSecurityConstants.getKeySecurityProblem(
masterKeyId, mKeyId, algorithm, bitStrength, curveOid); masterKeyId, mKeyId, algorithm, bitStrength, curveOid);

View File

@@ -1,19 +1,20 @@
import java.lang.Boolean; import java.lang.Boolean;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
CREATE TABLE IF NOT EXISTS keys ( CREATE TABLE IF NOT EXISTS keys (
master_key_id INTEGER NOT NULL, master_key_id INTEGER NOT NULL,
rank INTEGER NOT NULL, rank INTEGER NOT NULL,
key_id INTEGER NOT NULL, key_id INTEGER NOT NULL,
key_size INTEGER, key_size INTEGER AS Integer,
key_curve_oid TEXT, key_curve_oid TEXT,
algorithm INTEGER NOT NULL, algorithm INTEGER AS Integer NOT NULL,
fingerprint BLOB NOT NULL, fingerprint BLOB NOT NULL,
can_certify INTEGER AS Boolean NOT NULL, can_certify INTEGER AS Boolean NOT NULL,
can_sign INTEGER AS Boolean NOT NULL, can_sign INTEGER AS Boolean NOT NULL,
can_encrypt INTEGER AS Boolean NOT NULL, can_encrypt INTEGER AS Boolean NOT NULL,
can_authenticate INTEGER AS Boolean NOT NULL, can_authenticate INTEGER AS Boolean NOT NULL,
is_revoked INTEGER AS Boolean NOT NULL, is_revoked INTEGER AS Boolean NOT NULL,
has_secret INTEGER AS Boolean NOT NULL, has_secret INTEGER AS SecretKeyType NOT NULL,
is_secure INTEGER AS Boolean NOT NULL, is_secure INTEGER AS Boolean NOT NULL,
creation INTEGER NOT NULL, creation INTEGER NOT NULL,
expiry INTEGER, expiry INTEGER,
@@ -34,4 +35,15 @@ SELECT keys.master_key_id, MIN(user_packets.rank), user_packets.name, user_packe
LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id )
WHERE keys.rank = 0 WHERE keys.rank = 0
GROUP BY keys.master_key_id GROUP BY keys.master_key_id
ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC; ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC;
selectSubkeysByMasterKeyId:
SELECT *
FROM keys
WHERE master_key_id = ?
ORDER BY rank ASC;
selectSecretKeyType:
SELECT has_secret
FROM keys
WHERE key_id = ?;