Support parsing of Autocrypt-Gossip headers

This commit is contained in:
Vincent Breitmoser 2017-10-27 15:07:49 +02:00
parent a293294f22
commit 88c92feaf3
5 changed files with 213 additions and 4 deletions

View file

@ -14,9 +14,9 @@ class AutocryptGossipHeader {
@NonNull
private final byte[] keyData;
final byte[] keyData;
@NonNull
private final String addr;
final String addr;
AutocryptGossipHeader(@NonNull String addr, @NonNull byte[] keyData) {
this.addr = addr;

View file

@ -0,0 +1,93 @@
package com.fsck.k9.autocrypt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
import okio.ByteString;
import timber.log.Timber;
class AutocryptGossipHeaderParser {
private static final AutocryptGossipHeaderParser INSTANCE = new AutocryptGossipHeaderParser();
public static AutocryptGossipHeaderParser getInstance() {
return INSTANCE;
}
private AutocryptGossipHeaderParser() { }
List<AutocryptGossipHeader> getAllAutocryptGossipHeaders(Part part) {
String[] headers = part.getHeader(AutocryptGossipHeader.AUTOCRYPT_GOSSIP_HEADER);
List<AutocryptGossipHeader> autocryptHeaders = parseAllAutocryptGossipHeaders(headers);
return Collections.unmodifiableList(autocryptHeaders);
}
@Nullable
@VisibleForTesting
AutocryptGossipHeader parseAutocryptGossipHeader(String headerValue) {
Map<String,String> parameters = MimeUtility.getAllHeaderParameters(headerValue);
String type = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_TYPE);
if (type != null && !type.equals(AutocryptHeader.AUTOCRYPT_TYPE_1)) {
Timber.e("autocrypt: unsupported type parameter %s", type);
return null;
}
String base64KeyData = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_KEY_DATA);
if (base64KeyData == null) {
Timber.e("autocrypt: missing key parameter");
return null;
}
ByteString byteString = ByteString.decodeBase64(base64KeyData);
if (byteString == null) {
Timber.e("autocrypt: error parsing base64 data");
return null;
}
String addr = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_ADDR);
if (addr == null) {
Timber.e("autocrypt: no to header!");
return null;
}
if (hasCriticalParameters(parameters)) {
return null;
}
return new AutocryptGossipHeader(addr, byteString.toByteArray());
}
private boolean hasCriticalParameters(Map<String, String> parameters) {
for (String parameterName : parameters.keySet()) {
if (parameterName != null && !parameterName.startsWith("_")) {
return true;
}
}
return false;
}
@NonNull
private List<AutocryptGossipHeader> parseAllAutocryptGossipHeaders(String[] headers) {
ArrayList<AutocryptGossipHeader> autocryptHeaders = new ArrayList<>();
for (String header : headers) {
AutocryptGossipHeader autocryptHeader = parseAutocryptGossipHeader(header);
if (autocryptHeader != null) {
autocryptHeaders.add(autocryptHeader);
}
}
return autocryptHeaders;
}
}

View file

@ -1,12 +1,18 @@
package com.fsck.k9.autocrypt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeBodyPart;
import org.openintents.openpgp.AutocryptPeerUpdate;
import org.openintents.openpgp.util.OpenPgpApi;
@ -14,16 +20,20 @@ import org.openintents.openpgp.util.OpenPgpApi;
public class AutocryptOperations {
private final AutocryptHeaderParser autocryptHeaderParser;
private final AutocryptGossipHeaderParser autocryptGossipHeaderParser;
public static AutocryptOperations getInstance() {
AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance();
return new AutocryptOperations(autocryptHeaderParser);
AutocryptGossipHeaderParser autocryptGossipHeaderParser = AutocryptGossipHeaderParser.getInstance();
return new AutocryptOperations(autocryptHeaderParser, autocryptGossipHeaderParser);
}
private AutocryptOperations(AutocryptHeaderParser autocryptHeaderParser) {
private AutocryptOperations(AutocryptHeaderParser autocryptHeaderParser,
AutocryptGossipHeaderParser autocryptGossipHeaderParser) {
this.autocryptHeaderParser = autocryptHeaderParser;
this.autocryptGossipHeaderParser = autocryptGossipHeaderParser;
}
public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) {
@ -48,10 +58,92 @@ public class AutocryptOperations {
return true;
}
public boolean addAutocryptGossipUpdateToIntentIfPresent(Message message, MimeBodyPart decryptedPart, Intent intent) {
Bundle updates = createGossipUpdateBundle(message, decryptedPart);
if (updates == null) {
return false;
}
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES, updates);
return true;
}
@Nullable
private Bundle createGossipUpdateBundle(Message message, MimeBodyPart decryptedPart) {
List<String> gossipAcceptedAddresses = getGossipAcceptedAddresses(message);
if (gossipAcceptedAddresses.isEmpty()) {
return null;
}
List<AutocryptGossipHeader> autocryptGossipHeaders =
autocryptGossipHeaderParser.getAllAutocryptGossipHeaders(decryptedPart);
if (autocryptGossipHeaders.isEmpty()) {
return null;
}
Date messageDate = message.getSentDate();
Date internalDate = message.getInternalDate();
Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate;
return createGossipUpdateBundle(gossipAcceptedAddresses, autocryptGossipHeaders, effectiveDate);
}
@Nullable
private Bundle createGossipUpdateBundle(List<String> gossipAcceptedAddresses,
List<AutocryptGossipHeader> autocryptGossipHeaders, Date effectiveDate) {
Bundle updates = new Bundle();
for (AutocryptGossipHeader autocryptGossipHeader : autocryptGossipHeaders) {
boolean isAcceptedAddress = gossipAcceptedAddresses.contains(autocryptGossipHeader.addr.toLowerCase());
if (!isAcceptedAddress) {
continue;
}
AutocryptPeerUpdate update = AutocryptPeerUpdate.create(autocryptGossipHeader.keyData, effectiveDate, false);
updates.putParcelable(autocryptGossipHeader.addr, update);
}
if (updates.isEmpty()) {
return null;
}
return updates;
}
private List<String> getGossipAcceptedAddresses(Message message) {
ArrayList<String> result = new ArrayList<>();
addRecipientsToList(result, message, RecipientType.TO);
addRecipientsToList(result, message, RecipientType.CC);
removeRecipientsFromList(result, message, RecipientType.DELIVERED_TO);
return Collections.unmodifiableList(result);
}
private void addRecipientsToList(ArrayList<String> result, Message message, RecipientType recipientType) {
for (Address address : message.getRecipients(recipientType)) {
String addr = address.getAddress();
if (addr != null) {
result.add(addr.toLowerCase());
}
}
}
private void removeRecipientsFromList(ArrayList<String> result, Message message, RecipientType recipientType) {
for (Address address : message.getRecipients(recipientType)) {
String addr = address.getAddress();
if (addr != null) {
result.remove(addr);
}
}
}
public boolean hasAutocryptHeader(Message currentMessage) {
return currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER).length > 0;
}
public boolean hasAutocryptGossipHeader(MimeBodyPart part) {
return part.getHeader(AutocryptGossipHeader.AUTOCRYPT_GOSSIP_HEADER).length > 0;
}
public void addAutocryptHeaderToMessage(Message message, byte[] keyData,
String autocryptAddress, boolean preferEncryptMutual) {
AutocryptHeader autocryptHeader = new AutocryptHeader(

View file

@ -555,6 +555,9 @@ public class MessageCryptoHelper {
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_DECRYPTION);
OpenPgpSignatureResult signatureResult =
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_ENCRYPTED) {
parseAutocryptGossipHeadersFromDecryptedPart(outputPart);
}
PendingIntent pendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
PendingIntent insecureWarningPendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT);
boolean overrideCryptoWarning = currentCryptoResult.getBooleanExtra(
@ -566,6 +569,26 @@ public class MessageCryptoHelper {
onCryptoOperationSuccess(resultAnnotation);
}
private void parseAutocryptGossipHeadersFromDecryptedPart(MimeBodyPart outputPart) {
if (!autocryptOperations.hasAutocryptGossipHeader(outputPart)) {
return;
}
Intent intent = new Intent(OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER);
boolean hasInlineKeyData = autocryptOperations.addAutocryptGossipUpdateToIntentIfPresent(
currentMessage, outputPart, intent);
if (hasInlineKeyData) {
Timber.d("Passing autocrypt data from plain mail to OpenPGP API");
// We don't care about the result here, so we just call this fire-and-forget wait to minimize delay
openPgpApi.executeApiAsync(intent, null, null, new IOpenPgpCallback() {
@Override
public void onReturn(Intent result) {
Timber.d("Autocrypt update OK!");
}
});
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (isCancelled) {
return;

View file

@ -308,6 +308,7 @@ public class OpenPgpApi {
// UPDATE_AUTOCRYPT_PEER
public static final String EXTRA_AUTOCRYPT_PEER_ID = "autocrypt_peer_id";
public static final String EXTRA_AUTOCRYPT_PEER_UPDATE = "autocrypt_peer_update";
public static final String EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES = "autocrypt_peer_gossip_updates";
// INTERNAL, must not be used
public static final String EXTRA_CALL_UUID1 = "call_uuid1";