Support parsing of Autocrypt-Gossip headers
This commit is contained in:
parent
a293294f22
commit
88c92feaf3
5 changed files with 213 additions and 4 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue