Loading app/core/src/main/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeader.kt 0 → 100644 +56 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.message.CryptoStatus data class AutocryptDraftStateHeader(val isEncrypt: Boolean, val isSignOnly: Boolean, val isReply: Boolean, val isByChoice: Boolean, val isPgpInline: Boolean, val parameters: Map<String, String> = mapOf()) { fun toHeaderValue(): String { val builder = StringBuilder() builder.append(AutocryptDraftStateHeader.PARAM_ENCRYPT) builder.append(if (isEncrypt) "=yes; " else "=no; ") if (isReply) { builder.append(AutocryptDraftStateHeader.PARAM_IS_REPLY).append("=yes; ") } if (isSignOnly) { builder.append(AutocryptDraftStateHeader.PARAM_SIGN_ONLY).append("=yes; ") } if (isByChoice) { builder.append(AutocryptDraftStateHeader.PARAM_BY_CHOICE).append("=yes; ") } if (isPgpInline) { builder.append(AutocryptDraftStateHeader.PARAM_PGP_INLINE).append("=yes; ") } return builder.toString() } companion object { const val AUTOCRYPT_DRAFT_STATE_HEADER = "Autocrypt-Draft-State" const val PARAM_ENCRYPT = "encrypt" const val PARAM_IS_REPLY = "_is-reply" const val PARAM_BY_CHOICE = "_by-choice" const val PARAM_PGP_INLINE = "_pgp-inline" const val PARAM_SIGN_ONLY = "_sign-only" const val VALUE_YES = "yes"; fun fromCryptoStatus(cryptoStatus: CryptoStatus): AutocryptDraftStateHeader { if (cryptoStatus.isSignOnly()) { return AutocryptDraftStateHeader(false, true, cryptoStatus.isReplyToEncrypted(), cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf()) } return AutocryptDraftStateHeader(cryptoStatus.isEncryptionEnabled(), false, cryptoStatus.isReplyToEncrypted(), cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf()) } } } app/core/src/main/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeaderParser.kt 0 → 100644 +46 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.mail.internet.MimeUtility class AutocryptDraftStateHeaderParser internal constructor() { fun parseAutocryptDraftStateHeader(headerValue: String): AutocryptDraftStateHeader? { val parameters = MimeUtility.getAllHeaderParameters(headerValue) val isEncryptStr = parameters.remove(AutocryptDraftStateHeader.PARAM_ENCRYPT) ?: return null val isEncrypt = isEncryptStr == AutocryptDraftStateHeader.VALUE_YES val isSignOnlyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_SIGN_ONLY) val isSignOnly = isSignOnlyStr == AutocryptDraftStateHeader.VALUE_YES val isReplyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_IS_REPLY) val isReply = isReplyStr == AutocryptDraftStateHeader.VALUE_YES val isByChoiceStr = parameters.remove(AutocryptDraftStateHeader.PARAM_BY_CHOICE) val isByChoice = isByChoiceStr == AutocryptDraftStateHeader.VALUE_YES val isPgpInlineStr = parameters.remove(AutocryptDraftStateHeader.PARAM_PGP_INLINE) val isPgpInline = isPgpInlineStr == AutocryptDraftStateHeader.VALUE_YES if (hasCriticalParameters(parameters)) { return null } return AutocryptDraftStateHeader(isEncrypt, isSignOnly, isReply, isByChoice, isPgpInline, parameters) } private fun hasCriticalParameters(parameters: Map<String, String>): Boolean { for (parameterName in parameters.keys) { if (!parameterName.startsWith("_")) { return true } } return false } companion object { val instance = AutocryptDraftStateHeaderParser() } } app/core/src/main/java/com/fsck/k9/message/CryptoStatus.kt +3 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ interface CryptoStatus { fun isSigningEnabled(): Boolean fun isEncryptionEnabled(): Boolean fun isPgpInlineModeEnabled(): Boolean fun isSignOnly(): Boolean fun isUserChoice(): Boolean fun isReplyToEncrypted(): Boolean fun hasRecipients(): Boolean fun isEncryptSubject(): Boolean fun getRecipientAddresses(): Array<String> Loading app/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java +25 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import android.support.annotation.VisibleForTesting; import com.fsck.k9.CoreResourceProvider; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.autocrypt.AutocryptDraftStateHeader; import com.fsck.k9.autocrypt.AutocryptOpenPgpApiInteractor; import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.mail.Address; Loading Loading @@ -110,6 +111,13 @@ public class PgpMessageBuilder extends MessageBuilder { return; } addAutocryptHeaderIfAvailable(openPgpKeyId); addDraftStateHeader(); startOrContinueBuildMessage(null); } private void addAutocryptHeaderIfAvailable(long openPgpKeyId) { Address address = currentProcessedMimeMessage.getFrom()[0]; byte[] keyData = autocryptOpenPgpApiInteractor.getKeyMaterialForKeyId( openPgpApi, openPgpKeyId, address.getAddress()); Loading @@ -117,8 +125,13 @@ public class PgpMessageBuilder extends MessageBuilder { autocryptOperations.addAutocryptHeaderToMessage(currentProcessedMimeMessage, keyData, address.getAddress(), cryptoStatus.isSenderPreferEncryptMutual()); } } startOrContinueBuildMessage(null); private void addDraftStateHeader() { AutocryptDraftStateHeader autocryptDraftStateHeader = AutocryptDraftStateHeader.Companion.fromCryptoStatus(cryptoStatus); currentProcessedMimeMessage.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER, autocryptDraftStateHeader.toHeaderValue()); } @Override Loading Loading @@ -155,8 +168,9 @@ public class PgpMessageBuilder extends MessageBuilder { boolean payloadSupportsMimeHeaders = !isPgpInlineMode; if (payloadSupportsMimeHeaders) { moveDraftStateIntoEncryptedPayload(); if (cryptoStatus.isEncryptSubject()) { encryptMessageSubject(); moveSubjectIntoEncryptedPayload(); } maybeAddGossipHeadersToBodyPart(); } Loading Loading @@ -195,7 +209,7 @@ public class PgpMessageBuilder extends MessageBuilder { return bodyPart; } private void encryptMessageSubject() { private void moveSubjectIntoEncryptedPayload() { String[] subjects = currentProcessedMimeMessage.getHeader(MimeHeader.SUBJECT); if (subjects.length > 0) { messageContentBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, Loading @@ -205,6 +219,14 @@ public class PgpMessageBuilder extends MessageBuilder { } } private void moveDraftStateIntoEncryptedPayload() { String[] autocryptDraftState = currentProcessedMimeMessage.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER); if (autocryptDraftState.length == 1) { messageContentBodyPart.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER, autocryptDraftState[0]); currentProcessedMimeMessage.removeHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER); } } private void maybeAddGossipHeadersToBodyPart() { if (!cryptoStatus.isEncryptionEnabled()) { return; Loading app/core/src/test/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeaderParserTest.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.RobolectricTest import com.google.common.truth.Truth.assertThat import org.junit.Test class AutocryptDraftStateHeaderParserTest : RobolectricTest() { internal var autocryptHeaderParser = AutocryptDraftStateHeaderParser() @Test fun testEncryptReplyByChoice() { val draftStateHeader = AutocryptDraftStateHeader(true, false, true, true, false) val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader(draftStateHeader.toHeaderValue()) assertThat(parsedHeader).isEqualTo(draftStateHeader) } @Test fun testSignOnly() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _by-choice=yes; _sign-only=yes") with(parsedHeader!!) { assertThat(isEncrypt).isFalse() assertThat(isByChoice).isTrue() assertThat(isSignOnly).isTrue() assertThat(isPgpInline).isFalse() assertThat(isReply).isFalse() } } @Test fun badCritical() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; badcritical=value") assertThat(parsedHeader).isNull() } @Test fun missingEncrypt() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrpt-with-typo=no; _non_critical=value") assertThat(parsedHeader).isNull() } @Test fun unknownNonCritical() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _non-critical=value") assertThat(parsedHeader).isNotNull() } } Loading
app/core/src/main/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeader.kt 0 → 100644 +56 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.message.CryptoStatus data class AutocryptDraftStateHeader(val isEncrypt: Boolean, val isSignOnly: Boolean, val isReply: Boolean, val isByChoice: Boolean, val isPgpInline: Boolean, val parameters: Map<String, String> = mapOf()) { fun toHeaderValue(): String { val builder = StringBuilder() builder.append(AutocryptDraftStateHeader.PARAM_ENCRYPT) builder.append(if (isEncrypt) "=yes; " else "=no; ") if (isReply) { builder.append(AutocryptDraftStateHeader.PARAM_IS_REPLY).append("=yes; ") } if (isSignOnly) { builder.append(AutocryptDraftStateHeader.PARAM_SIGN_ONLY).append("=yes; ") } if (isByChoice) { builder.append(AutocryptDraftStateHeader.PARAM_BY_CHOICE).append("=yes; ") } if (isPgpInline) { builder.append(AutocryptDraftStateHeader.PARAM_PGP_INLINE).append("=yes; ") } return builder.toString() } companion object { const val AUTOCRYPT_DRAFT_STATE_HEADER = "Autocrypt-Draft-State" const val PARAM_ENCRYPT = "encrypt" const val PARAM_IS_REPLY = "_is-reply" const val PARAM_BY_CHOICE = "_by-choice" const val PARAM_PGP_INLINE = "_pgp-inline" const val PARAM_SIGN_ONLY = "_sign-only" const val VALUE_YES = "yes"; fun fromCryptoStatus(cryptoStatus: CryptoStatus): AutocryptDraftStateHeader { if (cryptoStatus.isSignOnly()) { return AutocryptDraftStateHeader(false, true, cryptoStatus.isReplyToEncrypted(), cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf()) } return AutocryptDraftStateHeader(cryptoStatus.isEncryptionEnabled(), false, cryptoStatus.isReplyToEncrypted(), cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf()) } } }
app/core/src/main/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeaderParser.kt 0 → 100644 +46 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.mail.internet.MimeUtility class AutocryptDraftStateHeaderParser internal constructor() { fun parseAutocryptDraftStateHeader(headerValue: String): AutocryptDraftStateHeader? { val parameters = MimeUtility.getAllHeaderParameters(headerValue) val isEncryptStr = parameters.remove(AutocryptDraftStateHeader.PARAM_ENCRYPT) ?: return null val isEncrypt = isEncryptStr == AutocryptDraftStateHeader.VALUE_YES val isSignOnlyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_SIGN_ONLY) val isSignOnly = isSignOnlyStr == AutocryptDraftStateHeader.VALUE_YES val isReplyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_IS_REPLY) val isReply = isReplyStr == AutocryptDraftStateHeader.VALUE_YES val isByChoiceStr = parameters.remove(AutocryptDraftStateHeader.PARAM_BY_CHOICE) val isByChoice = isByChoiceStr == AutocryptDraftStateHeader.VALUE_YES val isPgpInlineStr = parameters.remove(AutocryptDraftStateHeader.PARAM_PGP_INLINE) val isPgpInline = isPgpInlineStr == AutocryptDraftStateHeader.VALUE_YES if (hasCriticalParameters(parameters)) { return null } return AutocryptDraftStateHeader(isEncrypt, isSignOnly, isReply, isByChoice, isPgpInline, parameters) } private fun hasCriticalParameters(parameters: Map<String, String>): Boolean { for (parameterName in parameters.keys) { if (!parameterName.startsWith("_")) { return true } } return false } companion object { val instance = AutocryptDraftStateHeaderParser() } }
app/core/src/main/java/com/fsck/k9/message/CryptoStatus.kt +3 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ interface CryptoStatus { fun isSigningEnabled(): Boolean fun isEncryptionEnabled(): Boolean fun isPgpInlineModeEnabled(): Boolean fun isSignOnly(): Boolean fun isUserChoice(): Boolean fun isReplyToEncrypted(): Boolean fun hasRecipients(): Boolean fun isEncryptSubject(): Boolean fun getRecipientAddresses(): Array<String> Loading
app/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java +25 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import android.support.annotation.VisibleForTesting; import com.fsck.k9.CoreResourceProvider; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.autocrypt.AutocryptDraftStateHeader; import com.fsck.k9.autocrypt.AutocryptOpenPgpApiInteractor; import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.mail.Address; Loading Loading @@ -110,6 +111,13 @@ public class PgpMessageBuilder extends MessageBuilder { return; } addAutocryptHeaderIfAvailable(openPgpKeyId); addDraftStateHeader(); startOrContinueBuildMessage(null); } private void addAutocryptHeaderIfAvailable(long openPgpKeyId) { Address address = currentProcessedMimeMessage.getFrom()[0]; byte[] keyData = autocryptOpenPgpApiInteractor.getKeyMaterialForKeyId( openPgpApi, openPgpKeyId, address.getAddress()); Loading @@ -117,8 +125,13 @@ public class PgpMessageBuilder extends MessageBuilder { autocryptOperations.addAutocryptHeaderToMessage(currentProcessedMimeMessage, keyData, address.getAddress(), cryptoStatus.isSenderPreferEncryptMutual()); } } startOrContinueBuildMessage(null); private void addDraftStateHeader() { AutocryptDraftStateHeader autocryptDraftStateHeader = AutocryptDraftStateHeader.Companion.fromCryptoStatus(cryptoStatus); currentProcessedMimeMessage.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER, autocryptDraftStateHeader.toHeaderValue()); } @Override Loading Loading @@ -155,8 +168,9 @@ public class PgpMessageBuilder extends MessageBuilder { boolean payloadSupportsMimeHeaders = !isPgpInlineMode; if (payloadSupportsMimeHeaders) { moveDraftStateIntoEncryptedPayload(); if (cryptoStatus.isEncryptSubject()) { encryptMessageSubject(); moveSubjectIntoEncryptedPayload(); } maybeAddGossipHeadersToBodyPart(); } Loading Loading @@ -195,7 +209,7 @@ public class PgpMessageBuilder extends MessageBuilder { return bodyPart; } private void encryptMessageSubject() { private void moveSubjectIntoEncryptedPayload() { String[] subjects = currentProcessedMimeMessage.getHeader(MimeHeader.SUBJECT); if (subjects.length > 0) { messageContentBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, Loading @@ -205,6 +219,14 @@ public class PgpMessageBuilder extends MessageBuilder { } } private void moveDraftStateIntoEncryptedPayload() { String[] autocryptDraftState = currentProcessedMimeMessage.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER); if (autocryptDraftState.length == 1) { messageContentBodyPart.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER, autocryptDraftState[0]); currentProcessedMimeMessage.removeHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER); } } private void maybeAddGossipHeadersToBodyPart() { if (!cryptoStatus.isEncryptionEnabled()) { return; Loading
app/core/src/test/java/com/fsck/k9/autocrypt/AutocryptDraftStateHeaderParserTest.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line package com.fsck.k9.autocrypt import com.fsck.k9.RobolectricTest import com.google.common.truth.Truth.assertThat import org.junit.Test class AutocryptDraftStateHeaderParserTest : RobolectricTest() { internal var autocryptHeaderParser = AutocryptDraftStateHeaderParser() @Test fun testEncryptReplyByChoice() { val draftStateHeader = AutocryptDraftStateHeader(true, false, true, true, false) val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader(draftStateHeader.toHeaderValue()) assertThat(parsedHeader).isEqualTo(draftStateHeader) } @Test fun testSignOnly() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _by-choice=yes; _sign-only=yes") with(parsedHeader!!) { assertThat(isEncrypt).isFalse() assertThat(isByChoice).isTrue() assertThat(isSignOnly).isTrue() assertThat(isPgpInline).isFalse() assertThat(isReply).isFalse() } } @Test fun badCritical() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; badcritical=value") assertThat(parsedHeader).isNull() } @Test fun missingEncrypt() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrpt-with-typo=no; _non_critical=value") assertThat(parsedHeader).isNull() } @Test fun unknownNonCritical() { val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _non-critical=value") assertThat(parsedHeader).isNotNull() } }