272 lines
11 KiB
Java
272 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.provider;
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
|
|
import com.squareup.sqldelight.SqlDelightQuery;
|
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
|
import org.sufficientlysecure.keychain.model.Certification;
|
|
import org.sufficientlysecure.keychain.model.KeyRingPublic;
|
|
import org.sufficientlysecure.keychain.model.SubKey;
|
|
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
|
import org.sufficientlysecure.keychain.model.UserPacket;
|
|
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
|
import timber.log.Timber;
|
|
|
|
|
|
public class KeyRepository extends AbstractDao {
|
|
final ContentResolver contentResolver;
|
|
final LocalPublicKeyStorage mLocalPublicKeyStorage;
|
|
final LocalSecretKeyStorage localSecretKeyStorage;
|
|
|
|
OperationLog mLog;
|
|
int mIndent;
|
|
|
|
public static KeyRepository create(Context context) {
|
|
ContentResolver contentResolver = context.getContentResolver();
|
|
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
|
|
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
|
|
KeychainDatabase database = KeychainDatabase.getInstance(context);
|
|
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
|
|
|
return new KeyRepository(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage);
|
|
}
|
|
|
|
private KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
|
|
DatabaseNotifyManager databaseNotifyManager,
|
|
LocalPublicKeyStorage localPublicKeyStorage,
|
|
LocalSecretKeyStorage localSecretKeyStorage) {
|
|
this(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0);
|
|
}
|
|
|
|
KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
|
|
DatabaseNotifyManager databaseNotifyManager,
|
|
LocalPublicKeyStorage localPublicKeyStorage,
|
|
LocalSecretKeyStorage localSecretKeyStorage,
|
|
OperationLog log, int indent) {
|
|
super(database, databaseNotifyManager);
|
|
this.contentResolver = contentResolver;
|
|
mLocalPublicKeyStorage = localPublicKeyStorage;
|
|
this.localSecretKeyStorage = localSecretKeyStorage;
|
|
mIndent = indent;
|
|
mLog = log;
|
|
}
|
|
|
|
public OperationLog getLog() {
|
|
return mLog;
|
|
}
|
|
|
|
public void log(LogType type) {
|
|
if (mLog != null) {
|
|
mLog.add(type, mIndent);
|
|
}
|
|
}
|
|
|
|
public void log(LogType type, Object... parameters) {
|
|
if (mLog != null) {
|
|
mLog.add(type, mIndent, parameters);
|
|
}
|
|
}
|
|
|
|
public void clearLog() {
|
|
mLog = new OperationLog();
|
|
}
|
|
|
|
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException {
|
|
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
|
|
if (unifiedKeyInfo == null) {
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
|
|
return new CanonicalizedPublicKeyRing(publicKeyData, unifiedKeyInfo.verified());
|
|
}
|
|
|
|
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) throws NotFoundException {
|
|
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
|
|
if (unifiedKeyInfo == null || !unifiedKeyInfo.has_any_secret()) {
|
|
throw new NotFoundException();
|
|
}
|
|
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
|
|
if (secretKeyData == null) {
|
|
throw new IllegalStateException("Missing expected secret key data!");
|
|
}
|
|
return new CanonicalizedSecretKeyRing(secretKeyData, unifiedKeyInfo.verified());
|
|
}
|
|
|
|
public List<Long> getAllMasterKeyIds() {
|
|
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
|
|
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
|
|
}
|
|
|
|
public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
|
|
long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds);
|
|
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
|
|
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
|
|
}
|
|
|
|
public Long getMasterKeyIdBySubkeyId(long subKeyId) {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId);
|
|
return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map);
|
|
}
|
|
|
|
public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId);
|
|
return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
|
}
|
|
|
|
public List<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds);
|
|
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
|
}
|
|
|
|
public List<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%');
|
|
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
|
}
|
|
|
|
public List<UnifiedKeyInfo> getAllUnifiedKeyInfo() {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
|
|
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
|
}
|
|
|
|
public List<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret();
|
|
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
|
}
|
|
|
|
public List<UserId> getUserIds(long... masterKeyIds) {
|
|
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds);
|
|
return mapAllRows(query, UserPacket.USER_ID_MAPPER::map);
|
|
}
|
|
|
|
public List<String> getConfirmedUserIds(long masterKeyId) {
|
|
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(
|
|
Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET);
|
|
return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id());
|
|
}
|
|
|
|
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
|
|
return mapAllRows(query, SubKey.SUBKEY_MAPPER::map);
|
|
}
|
|
|
|
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId);
|
|
return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map);
|
|
}
|
|
|
|
public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId);
|
|
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map);
|
|
}
|
|
|
|
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException {
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
|
|
|
aos.write(data);
|
|
aos.close();
|
|
|
|
return bos.toByteArray();
|
|
}
|
|
|
|
public String getPublicKeyRingAsArmoredString(long masterKeyId) throws NotFoundException, IOException {
|
|
byte[] data = loadPublicKeyRingData(masterKeyId);
|
|
byte[] armoredData = getKeyRingAsArmoredData(data);
|
|
return new String(armoredData);
|
|
}
|
|
|
|
public byte[] getSecretKeyRingAsArmoredData(long masterKeyId) throws NotFoundException, IOException {
|
|
byte[] data = loadSecretKeyRingData(masterKeyId);
|
|
return getKeyRingAsArmoredData(data);
|
|
}
|
|
|
|
public ContentResolver getContentResolver() {
|
|
return contentResolver;
|
|
}
|
|
|
|
public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException {
|
|
SqlDelightQuery query = KeyRingPublic.FACTORY.selectByMasterKeyId(masterKeyId);
|
|
try (Cursor cursor = getReadableDb().query(query)) {
|
|
if (cursor.moveToFirst()) {
|
|
KeyRingPublic keyRingPublic = KeyRingPublic.MAPPER.map(cursor);
|
|
byte[] keyRingData = keyRingPublic.key_ring_data();
|
|
if (keyRingData == null) {
|
|
keyRingData = mLocalPublicKeyStorage.readPublicKey(masterKeyId);
|
|
}
|
|
return keyRingData;
|
|
}
|
|
} catch (IOException e) {
|
|
Timber.e(e, "Error reading public key from storage!");
|
|
}
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException {
|
|
try {
|
|
return localSecretKeyStorage.readSecretKey(masterKeyId);
|
|
} catch (IOException e) {
|
|
Timber.e(e, "Error reading secret key from storage!");
|
|
throw new NotFoundException();
|
|
}
|
|
}
|
|
|
|
public long getSecretSignId(long masterKeyId) throws NotFoundException {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId);
|
|
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map);
|
|
}
|
|
|
|
public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException {
|
|
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId);
|
|
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map);
|
|
}
|
|
|
|
public static class NotFoundException extends Exception {
|
|
public NotFoundException() {
|
|
}
|
|
|
|
public NotFoundException(String name) {
|
|
super(name);
|
|
}
|
|
}
|
|
|
|
private long[] getLongListAsArray(List<Long> longList) {
|
|
long[] longs = new long[longList.size()];
|
|
int i = 0;
|
|
for (Long aLong : longList) {
|
|
longs[i++] = aLong;
|
|
}
|
|
return longs;
|
|
}
|
|
}
|