Skip to content

Commit

Permalink
feat: use pkcs12 keystore for ssl
Browse files Browse the repository at this point in the history
  • Loading branch information
lost-illusi0n committed Nov 26, 2023
1 parent 994dd33 commit 8c68fe3
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 65 deletions.
9 changes: 8 additions & 1 deletion GENERATING_CERT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Generating a certificate
# Keystore
Kmail uses a PKCS12 keystore to store certificates. Given a certificate and key (e.g, from CloudFlare), a keystore can be generated using the following command:
```bash
openssl pkcs12 -export -inkey server.key -in server.pem -out server.p12
```
Configure the keystore to be used in [the configuration file](kmail.toml).

## Self-Signed (outdated)
To run a secure mail server, a certificate for the domain the mail server is handling is required. For this example, we wil generate a self-signed certificate.
```
openssl req -x509 -newkey rsa:4096 -keyout private.pem -out xgophers.crt -sha256 -days 365 -nodes
Expand Down
4 changes: 1 addition & 3 deletions mailserver/runner/src/main/kotlin/config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ data class KmailConfig(
}
}

data class Security(val certificates: List<CertificateAndKey>) {
data class CertificateAndKey(val certificate: String, val key: String)
}
data class Security(val keystore: String, val password: String)
data class Smtp(
val submission: Submission,
val transfer: Transfer
Expand Down
5 changes: 5 additions & 0 deletions mailserver/runner/src/main/kotlin/launcherJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package dev.sitar.kmail.runner
import dev.sitar.kmail.utils.connection.TlsCapableConnectionFactory
import dev.sitar.kmail.utils.server.TlsCapableServerSocketFactory
import kotlinx.coroutines.coroutineScope
import mu.KotlinLogging

private val logger = KotlinLogging.logger { }

suspend fun main() {
logger.info { "Using JVM version: ${Runtime.version()}" }

dns()

// System.setProperty("javax.net.debug", "all")
Expand Down
71 changes: 10 additions & 61 deletions mailserver/runner/src/main/kotlin/sslJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.sitar.kmail.runner

import mu.KotlinLogging
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.security.KeyFactory
import java.security.KeyStore
Expand All @@ -10,81 +11,29 @@ import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.spec.PKCS8EncodedKeySpec
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import javax.net.ssl.*


private val logger = KotlinLogging.logger { }

private fun InputStream.load(): Array<Certificate> {
logger.debug { "Loading certificates from input stream." }
val factory = CertificateFactory.getInstance("X.509")

return buildList {
while (available() > 0) {
val cert = factory.generateCertificate(this@load)
add(cert)
logger.debug { "Generated a certificate." }
logger.trace { cert }
}
}.toTypedArray()
}

fun ssl(): Pair<SSLContext, KeyStore> {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)

Config.security.certificates.forEach { (certPath, key) ->
FileInputStream(certPath).use { certStream ->
certStream.load().apply {
forEachIndexed { i, cert ->
keyStore.setCertificateEntry(certPath, cert)
}

keyStore.setKeyEntry("private",
KeyFactory.getInstance("RSA")
.generatePrivate(PKCS8EncodedKeySpec(FileInputStream(key).readAllBytes())),
null,
this
)
}
}
}
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(FileInputStream(Config.security.keystore), Config.security.password.toCharArray())

logger.debug { "Generating SSL context." }
val ssl = SSLContext.getInstance("TLS")

val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, null)
keyManagerFactory.init(keyStore, Config.security.password.toCharArray())

keyStore.aliases().iterator().forEach {
logger.debug { "got certificate\n${keyStore.getCertificate(it)}"}
}

val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStore)

val mine = trustManagerFactory.trustManagers.filterIsInstance<X509TrustManager>().first()
val default = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).also { it.init(null as KeyStore?) }.trustManagers.filterIsInstance<X509TrustManager>().first()

val trustManager = object: X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
default.checkClientTrusted(chain, authType)
}

override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
try {
mine.checkServerTrusted(chain, authType)
} catch (e: CertificateException) {
default.checkServerTrusted(chain, authType)
}
}

override fun getAcceptedIssuers(): Array<X509Certificate> {
return default.acceptedIssuers
}

}

ssl.init(keyManagerFactory.keyManagers, arrayOf(trustManager), null)
ssl.init(keyManagerFactory.keyManagers, trustManagerFactory.trustManagers, null)

logger.debug { "Generated SSL context." }

Expand Down

0 comments on commit 8c68fe3

Please sign in to comment.