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
|
@NonNull
|
||||||
private final byte[] keyData;
|
final byte[] keyData;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String addr;
|
final String addr;
|
||||||
|
|
||||||
AutocryptGossipHeader(@NonNull String addr, @NonNull byte[] keyData) {
|
AutocryptGossipHeader(@NonNull String addr, @NonNull byte[] keyData) {
|
||||||
this.addr = addr;
|
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;
|
package com.fsck.k9.autocrypt;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.content.Intent;
|
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;
|
||||||
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||||
import org.openintents.openpgp.AutocryptPeerUpdate;
|
import org.openintents.openpgp.AutocryptPeerUpdate;
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
|
@ -14,16 +20,20 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
|
|
||||||
public class AutocryptOperations {
|
public class AutocryptOperations {
|
||||||
private final AutocryptHeaderParser autocryptHeaderParser;
|
private final AutocryptHeaderParser autocryptHeaderParser;
|
||||||
|
private final AutocryptGossipHeaderParser autocryptGossipHeaderParser;
|
||||||
|
|
||||||
|
|
||||||
public static AutocryptOperations getInstance() {
|
public static AutocryptOperations getInstance() {
|
||||||
AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.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.autocryptHeaderParser = autocryptHeaderParser;
|
||||||
|
this.autocryptGossipHeaderParser = autocryptGossipHeaderParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) {
|
public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) {
|
||||||
|
@ -48,10 +58,92 @@ public class AutocryptOperations {
|
||||||
return true;
|
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) {
|
public boolean hasAutocryptHeader(Message currentMessage) {
|
||||||
return currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER).length > 0;
|
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,
|
public void addAutocryptHeaderToMessage(Message message, byte[] keyData,
|
||||||
String autocryptAddress, boolean preferEncryptMutual) {
|
String autocryptAddress, boolean preferEncryptMutual) {
|
||||||
AutocryptHeader autocryptHeader = new AutocryptHeader(
|
AutocryptHeader autocryptHeader = new AutocryptHeader(
|
||||||
|
|
|
@ -555,6 +555,9 @@ public class MessageCryptoHelper {
|
||||||
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_DECRYPTION);
|
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_DECRYPTION);
|
||||||
OpenPgpSignatureResult signatureResult =
|
OpenPgpSignatureResult signatureResult =
|
||||||
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
||||||
|
if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_ENCRYPTED) {
|
||||||
|
parseAutocryptGossipHeadersFromDecryptedPart(outputPart);
|
||||||
|
}
|
||||||
PendingIntent pendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
PendingIntent pendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||||
PendingIntent insecureWarningPendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT);
|
PendingIntent insecureWarningPendingIntent = currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INSECURE_DETAIL_INTENT);
|
||||||
boolean overrideCryptoWarning = currentCryptoResult.getBooleanExtra(
|
boolean overrideCryptoWarning = currentCryptoResult.getBooleanExtra(
|
||||||
|
@ -566,6 +569,26 @@ public class MessageCryptoHelper {
|
||||||
onCryptoOperationSuccess(resultAnnotation);
|
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) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -308,6 +308,7 @@ public class OpenPgpApi {
|
||||||
// UPDATE_AUTOCRYPT_PEER
|
// UPDATE_AUTOCRYPT_PEER
|
||||||
public static final String EXTRA_AUTOCRYPT_PEER_ID = "autocrypt_peer_id";
|
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_UPDATE = "autocrypt_peer_update";
|
||||||
|
public static final String EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES = "autocrypt_peer_gossip_updates";
|
||||||
|
|
||||||
// INTERNAL, must not be used
|
// INTERNAL, must not be used
|
||||||
public static final String EXTRA_CALL_UUID1 = "call_uuid1";
|
public static final String EXTRA_CALL_UUID1 = "call_uuid1";
|
||||||
|
|
Loading…
Reference in a new issue