split up autocrypt operations class into package
This commit is contained in:
parent
05361adfde
commit
520bc2543d
6 changed files with 186 additions and 163 deletions
|
@ -0,0 +1,31 @@
|
|||
package com.fsck.k9.autocrypt;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
class AutocryptHeader {
|
||||
static final String AUTOCRYPT_HEADER = "Autocrypt";
|
||||
|
||||
static final String AUTOCRYPT_PARAM_TO = "addr";
|
||||
static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata";
|
||||
|
||||
static final String AUTOCRYPT_PARAM_TYPE = "type";
|
||||
static final String AUTOCRYPT_TYPE_1 = "1";
|
||||
|
||||
static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt";
|
||||
static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual";
|
||||
|
||||
|
||||
final byte[] keyData;
|
||||
final String addr;
|
||||
final Map<String,String> parameters;
|
||||
final boolean isPreferEncryptMutual;
|
||||
|
||||
AutocryptHeader(Map<String, String> parameters, String addr, byte[] keyData, boolean isPreferEncryptMutual) {
|
||||
this.parameters = parameters;
|
||||
this.addr = addr;
|
||||
this.keyData = keyData;
|
||||
this.isPreferEncryptMutual = isPreferEncryptMutual;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package com.fsck.k9.autocrypt;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import okio.ByteString;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
class AutocryptHeaderParser {
|
||||
private static final AutocryptHeaderParser INSTANCE = new AutocryptHeaderParser();
|
||||
|
||||
|
||||
public static AutocryptHeaderParser getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private AutocryptHeaderParser() { }
|
||||
|
||||
|
||||
@Nullable
|
||||
AutocryptHeader getValidAutocryptHeader(Message currentMessage) {
|
||||
String[] headers = currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER);
|
||||
ArrayList<AutocryptHeader> autocryptHeaders = parseAllAutocryptHeaders(headers);
|
||||
|
||||
boolean isSingleValidHeader = autocryptHeaders.size() == 1;
|
||||
return isSingleValidHeader ? autocryptHeaders.get(0) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AutocryptHeader parseAutocryptHeader(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 to = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_TO);
|
||||
if (to == null) {
|
||||
Timber.e("autocrypt: no to header!");
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isPreferEncryptMutual = false;
|
||||
String preferEncrypt = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_PREFER_ENCRYPT);
|
||||
if (AutocryptHeader.AUTOCRYPT_PREFER_ENCRYPT_MUTUAL.equalsIgnoreCase(preferEncrypt)) {
|
||||
isPreferEncryptMutual = true;
|
||||
}
|
||||
|
||||
if (hasCriticalParameters(parameters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AutocryptHeader(parameters, to, byteString.toByteArray(), isPreferEncryptMutual);
|
||||
}
|
||||
|
||||
private boolean hasCriticalParameters(Map<String, String> parameters) {
|
||||
for (String parameterName : parameters.keySet()) {
|
||||
if (parameterName != null && !parameterName.startsWith("_")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ArrayList<AutocryptHeader> parseAllAutocryptHeaders(String[] headers) {
|
||||
ArrayList<AutocryptHeader> autocryptHeaders = new ArrayList<>();
|
||||
for (String header : headers) {
|
||||
AutocryptHeader autocryptHeader = parseAutocryptHeader(header);
|
||||
if (autocryptHeader != null) {
|
||||
autocryptHeaders.add(autocryptHeader);
|
||||
}
|
||||
}
|
||||
return autocryptHeaders;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.fsck.k9.autocrypt;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.fsck.k9.mail.Message;
|
||||
import org.openintents.openpgp.AutocryptPeerUpdate;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
|
||||
|
||||
public class AutocryptOperations {
|
||||
private final AutocryptHeaderParser autocryptHeaderParser;
|
||||
|
||||
public AutocryptOperations() {
|
||||
this.autocryptHeaderParser = AutocryptHeaderParser.getInstance();
|
||||
}
|
||||
|
||||
public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) {
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(currentMessage);
|
||||
if (autocryptHeader == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String messageFromAddress = currentMessage.getFrom()[0].getAddress();
|
||||
if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Date messageDate = currentMessage.getSentDate();
|
||||
Date internalDate = currentMessage.getInternalDate();
|
||||
Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate;
|
||||
|
||||
AutocryptPeerUpdate data = AutocryptPeerUpdate.create(
|
||||
autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasAutocryptHeader(Message currentMessage) {
|
||||
return currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER).length > 0;
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
package com.fsck.k9.crypto;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import okio.ByteString;
|
||||
import org.openintents.openpgp.AutocryptPeerUpdate;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class AutocryptOperations {
|
||||
private static final String AUTOCRYPT_HEADER = "Autocrypt";
|
||||
|
||||
private static final String AUTOCRYPT_PARAM_TO = "addr";
|
||||
private static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata";
|
||||
|
||||
private static final String AUTOCRYPT_PARAM_TYPE = "type";
|
||||
private static final String AUTOCRYPT_TYPE_1 = "1";
|
||||
|
||||
private static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt";
|
||||
private static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual";
|
||||
|
||||
|
||||
public AutocryptOperations() {
|
||||
}
|
||||
|
||||
|
||||
public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) {
|
||||
AutocryptHeader autocryptHeader = getValidAutocryptHeader(currentMessage);
|
||||
if (autocryptHeader == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String messageFromAddress = currentMessage.getFrom()[0].getAddress();
|
||||
if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Date messageDate = currentMessage.getSentDate();
|
||||
Date internalDate = currentMessage.getInternalDate();
|
||||
Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate;
|
||||
|
||||
AutocryptPeerUpdate data = AutocryptPeerUpdate.create(
|
||||
autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
AutocryptHeader getValidAutocryptHeader(Message currentMessage) {
|
||||
String[] headers = currentMessage.getHeader(AUTOCRYPT_HEADER);
|
||||
ArrayList<AutocryptHeader> autocryptHeaders = parseAllAutocryptHeaders(headers);
|
||||
|
||||
boolean isSingleValidHeader = autocryptHeaders.size() == 1;
|
||||
return isSingleValidHeader ? autocryptHeaders.get(0) : null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ArrayList<AutocryptHeader> parseAllAutocryptHeaders(String[] headers) {
|
||||
ArrayList<AutocryptHeader> autocryptHeaders = new ArrayList<>();
|
||||
for (String header : headers) {
|
||||
AutocryptHeader autocryptHeader = parseAutocryptHeader(header);
|
||||
if (autocryptHeader != null) {
|
||||
autocryptHeaders.add(autocryptHeader);
|
||||
}
|
||||
}
|
||||
return autocryptHeaders;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AutocryptHeader parseAutocryptHeader(String headerValue) {
|
||||
Map<String,String> parameters = MimeUtility.getAllHeaderParameters(headerValue);
|
||||
|
||||
String type = parameters.remove(AUTOCRYPT_PARAM_TYPE);
|
||||
if (type != null && !type.equals(AUTOCRYPT_TYPE_1)) {
|
||||
Timber.e("autocrypt: unsupported type parameter %s", type);
|
||||
return null;
|
||||
}
|
||||
|
||||
String base64KeyData = parameters.remove(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 to = parameters.remove(AUTOCRYPT_PARAM_TO);
|
||||
if (to == null) {
|
||||
Timber.e("autocrypt: no to header!");
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isPreferEncryptMutual = false;
|
||||
String preferEncrypt = parameters.remove(AUTOCRYPT_PARAM_PREFER_ENCRYPT);
|
||||
if (AUTOCRYPT_PREFER_ENCRYPT_MUTUAL.equalsIgnoreCase(preferEncrypt)) {
|
||||
isPreferEncryptMutual = true;
|
||||
}
|
||||
|
||||
if (hasCriticalParameters(parameters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AutocryptHeader(parameters, to, byteString.toByteArray(), isPreferEncryptMutual);
|
||||
}
|
||||
|
||||
private boolean hasCriticalParameters(Map<String, String> parameters) {
|
||||
for (String parameterName : parameters.keySet()) {
|
||||
if (parameterName != null && !parameterName.startsWith("_")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasAutocryptHeader(Message currentMessage) {
|
||||
return currentMessage.getHeader(AUTOCRYPT_HEADER).length > 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class AutocryptHeader {
|
||||
final byte[] keyData;
|
||||
final String addr;
|
||||
final Map<String,String> parameters;
|
||||
final boolean isPreferEncryptMutual;
|
||||
|
||||
private AutocryptHeader(Map<String, String> parameters, String addr, byte[] keyData, boolean isPreferEncryptMutual) {
|
||||
this.parameters = parameters;
|
||||
this.addr = addr;
|
||||
this.keyData = keyData;
|
||||
this.isPreferEncryptMutual = isPreferEncryptMutual;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import android.support.annotation.Nullable;
|
|||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.crypto.AutocryptOperations;
|
||||
import com.fsck.k9.autocrypt.AutocryptOperations;
|
||||
import com.fsck.k9.crypto.MessageDecryptVerifier;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Body;
|
||||
|
@ -94,7 +94,7 @@ public class MessageCryptoHelper {
|
|||
this.context = context.getApplicationContext();
|
||||
|
||||
if (!K9.isOpenPgpProviderConfigured()) {
|
||||
throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!");
|
||||
throw new IllegalStateException("MessageCryptoHelper must only be called with a OpenPGP provider!");
|
||||
}
|
||||
|
||||
this.openPgpApiFactory = openPgpApiFactory;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.fsck.k9.crypto;
|
||||
package com.fsck.k9.autocrypt;
|
||||
|
||||
|
||||
import java.io.FileInputStream;
|
||||
|
@ -6,7 +6,6 @@ import java.io.FileNotFoundException;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.fsck.k9.crypto.AutocryptOperations.AutocryptHeader;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||
import com.fsck.k9.mail.internet.MimeMessage;
|
||||
|
@ -24,8 +23,9 @@ import static org.junit.Assert.assertNull;
|
|||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, sdk = 21)
|
||||
public class AutocryptOperationsTest {
|
||||
AutocryptOperations autocryptOperations = new AutocryptOperations();
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class AutocryptHeaderParserTest {
|
||||
AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
@ -38,7 +38,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withNoHeader__shouldReturnNull() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/no_autocrypt.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNull(autocryptHeader);
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNull(autocryptHeader);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNotNull(autocryptHeader);
|
||||
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
|
||||
|
@ -68,7 +68,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withExplicitType() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/rsa2048-explicit-type.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNotNull(autocryptHeader);
|
||||
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
|
||||
|
@ -79,7 +79,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/unknown-type.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNull(autocryptHeader);
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNull(autocryptHeader);
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public class AutocryptOperationsTest {
|
|||
public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception {
|
||||
MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml");
|
||||
|
||||
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message);
|
||||
AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
|
||||
|
||||
assertNotNull(autocryptHeader);
|
||||
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
|
Loading…
Reference in a new issue