split up autocrypt operations class into package

This commit is contained in:
Vincent Breitmoser 2017-08-27 23:41:03 +02:00
parent 05361adfde
commit 520bc2543d
6 changed files with 186 additions and 163 deletions

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -18,7 +18,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import com.fsck.k9.K9; 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.crypto.MessageDecryptVerifier;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
@ -94,7 +94,7 @@ public class MessageCryptoHelper {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
if (!K9.isOpenPgpProviderConfigured()) { 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; this.openPgpApiFactory = openPgpApiFactory;

View file

@ -1,4 +1,4 @@
package com.fsck.k9.crypto; package com.fsck.k9.autocrypt;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -6,7 +6,6 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import com.fsck.k9.crypto.AutocryptOperations.AutocryptHeader;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody; import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
@ -24,8 +23,9 @@ import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21) @Config(manifest = Config.NONE, sdk = 21)
public class AutocryptOperationsTest { @SuppressWarnings("WeakerAccess")
AutocryptOperations autocryptOperations = new AutocryptOperations(); public class AutocryptHeaderParserTest {
AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance();
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -38,7 +38,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withNoHeader__shouldReturnNull() throws Exception { public void getValidAutocryptHeader__withNoHeader__shouldReturnNull() throws Exception {
MimeMessage message = parseFromResource("autocrypt/no_autocrypt.eml"); MimeMessage message = parseFromResource("autocrypt/no_autocrypt.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNull(autocryptHeader); assertNull(autocryptHeader);
} }
@ -47,7 +47,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception { public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception {
MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml"); MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNull(autocryptHeader); assertNull(autocryptHeader);
} }
@ -56,7 +56,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception { public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception {
MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml"); MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNotNull(autocryptHeader); assertNotNull(autocryptHeader);
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
@ -68,7 +68,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withExplicitType() throws Exception { public void getValidAutocryptHeader__withExplicitType() throws Exception {
MimeMessage message = parseFromResource("autocrypt/rsa2048-explicit-type.eml"); MimeMessage message = parseFromResource("autocrypt/rsa2048-explicit-type.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNotNull(autocryptHeader); assertNotNull(autocryptHeader);
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
@ -79,7 +79,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception { public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception {
MimeMessage message = parseFromResource("autocrypt/unknown-type.eml"); MimeMessage message = parseFromResource("autocrypt/unknown-type.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNull(autocryptHeader); assertNull(autocryptHeader);
} }
@ -88,7 +88,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception { public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception {
MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml"); MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNull(autocryptHeader); assertNull(autocryptHeader);
} }
@ -97,7 +97,7 @@ public class AutocryptOperationsTest {
public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception { public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception {
MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml"); MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml");
AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);
assertNotNull(autocryptHeader); assertNotNull(autocryptHeader);
assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);