Change Autocrypt logic to more closely match the spec

This commit is contained in:
Vincent Breitmoser
2018-01-15 18:16:48 +01:00
parent 1c3f9fd27f
commit ebe262015a
5 changed files with 118 additions and 86 deletions

View File

@@ -24,12 +24,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
public class SaveKeyringResult extends OperationResult { public class SaveKeyringResult extends OperationResult {
public final long mRingMasterKeyId; public final Long savedMasterKeyId;
public SaveKeyringResult(int result, OperationLog log, public SaveKeyringResult(int result, OperationLog log,
CanonicalizedKeyRing ring) { CanonicalizedKeyRing ring) {
super(result, log); super(result, log);
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; savedMasterKeyId = ring != null ? ring.getMasterKeyId() : null;
} }
// Some old key was updated // Some old key was updated
@@ -46,13 +46,13 @@ public class SaveKeyringResult extends OperationResult {
public SaveKeyringResult(Parcel source) { public SaveKeyringResult(Parcel source) {
super(source); super(source);
mRingMasterKeyId = source.readLong(); savedMasterKeyId = source.readLong();
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeLong(mRingMasterKeyId); dest.writeLong(savedMasterKeyId);
} }
public static Creator<SaveKeyringResult> CREATOR = new Creator<SaveKeyringResult>() { public static Creator<SaveKeyringResult> CREATOR = new Creator<SaveKeyringResult>() {

View File

@@ -153,6 +153,23 @@ public class AutocryptPeerDataAccessObject {
return null; return null;
} }
public Date getLastSeenGossip(String autocryptId) {
Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId),
null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long lastUpdated = cursor.getColumnIndex(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY);
return new Date(lastUpdated);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public void updateLastSeen(String autocryptId, Date date) { public void updateLastSeen(String autocryptId, Date date) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime()); cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime());

View File

@@ -1114,42 +1114,25 @@ public class KeychainProvider extends ContentProvider {
break; break;
} }
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
ContentValues actualValues = new ContentValues();
String packageName = uri.getPathSegments().get(2); String packageName = uri.getPathSegments().get(2);
actualValues.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); String identifier = uri.getLastPathSegment();
actualValues.put(ApiAutocryptPeer.IDENTIFIER, uri.getLastPathSegment()); values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
values.put(ApiAutocryptPeer.IDENTIFIER, identifier);
Long newLastSeen = values.getAsLong(ApiAutocryptPeer.LAST_SEEN); int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values,
if (newLastSeen != null) { ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?",
actualValues.put(ApiAutocryptPeer.LAST_SEEN, newLastSeen); new String[] { packageName, identifier });
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues); if (updated == 0) {
break; db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values);
} }
Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID); Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID);
if (masterKeyId != null) { if (masterKeyId != null) {
Long lastSeenKey = values.getAsLong(ApiAutocryptPeer.LAST_SEEN_KEY);
Long preferEncryptState = values.getAsLong(ApiAutocryptPeer.IS_MUTUAL);
actualValues.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId);
actualValues.put(ApiAutocryptPeer.LAST_SEEN_KEY, lastSeenKey);
actualValues.put(ApiAutocryptPeer.IS_MUTUAL, preferEncryptState);
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues);
contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null); contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null);
break;
} }
Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID);
if (gossipMasterKeyId != null) { if (gossipMasterKeyId != null && !gossipMasterKeyId.equals(masterKeyId)) {
Long gossipLastSeenKey = values.getAsLong(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY);
actualValues.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, gossipMasterKeyId);
actualValues.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, gossipLastSeenKey);
db.replace(Tables.API_AUTOCRYPT_PEERS, null, actualValues);
contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null); contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null);
break;
} }
break; break;

View File

@@ -5,15 +5,17 @@ import java.io.IOException;
import java.util.Date; import java.util.Date;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable;
import org.openintents.openpgp.AutocryptPeerUpdate; import org.openintents.openpgp.AutocryptPeerUpdate;
import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt; import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
import org.sufficientlysecure.keychain.util.Log; import timber.log.Timber;
class AutocryptInteractor { class AutocryptInteractor {
@@ -33,74 +35,100 @@ class AutocryptInteractor {
this.keyWritableRepository = keyWritableRepository; this.keyWritableRepository = keyWritableRepository;
} }
void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
throws PgpGeneralException, IOException {
if (autocryptPeerUpdate == null) {
return;
}
Long newMasterKeyId;
if (autocryptPeerUpdate.hasKeyData()) {
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData());
if (uncachedKeyRing.isSecret()) {
Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring");
return;
}
// this will merge if the key already exists - no worries!
keyWritableRepository.savePublicKeyRing(uncachedKeyRing);
newMasterKeyId = uncachedKeyRing.getMasterKeyId();
} else {
newMasterKeyId = null;
}
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate);
if (newMasterKeyId == null) { // 1. If the messages effective date is older than the peers[from-addr].autocrypt_timestamp value, then no changes are required, and the update process terminates.
Date lastSeenAutocrypt = autocryptPeerDao.getLastSeenKey(autocryptPeerId);
if (lastSeenAutocrypt != null && lastSeenAutocrypt.after(effectiveDate)) {
return; return;
} }
Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); // 2. If the messages effective date is more recent than peers[from-addr].last_seen then set peers[from-addr].last_seen to the messages effective date.
if (lastSeenKey == null || effectiveDate.after(lastSeenKey)) { Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId);
boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL; if (lastSeen == null || lastSeen.after(effectiveDate)) {
autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate);
} }
// 3. If the Autocrypt header is unavailable, no further changes are required and the update process terminates.
if (!autocryptPeerUpdate.hasKeyData()) {
return;
}
SaveKeyringResult saveKeyringResult = parseAndImportAutocryptKeyData(autocryptPeerUpdate);
if (saveKeyringResult == null) {
return;
}
// 4. Set peers[from-addr].autocrypt_timestamp to the messages effective date.
// 5. Set peers[from-addr].public_key to the corresponding keydata value of the Autocrypt header.
Long newMasterKeyId = saveKeyringResult.savedMasterKeyId;
// 6. Set peers[from-addr].prefer_encrypt to the corresponding prefer-encrypt value of the Autocrypt header.
boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL;
autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual);
} }
void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) {
void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate)
throws PgpGeneralException, IOException {
if (autocryptPeerUpdate == null) {
return;
}
Long newMasterKeyId;
if (autocryptPeerUpdate.hasKeyData()) {
UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData());
if (uncachedKeyRing.isSecret()) {
Log.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring");
return;
}
// this will merge if the key already exists - no worries!
keyWritableRepository.savePublicKeyRing(uncachedKeyRing);
newMasterKeyId = uncachedKeyRing.getMasterKeyId();
} else {
newMasterKeyId = null;
}
Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId);
Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); Date effectiveDate = autocryptPeerUpdate.getEffectiveDate();
if (newMasterKeyId == null) {
// 1. If gossip-addr does not match any recipient in the mails To or Cc header, the update process terminates (i.e., header is ignored).
// -> This should be taken care of in the mail client that sends us this data!
// 2. If peers[gossip-addr].gossip_timestamp is more recent than the messages effective date, then the update process terminates.
Date lastSeenGossip = autocryptPeerDao.getLastSeenGossip(autocryptPeerId);
if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) {
return; return;
} }
Date lastSeenKey = autocryptPeerDao.getLastSeenKey(autocryptPeerId); if (!autocryptPeerUpdate.hasKeyData()) {
if (lastSeenKey != null && effectiveDate.before(lastSeenKey)) {
return; return;
} }
if (lastSeen == null || effectiveDate.after(lastSeen)) { SaveKeyringResult saveKeyringResult = parseAndImportAutocryptKeyData(autocryptPeerUpdate);
autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId); if (saveKeyringResult == null) {
return;
} }
// 3. Set peers[gossip-addr].gossip_timestamp to the messages effective date.
// 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute.
Long newMasterKeyId = saveKeyringResult.savedMasterKeyId;
autocryptPeerDao.updateKeyGossip(autocryptPeerId, effectiveDate, newMasterKeyId);
}
@Nullable
private SaveKeyringResult parseAndImportAutocryptKeyData(AutocryptPeerUpdate autocryptPeerUpdate) {
UncachedKeyRing uncachedKeyRing = parseAutocryptKeyData(autocryptPeerUpdate);
if (uncachedKeyRing != null) {
return importAutocryptKeyData(uncachedKeyRing);
}
return null;
}
@Nullable
private SaveKeyringResult importAutocryptKeyData(UncachedKeyRing uncachedKeyRing) {
SaveKeyringResult saveKeyringResult = keyWritableRepository.savePublicKeyRing(uncachedKeyRing);
if (!saveKeyringResult.success()) {
Timber.e(Constants.TAG, "Error inserting key - ignoring!");
return null;
}
return saveKeyringResult;
}
@Nullable
private UncachedKeyRing parseAutocryptKeyData(AutocryptPeerUpdate autocryptPeerUpdate) {
UncachedKeyRing uncachedKeyRing;
try {
uncachedKeyRing = UncachedKeyRing.decodeFromData(autocryptPeerUpdate.getKeyData());
} catch (IOException | PgpGeneralException e) {
Timber.e(Constants.TAG, "Error parsing public key! - Ignoring");
return null;
}
if (uncachedKeyRing.isSecret()) {
Timber.e(Constants.TAG, "Found secret key in autocrypt id! - Ignoring");
return null;
}
return uncachedKeyRing;
} }
} }

View File

@@ -762,7 +762,9 @@ public class OpenPgpService extends Service {
String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID); String autocryptPeerId = data.getStringExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID);
AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE); AutocryptPeerUpdate autocryptPeerUpdate = data.getParcelableExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE);
autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate); if (autocryptPeerUpdate != null) {
autocryptInteractor.updateAutocryptPeerState(autocryptPeerId, autocryptPeerUpdate);
}
} }
if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) { if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES)) {
@@ -770,7 +772,9 @@ public class OpenPgpService extends Service {
for (String address : updates.keySet()) { for (String address : updates.keySet()) {
Timber.d(Constants.TAG, "Updating gossip state: " + address); Timber.d(Constants.TAG, "Updating gossip state: " + address);
AutocryptPeerUpdate update = updates.getParcelable(address); AutocryptPeerUpdate update = updates.getParcelable(address);
autocryptInteractor.updateAutocryptPeerGossipState(address, update); if (update != null) {
autocryptInteractor.updateAutocryptPeerGossipState(address, update);
}
} }
} }