Add more errors to ServerSettingsValidationResult
This commit is contained in:
parent
b73ebdeecc
commit
928b18422e
12 changed files with 388 additions and 0 deletions
|
@ -136,6 +136,14 @@ abstract class BaseServerValidationViewModel(
|
|||
Error.CertificateError(result.certificateChain),
|
||||
)
|
||||
|
||||
ServerSettingsValidationResult.ClientCertificateError.ClientCertificateExpired -> updateError(
|
||||
Error.ClientCertificateExpired,
|
||||
)
|
||||
|
||||
ServerSettingsValidationResult.ClientCertificateError.ClientCertificateRetrievalFailure -> updateError(
|
||||
Error.ClientCertificateRetrievalFailure,
|
||||
)
|
||||
|
||||
is ServerSettingsValidationResult.NetworkError -> updateError(
|
||||
Error.NetworkError(result.exception),
|
||||
)
|
||||
|
@ -144,6 +152,10 @@ abstract class BaseServerValidationViewModel(
|
|||
Error.ServerError(result.serverMessage),
|
||||
)
|
||||
|
||||
is ServerSettingsValidationResult.MissingServerCapabilityError -> updateError(
|
||||
Error.MissingServerCapabilityError(result.capabilityName),
|
||||
)
|
||||
|
||||
is ServerSettingsValidationResult.UnknownError -> updateError(
|
||||
Error.UnknownError(result.exception.message ?: "Unknown error"),
|
||||
)
|
||||
|
|
|
@ -46,8 +46,11 @@ interface ServerValidationContract {
|
|||
sealed interface Error {
|
||||
data class NetworkError(val exception: IOException) : Error
|
||||
data class CertificateError(val certificateChain: List<X509Certificate>) : Error
|
||||
data object ClientCertificateRetrievalFailure : Error
|
||||
data object ClientCertificateExpired : Error
|
||||
data class AuthenticationError(val serverMessage: String?) : Error
|
||||
data class ServerError(val serverMessage: String?) : Error
|
||||
data class MissingServerCapabilityError(val capabilityName: String) : Error
|
||||
data class UnknownError(val message: String) : Error
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,22 @@ internal fun Error.toResourceString(resources: Resources): String {
|
|||
detailsMessage = message,
|
||||
)
|
||||
}
|
||||
|
||||
Error.ClientCertificateExpired -> {
|
||||
resources.getString(R.string.account_server_validation_error_client_certificate_expired)
|
||||
}
|
||||
|
||||
Error.ClientCertificateRetrievalFailure -> {
|
||||
resources.getString(R.string.account_server_validation_error_client_certificate_retrieval_failure)
|
||||
}
|
||||
|
||||
is Error.MissingServerCapabilityError -> {
|
||||
resources.buildErrorString(
|
||||
titleResId = R.string.account_server_validation_error_missing_server_capability,
|
||||
detailsResId = R.string.account_server_validation_error_missing_server_capability_details,
|
||||
detailsMessage = capabilityName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
<string name="account_server_validation_error_network">Network error</string>
|
||||
<string name="account_server_validation_error_server">Server error</string>
|
||||
<string name="account_server_validation_error_unknown">Unknown error</string>
|
||||
<string name="account_server_validation_error_client_certificate_expired">The client certificate is no longer valid</string>
|
||||
<string name="account_server_validation_error_client_certificate_retrieval_failure">"The client certificate couldn't be accessed"</string>
|
||||
<string name="account_server_validation_error_missing_server_capability">Missing server capability</string>
|
||||
<string name="account_server_validation_error_server_message">The server returned the following message:\n%s</string>
|
||||
<string name="account_server_validation_error_details">Details:\n%s</string>
|
||||
<string name="account_server_validation_error_missing_server_capability_details">The server is missing this capability:\n%s</string>
|
||||
<string name="account_server_validation_incoming_loading_message">Checking incoming server settings…</string>
|
||||
<string name="account_server_validation_incoming_loading_error">Checking incoming server settings failed</string>
|
||||
<string name="account_server_validation_incoming_success">Incoming server settings are valid</string>
|
||||
|
|
|
@ -23,6 +23,21 @@ sealed interface ServerSettingsValidationResult {
|
|||
*/
|
||||
data class CertificateError(val certificateChain: List<X509Certificate>) : ServerSettingsValidationResult
|
||||
|
||||
/**
|
||||
* There's a problem with the client certificate.
|
||||
*/
|
||||
sealed interface ClientCertificateError : ServerSettingsValidationResult {
|
||||
/**
|
||||
* The client certificate couldn't be retrieved.
|
||||
*/
|
||||
data object ClientCertificateRetrievalFailure : ClientCertificateError
|
||||
|
||||
/**
|
||||
* The client certificate (or another one in the chain) has expired.
|
||||
*/
|
||||
data object ClientCertificateExpired : ClientCertificateError
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication failed while checking the server settings.
|
||||
*/
|
||||
|
@ -33,6 +48,11 @@ sealed interface ServerSettingsValidationResult {
|
|||
*/
|
||||
data class ServerError(val serverMessage: String?) : ServerSettingsValidationResult
|
||||
|
||||
/**
|
||||
* The server is missing a capability that is required by the current server settings.
|
||||
*/
|
||||
data class MissingServerCapabilityError(val capabilityName: String) : ServerSettingsValidationResult
|
||||
|
||||
/**
|
||||
* An unknown error occurred while checking the server settings.
|
||||
*/
|
||||
|
|
|
@ -2,12 +2,17 @@ package com.fsck.k9.mail.store.imap
|
|||
|
||||
import com.fsck.k9.mail.AuthenticationFailedException
|
||||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.ClientCertificateError.CertificateExpired
|
||||
import com.fsck.k9.mail.ClientCertificateError.RetrievalFailure
|
||||
import com.fsck.k9.mail.ClientCertificateException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.MissingCapabilityException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult.ClientCertificateError
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.io.IOException
|
||||
|
@ -42,6 +47,13 @@ class ImapServerSettingsValidator(
|
|||
ServerSettingsValidationResult.CertificateError(e.certificateChain)
|
||||
} catch (e: NegativeImapResponseException) {
|
||||
ServerSettingsValidationResult.ServerError(e.responseText)
|
||||
} catch (e: MissingCapabilityException) {
|
||||
ServerSettingsValidationResult.MissingServerCapabilityError(e.capabilityName)
|
||||
} catch (e: ClientCertificateException) {
|
||||
when (e.error) {
|
||||
RetrievalFailure -> ClientCertificateError.ClientCertificateRetrievalFailure
|
||||
CertificateExpired -> ClientCertificateError.ClientCertificateExpired
|
||||
}
|
||||
} catch (e: MessagingException) {
|
||||
val cause = e.cause
|
||||
if (cause is IOException) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import assertk.assertions.isEqualTo
|
|||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.ClientCertificateError
|
||||
import com.fsck.k9.mail.ConnectionSecurity
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.helpers.FakeTrustManager
|
||||
|
@ -215,6 +216,96 @@ class ImapServerSettingsValidatorTest {
|
|||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing capability should return MissingServerCapabilityError`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.RetrievalFailure)
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
expect("1 CAPABILITY")
|
||||
output("* CAPABILITY IMAP4rev1")
|
||||
output("1 OK CAPABILITY Completed")
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "imap",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.MissingServerCapabilityError>()
|
||||
.prop(ServerSettingsValidationResult.MissingServerCapabilityError::capabilityName).isEqualTo("STARTTLS")
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate retrieval failure connect should return ClientCertificateRetrievalFailure`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.RetrievalFailure)
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
expect("1 CAPABILITY")
|
||||
output("* CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS")
|
||||
output("1 OK CAPABILITY Completed")
|
||||
expect("2 STARTTLS")
|
||||
output("2 OK Begin TLS negotiation now")
|
||||
startTls()
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "imap",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateRetrievalFailure>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate expired should return ClientCertificateExpired`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.CertificateExpired)
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
expect("1 CAPABILITY")
|
||||
output("* CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS")
|
||||
output("1 OK CAPABILITY Completed")
|
||||
expect("2 STARTTLS")
|
||||
output("2 OK Begin TLS negotiation now")
|
||||
startTls()
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "imap",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateExpired>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non-existent hostname should return NetworkError`() {
|
||||
val serverSettings = ServerSettings(
|
||||
|
|
|
@ -2,10 +2,15 @@ package com.fsck.k9.mail.store.pop3
|
|||
|
||||
import com.fsck.k9.mail.AuthenticationFailedException
|
||||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.ClientCertificateError.CertificateExpired
|
||||
import com.fsck.k9.mail.ClientCertificateError.RetrievalFailure
|
||||
import com.fsck.k9.mail.ClientCertificateException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.MissingCapabilityException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult.ClientCertificateError
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.io.IOException
|
||||
|
@ -31,6 +36,13 @@ class Pop3ServerSettingsValidator(
|
|||
ServerSettingsValidationResult.CertificateError(e.certificateChain)
|
||||
} catch (e: Pop3ErrorResponse) {
|
||||
ServerSettingsValidationResult.ServerError(e.responseText)
|
||||
} catch (e: MissingCapabilityException) {
|
||||
ServerSettingsValidationResult.MissingServerCapabilityError(e.capabilityName)
|
||||
} catch (e: ClientCertificateException) {
|
||||
when (e.error) {
|
||||
RetrievalFailure -> ClientCertificateError.ClientCertificateRetrievalFailure
|
||||
CertificateExpired -> ClientCertificateError.ClientCertificateExpired
|
||||
}
|
||||
} catch (e: MessagingException) {
|
||||
val cause = e.cause
|
||||
if (cause is IOException) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import assertk.assertions.isEqualTo
|
|||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.ClientCertificateError
|
||||
import com.fsck.k9.mail.ConnectionSecurity
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.helpers.FakeTrustManager
|
||||
|
@ -125,6 +126,99 @@ class Pop3ServerSettingsValidatorTest {
|
|||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing capability should return MissingServerCapabilityError`() {
|
||||
val server = startServer {
|
||||
output("+OK POP3 server greeting")
|
||||
expect("CAPA")
|
||||
output("+OK Listing of supported mechanisms follows")
|
||||
output(".")
|
||||
expect("QUIT")
|
||||
closeConnection()
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "pop3",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.MissingServerCapabilityError>()
|
||||
.prop(ServerSettingsValidationResult.MissingServerCapabilityError::capabilityName).isEqualTo("STLS")
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate retrieval failure should return ClientCertificateRetrievalFailure`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.RetrievalFailure)
|
||||
val server = startServer {
|
||||
output("+OK POP3 server greeting")
|
||||
expect("CAPA")
|
||||
output("+OK Listing of supported mechanisms follows")
|
||||
output("STLS")
|
||||
output(".")
|
||||
expect("STLS")
|
||||
output("+OK Begin TLS negotiation")
|
||||
startTls()
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "pop3",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateRetrievalFailure>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate expired error should return ClientCertificateExpired`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.CertificateExpired)
|
||||
val server = startServer {
|
||||
output("+OK POP3 server greeting")
|
||||
expect("CAPA")
|
||||
output("+OK Listing of supported mechanisms follows")
|
||||
output("STLS")
|
||||
output(".")
|
||||
expect("STLS")
|
||||
output("+OK Begin TLS negotiation")
|
||||
startTls()
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "pop3",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateExpired>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `certificate error when trying to connect should return CertificateError`() {
|
||||
fakeTrustManager.shouldThrowException = true
|
||||
|
|
|
@ -2,12 +2,17 @@ package com.fsck.k9.mail.transport.smtp
|
|||
|
||||
import com.fsck.k9.mail.AuthenticationFailedException
|
||||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.ClientCertificateError.CertificateExpired
|
||||
import com.fsck.k9.mail.ClientCertificateError.RetrievalFailure
|
||||
import com.fsck.k9.mail.ClientCertificateException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.MissingCapabilityException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult.ClientCertificateError
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.io.IOException
|
||||
|
@ -35,6 +40,13 @@ class SmtpServerSettingsValidator(
|
|||
ServerSettingsValidationResult.CertificateError(e.certificateChain)
|
||||
} catch (e: NegativeSmtpReplyException) {
|
||||
ServerSettingsValidationResult.ServerError(e.replyText)
|
||||
} catch (e: MissingCapabilityException) {
|
||||
ServerSettingsValidationResult.MissingServerCapabilityError(e.capabilityName)
|
||||
} catch (e: ClientCertificateException) {
|
||||
when (e.error) {
|
||||
RetrievalFailure -> ClientCertificateError.ClientCertificateRetrievalFailure
|
||||
CertificateExpired -> ClientCertificateError.ClientCertificateExpired
|
||||
}
|
||||
} catch (e: MessagingException) {
|
||||
val cause = e.cause
|
||||
if (cause is IOException) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import assertk.assertions.isEqualTo
|
|||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.ClientCertificateError
|
||||
import com.fsck.k9.mail.ConnectionSecurity
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.helpers.FakeTrustManager
|
||||
|
@ -165,6 +166,102 @@ class SmtpServerSettingsValidatorTest {
|
|||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `missing capability should return MissingServerCapabilityError`() {
|
||||
val server = MockSmtpServer().apply {
|
||||
output("220 localhost Simple Mail Transfer Service Ready")
|
||||
expect("EHLO [127.0.0.1]")
|
||||
output("250-localhost Hello 127.0.0.1")
|
||||
output("250 HELP")
|
||||
expect("QUIT")
|
||||
closeConnection()
|
||||
}
|
||||
server.start()
|
||||
val serverSettings = ServerSettings(
|
||||
type = "smtp",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.MissingServerCapabilityError>()
|
||||
.prop(ServerSettingsValidationResult.MissingServerCapabilityError::capabilityName).isEqualTo("STARTTLS")
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate retrieval failure should return ClientCertificateRetrievalFailure`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.RetrievalFailure)
|
||||
val server = MockSmtpServer().apply {
|
||||
output("220 localhost Simple Mail Transfer Service Ready")
|
||||
expect("EHLO [127.0.0.1]")
|
||||
output("250-localhost Hello 127.0.0.1")
|
||||
output("250-STARTTLS")
|
||||
output("250 HELP")
|
||||
expect("STARTTLS")
|
||||
output("220 Ready to start TLS")
|
||||
startTls()
|
||||
}
|
||||
server.start()
|
||||
val serverSettings = ServerSettings(
|
||||
type = "smtp",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateRetrievalFailure>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `client certificate expired error should return ClientCertificateExpired`() {
|
||||
trustedSocketFactory.injectClientCertificateError(ClientCertificateError.CertificateExpired)
|
||||
val server = MockSmtpServer().apply {
|
||||
output("220 localhost Simple Mail Transfer Service Ready")
|
||||
expect("EHLO [127.0.0.1]")
|
||||
output("250-localhost Hello 127.0.0.1")
|
||||
output("250-STARTTLS")
|
||||
output("250 HELP")
|
||||
expect("STARTTLS")
|
||||
output("220 Ready to start TLS")
|
||||
startTls()
|
||||
}
|
||||
server.start()
|
||||
val serverSettings = ServerSettings(
|
||||
type = "smtp",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result)
|
||||
.isInstanceOf<ServerSettingsValidationResult.ClientCertificateError.ClientCertificateExpired>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `certificate error when trying to connect should return CertificateError`() {
|
||||
fakeTrustManager.shouldThrowException = true
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.fsck.k9.mail.helpers
|
||||
|
||||
import com.fsck.k9.mail.ClientCertificateError
|
||||
import com.fsck.k9.mail.ClientCertificateException
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.SSLContext
|
||||
|
@ -7,9 +9,18 @@ import javax.net.ssl.TrustManager
|
|||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
class SimpleTrustedSocketFactory(private val trustManager: X509TrustManager) : TrustedSocketFactory {
|
||||
private var clientCertificateError: ClientCertificateError? = null
|
||||
|
||||
override fun createSocket(socket: Socket?, host: String, port: Int, clientCertificateAlias: String?): Socket {
|
||||
requireNotNull(socket)
|
||||
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
when (val error = clientCertificateError) {
|
||||
ClientCertificateError.RetrievalFailure -> throw ClientCertificateException(error, RuntimeException())
|
||||
ClientCertificateError.CertificateExpired -> throw ClientCertificateException(error, RuntimeException())
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
val trustManagers = arrayOf<TrustManager>(trustManager)
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
|
@ -23,4 +34,8 @@ class SimpleTrustedSocketFactory(private val trustManager: X509TrustManager) : T
|
|||
true,
|
||||
)
|
||||
}
|
||||
|
||||
fun injectClientCertificateError(error: ClientCertificateError) {
|
||||
clientCertificateError = error
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue