All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.wisp.security.ssl.SslLoader.kt Maven / Gradle / Ivy

There is a newer version: 2024.12.12.153725-b454031
Show newest version
package wisp.security.ssl

import wisp.resources.ResourceLoader
import java.io.IOException
import java.security.KeyFactory
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.security.spec.PKCS8EncodedKeySpec

/** Loads keys and certificates from the file system. */
open class SslLoader constructor(
    private val resourceLoader: ResourceLoader
) {
    @JvmOverloads
    fun loadTrustStore(
        path: String,
        format: String = FORMAT_PEM,
        passphrase: String? = null
    ): TrustStore? {
        return when (format) {
            FORMAT_PEM -> {
                // Load a TrustStore from a combined PEM file, returning null in the event that it contains
                // a certificate chain and no private keys.
                load(path, passphrase).toTrustStore()
            }
            FORMAT_JCEKS, FORMAT_JKS, FORMAT_PKCS12 -> {
                // TODO(young): This function should check that the underlying keystore complies with what
                // TrustStore provides.
                TrustStore(loadJavaKeystore(path, format, passphrase))
            }
            else -> throw IllegalArgumentException(format)
        }
    }

    private fun load(cert_key_combo: String, passphrase: String? = null): PemComboFile {
        val source = resourceLoader.open(cert_key_combo)
            ?: throw IllegalArgumentException("no such resource $cert_key_combo")
        source.use {
            return PemComboFile.parse(source, passphrase)
        }
    }

    fun loadTrustStore(config: TrustStoreConfig) = loadTrustStore(
        config.resource, config.format,
        config.passphrase
    )

    private fun PemComboFile.toTrustStore(): TrustStore? {
        if (!privateKeys.isEmpty() || !privateRsaKeys.isEmpty()) return null

        val keyStore = newEmptyKeyStore()
        decodeCertificates().forEachIndexed { index, certificate ->
            keyStore.setCertificateEntry(index.toString(), certificate)
        }
        return TrustStore(keyStore)
    }

    @JvmOverloads
    fun loadCertStore(
        path: String,
        format: String = FORMAT_PEM,
        passphrase: String? = null
    ): CertStore? {
        return when (format) {
            FORMAT_PEM -> {
                // Load a CertStore from a combined PEM file, returning null in the event that it doesn't
                // contain a single private key and a cert chain.
                load(path, passphrase).toCertStore()
            }
            FORMAT_JCEKS, FORMAT_JKS, FORMAT_PKCS12 -> {
                // TODO(young): This function should check that the underlying keystore complies with what
                // CertStore provides.
                CertStore(loadJavaKeystore(path, format, passphrase))
            }
            else -> throw IllegalArgumentException(format)
        }
    }

    fun loadCertStore(config: CertStoreConfig) =
        loadCertStore(config.resource, config.format, config.passphrase)

    private fun PemComboFile.toCertStore(): CertStore? {
        if (certificates.isEmpty() || privateRsaKeys.size + privateKeys.size != 1) return null

        val keyStore = newEmptyKeyStore()
        val privateKeySpec = if (privateKeys.isEmpty()) PemComboFile.convertPKCS1toPKCS8(
            privateRsaKeys[0]
        )
        else PKCS8EncodedKeySpec(privateKeys[0].toByteArray())
        val keyFactory = KeyFactory.getInstance("RSA")
        val privateKey = keyFactory.generatePrivate(privateKeySpec)
        keyStore.setKeyEntry(
            "key", privateKey, passphrase.toCharArray(),
            decodeCertificates().toTypedArray()
        )
        return CertStore(keyStore)
    }

    private fun loadJavaKeystore(path: String, type: String, passphrase: String? = null): KeyStore {
        val source = resourceLoader.open(path)
            ?: throw IllegalArgumentException("no such resource $path")
        source.use {
            val keystore = try {
                KeyStore.getInstance(type)
            } catch (e: KeyStoreException) {
                throw IllegalStateException("no provider exists for the keystore type $type", e)
            } catch (e: IllegalAccessException) {
                throw IllegalStateException("no provider exists for the keystore type $type", e)
            } catch (e: ClassNotFoundException) {
                throw IllegalStateException("no provider exists for the keystore type $type", e)
            }

            try {
                keystore.load(source.inputStream(), passphrase?.toCharArray())
            } catch (e: CertificateException) {
                throw IllegalStateException("some certificates could not be loaded", e)
            } catch (e: NoSuchAlgorithmException) {
                throw IllegalStateException("integrity check algorithm is unavailable", e)
            } catch (e: IOException) {
                throw IllegalArgumentException("I/O error or a bad password", e)
            }

            return keystore
        }
    }

    companion object {
        const val FORMAT_PEM = "PEM"
        const val FORMAT_JCEKS = "JCEKS"
        const val FORMAT_JKS = "JKS"
        const val FORMAT_PKCS12 = "PKCS12"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy