trustedCerts = new LongSparseArray<>();
@Override
public int compareTo(@NonNull UserPacketItem o) {
// revoked keys always come last!
//noinspection DoubleNegation
if ((selfRevocation != null) != (o.selfRevocation != null)) {
return selfRevocation != null ? 1 : -1;
}
// if one is a user id, but the other isn't, the user id always comes first.
// we compare for null values here, so != is the correct operator!
// noinspection NumberEquality
if (type != o.type) {
return type == null ? -1 : 1;
}
// if one is *trusted* but the other isn't, that one comes first
// this overrides the primary attribute, even!
if ((trustedCerts.size() == 0) != (o.trustedCerts.size() == 0)) {
return trustedCerts.size() > o.trustedCerts.size() ? -1 : 1;
}
// if one key is primary but the other isn't, the primary one always comes first
if (isPrimary != o.isPrimary) {
return isPrimary ? -1 : 1;
}
return 0;
}
}
/**
* Saves an UncachedKeyRing of the secret variant into the db.
* This method will fail if no corresponding public keyring is in the database!
*/
private int saveCanonicalizedSecretKeyRing(CanonicalizedSecretKeyRing keyRing) {
long masterKeyId = keyRing.getMasterKeyId();
log(LogType.MSG_IS, KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
mIndent += 1;
try {
// IF this is successful, it's a secret key
int result = SaveKeyringResult.SAVED_SECRET;
// save secret keyring
try {
writeSecretKeyRing(keyRing, masterKeyId);
} catch (IOException e) {
Timber.e(e, "Failed to encode key!");
log(LogType.MSG_IS_ERROR_IO_EXC);
return SaveKeyringResult.RESULT_ERROR;
}
{
UpdateHasSecretByMasterKeyId resetStatement =
SubKey.createUpdateHasSecretByMasterKeyIdStatement(getWritableDb());
resetStatement.bind(masterKeyId, SecretKeyType.GNU_DUMMY);
resetStatement.executeUpdateDelete();
UpdateHasSecretByKeyId updateStatement = SubKey.createUpdateHasSecretByKeyId(getWritableDb());
// then, mark exactly the keys we have available
log(LogType.MSG_IS_IMPORTING_SUBKEYS);
mIndent += 1;
for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) {
long id = sub.getKeyId();
SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive();
updateStatement.bind(id, mode);
int upd = updateStatement.executeUpdateDelete();
if (upd == 1) {
switch (mode) {
case PASSPHRASE:
log(LogType.MSG_IS_SUBKEY_OK, KeyFormattingUtils.convertKeyIdToHex(id));
break;
case PASSPHRASE_EMPTY:
log(LogType.MSG_IS_SUBKEY_EMPTY, KeyFormattingUtils.convertKeyIdToHex(id));
break;
case GNU_DUMMY:
log(LogType.MSG_IS_SUBKEY_STRIPPED, KeyFormattingUtils.convertKeyIdToHex(id));
break;
case DIVERT_TO_CARD:
log(LogType.MSG_IS_SUBKEY_DIVERT, KeyFormattingUtils.convertKeyIdToHex(id));
break;
}
} else {
log(LogType.MSG_IS_SUBKEY_NONEXISTENT, KeyFormattingUtils.convertKeyIdToHex(id));
}
}
mIndent -= 1;
// this implicitly leaves all keys which were not in the secret key ring
// with has_secret = 1
}
databaseNotifyManager.notifyKeyChange(masterKeyId);
log(LogType.MSG_IS_SUCCESS);
return result;
} finally {
mIndent -= 1;
}
}
/**
* Save a public keyring into the database.
*
* This is a high level method, which takes care of merging all new information into the old and
* keep public and secret keyrings in sync.
*
* If you want to merge keys in-memory only and not save in database set skipSave=true.
*/
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing,
byte[] expectedFingerprint,
ArrayList canKeyRings,
boolean forceRefresh,
boolean skipSave) {
try {
long masterKeyId = publicRing.getMasterKeyId();
log(LogType.MSG_IP, KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
mIndent += 1;
if (publicRing.isSecret()) {
log(LogType.MSG_IP_BAD_TYPE_SECRET);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
CanonicalizedPublicKeyRing canPublicRing;
boolean alreadyExists = false;
// If there is an old keyring, merge it
try {
UncachedKeyRing oldPublicRing = UncachedKeyRing.decodeFromData(loadPublicKeyRingData(masterKeyId));
alreadyExists = true;
// Merge data from new public ring into the old one
log(LogType.MSG_IP_MERGE_PUBLIC);
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
// If this is null, there is an error in the log so we can just return
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
// Canonicalize this keyring, to assert a number of assumptions made about it.
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
if (canKeyRings != null) canKeyRings.add(canPublicRing);
// Early breakout if nothing changed
if (!forceRefresh && Arrays.hashCode(publicRing.getEncoded())
== Arrays.hashCode(oldPublicRing.getEncoded())) {
log(LogType.MSG_IP_SUCCESS_IDENTICAL);
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, canPublicRing);
}
} catch (PgpGeneralException | NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring.
// Canonicalize this keyring, to assert a number of assumptions made about it.
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
if (canKeyRings != null) canKeyRings.add(canPublicRing);
}
// If there is a secret key, merge new data (if any) and save the key for later
CanonicalizedSecretKeyRing canSecretRing;
try {
UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId())
.getUncachedKeyRing();
// Merge data from new public ring into secret one
log(LogType.MSG_IP_MERGE_SECRET);
secretRing = secretRing.merge(publicRing, mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
// This has always been a secret key ring, this is a safe cast
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
} catch (NotFoundException e) {
// No secret key available (this is what happens most of the time)
canSecretRing = null;
}
// If we have an expected fingerprint, make sure it matches
if (expectedFingerprint != null) {
if (!canPublicRing.containsBoundSubkey(expectedFingerprint)) {
log(LogType.MSG_IP_FINGERPRINT_ERROR);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} else {
log(LogType.MSG_IP_FINGERPRINT_OK);
}
}
int result;
if (skipSave) {
// skip save method, set fixed result
result = SaveKeyringResult.SAVED_PUBLIC
| (alreadyExists ? SaveKeyringResult.UPDATED : 0);
} else {
result = saveCanonicalizedPublicKeyRing(canPublicRing, canSecretRing != null);
}
// Save the saved keyring (if any)
if (canSecretRing != null) {
int secretResult;
if (skipSave) {
// skip save method, set fixed result
secretResult = SaveKeyringResult.SAVED_SECRET;
} else {
secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
}
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
result |= SaveKeyringResult.SAVED_SECRET;
}
}
return new SaveKeyringResult(result, mLog, canPublicRing);
} catch (IOException e) {
log(LogType.MSG_IP_ERROR_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} finally {
mIndent -= 1;
}
}
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, byte[] expectedFingerprint) {
return savePublicKeyRing(publicRing, expectedFingerprint, null, false, false);
}
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, byte[] expectedFingerprint,
boolean forceRefresh) {
return savePublicKeyRing(publicRing, expectedFingerprint, null, forceRefresh, false);
}
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
return savePublicKeyRing(keyRing, null, false);
}
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing, boolean forceRefresh) {
return savePublicKeyRing(keyRing, null, forceRefresh);
}
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing,
ArrayList canKeyRings,
boolean skipSave) {
try {
long masterKeyId = secretRing.getMasterKeyId();
log(LogType.MSG_IS, KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
mIndent += 1;
if (!secretRing.isSecret()) {
log(LogType.MSG_IS_BAD_TYPE_PUBLIC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
CanonicalizedSecretKeyRing canSecretRing;
boolean alreadyExists = false;
// If there is an old secret key, merge it.
try {
UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing();
alreadyExists = true;
// Merge data from new secret ring into old one
log(LogType.MSG_IS_MERGE_SECRET);
secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);
// If this is null, there is an error in the log so we can just return
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
// Canonicalize this keyring, to assert a number of assumptions made about it.
// This is a safe cast, because we made sure this is a secret ring above
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
if (canKeyRings != null) canKeyRings.add(canSecretRing);
// Early breakout if nothing changed
if (Arrays.hashCode(secretRing.getEncoded())
== Arrays.hashCode(oldSecretRing.getEncoded())) {
log(LogType.MSG_IS_SUCCESS_IDENTICAL,
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
return new SaveKeyringResult(SaveKeyringResult.UPDATED, mLog, null);
}
} catch (NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring
// Canonicalize this keyring, to assert a number of assumptions made about it.
// This is a safe cast, because we made sure this is a secret ring above
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
if (canSecretRing == null) {
// Special case: If keyring canonicalization failed, try again after adding
// all self-certificates from the public key.
try {
log(LogType.MSG_IS_MERGE_SPECIAL);
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
secretRing = secretRing.merge(oldPublicRing, mLog, mIndent);
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
} catch (NotFoundException e2) {
// nothing, this is handled right in the next line
}
if (canSecretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
}
if (canKeyRings != null) canKeyRings.add(canSecretRing);
}
// Merge new data into public keyring as well, if there is any
UncachedKeyRing publicRing;
try {
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
// Merge data from new secret ring into public one
log(LogType.MSG_IS_MERGE_PUBLIC);
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
} catch (NotFoundException e) {
log(LogType.MSG_IS_PUBRING_GENERATE);
publicRing = secretRing.extractPublicKeyRing();
}
CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog,
mIndent);
if (canPublicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
int publicResult;
if (skipSave) {
// skip save method, set fixed result
publicResult = SaveKeyringResult.SAVED_PUBLIC;
} else {
publicResult = saveCanonicalizedPublicKeyRing(canPublicRing, true);
}
if ((publicResult & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
}
int result;
if (skipSave) {
// skip save method, set fixed result
result = SaveKeyringResult.SAVED_SECRET
| (alreadyExists ? SaveKeyringResult.UPDATED : 0);
} else {
result = saveCanonicalizedSecretKeyRing(canSecretRing);
}
return new SaveKeyringResult(result, mLog, canSecretRing);
} catch (IOException e) {
log(LogType.MSG_IS_ERROR_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} finally {
mIndent -= 1;
}
}
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing) {
return saveSecretKeyRing(secretRing, null, false);
}
@NonNull
public UpdateTrustResult updateTrustDb(List signerMasterKeyIds, Progressable progress) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_TRUST, 0);
Preferences preferences = Preferences.getPreferences(context);
boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized();
List masterKeyIds;
if (!isTrustDbInitialized) {
log.add(LogType.MSG_TRUST_INITIALIZE, 1);
masterKeyIds = getAllMasterKeyIds();
} else {
masterKeyIds = getMasterKeyIdsBySigner(signerMasterKeyIds);
}
int totalKeys = masterKeyIds.size();
int processedKeys = 0;
if (totalKeys == 0) {
log.add(LogType.MSG_TRUST_COUNT_NONE, 1);
} else {
progress.setProgress(R.string.progress_update_trust, 0, totalKeys);
log.add(LogType.MSG_TRUST_COUNT, 1, totalKeys);
}
for (long masterKeyId : masterKeyIds) {
try {
log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId));
byte[] pubKeyData = loadPublicKeyRingData(masterKeyId);
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData);
clearLog();
SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true);
log.add(result, 1);
progress.setProgress(processedKeys++, totalKeys);
} catch (NotFoundException | PgpGeneralException | IOException e) {
Timber.e(e, "Error updating trust database");
return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log);
}
}
preferences.setKeySignaturesTableInitialized();
log.add(LogType.MSG_TRUST_OK, 1);
return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log);
}
private BatchOp buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) {
try {
Certification certification = Certification.create(masterKeyId, rank, cert.getKeyId(),
cert.getSignatureType(), verificationStatus, cert.getCreationTime(), cert.getEncoded());
return DatabaseBatchInteractor.createInsertCertification(certification);
} catch (IOException e) {
throw new AssertionError(e);
}
}
}