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

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

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

import okio.Buffer
import okio.BufferedSource
import okio.ByteString
import okio.ByteString.Companion.decodeBase64
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import java.io.IOException
import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.spec.KeySpec
import java.security.spec.RSAPrivateCrtKeySpec

/**
 * A file containing a mix of PEM-encoded certificates and PEM-encoded private
 * keys. Can be used both for trust stores (which certificate authorities a TLS
 * client trusts) and also for TLS servers (which certificate chain a TLS server
 * serves).
 */
data class PemComboFile(
    val certificates: List,
    val privateRsaKeys: List,
    val privateKeys: List,
    val passphrase: String
) {
    fun newEmptyKeyStore(): KeyStore {
        val password = passphrase.toCharArray() // Any password will work.

        val result = KeyStore.getInstance(KeyStore.getDefaultType())
        result.load(null, password) // By convention, null input creates a new empty store
        return result
    }

    fun decodeCertificates(): List {
        val certificateFactory = CertificateFactory.getInstance("X.509")
        return certificates.map {
            certificateFactory.generateCertificate(Buffer().write(it).inputStream())
        }
    }

    companion object {
        fun parse(certKeyComboSource: BufferedSource, passphrase: String? = null): PemComboFile {
            val certificates = mutableListOf()
            val privateRsaKeys = mutableListOf()
            val privateKeys = mutableListOf()

            val lines = certKeyComboSource.lines().iterator()
            while (lines.hasNext()) {
                val line = lines.next()

                when {
                    line.matches(Regex("-+BEGIN CERTIFICATE-+")) -> {
                        certificates += decodeBase64Until(
                            lines,
                            Regex("-+END CERTIFICATE-+")
                        )
                    }
                    line.matches(Regex("-+BEGIN RSA PRIVATE KEY-+")) -> {
                        privateRsaKeys += decodeBase64Until(
                            lines,
                            Regex("-+END RSA PRIVATE KEY-+")
                        )
                    }
                    line.matches(Regex("-+BEGIN PRIVATE KEY-+")) -> {
                        privateKeys += decodeBase64Until(
                            lines,
                            Regex("-+END PRIVATE KEY-+")
                        )
                    }

                    // Ignore everything else
                }
            }

            return PemComboFile(
                certificates, privateRsaKeys, privateKeys,
                passphrase ?: "password"
            )
        }

        fun convertPKCS1toPKCS8(pkcs1Key: ByteString): KeySpec {
            val keyObject = ASN1Sequence.fromByteArray(pkcs1Key.toByteArray())
            val rsaPrivateKey = RSAPrivateKey.getInstance(keyObject)

            return RSAPrivateCrtKeySpec(
                rsaPrivateKey.modulus,
                rsaPrivateKey.publicExponent,
                rsaPrivateKey.privateExponent,
                rsaPrivateKey.prime1,
                rsaPrivateKey.prime2,
                rsaPrivateKey.exponent1,
                rsaPrivateKey.exponent2,
                rsaPrivateKey.coefficient
            )
        }

        private fun BufferedSource.lines(): List {
            use {
                val result = mutableListOf()
                while (true) {
                    val line = readUtf8Line() ?: break
                    result.add(line)
                }
                return result
            }
        }

        private fun decodeBase64Until(lines: Iterator, until: Regex): ByteString {
            val result = Buffer()

            while (true) {
                if (!lines.hasNext()) throw IOException("$until not found")

                val line = lines.next()
                if (line.matches(until)) break
                if (line.isEmpty()) continue
                result.writeUtf8(line)
            }

            return result.readUtf8().decodeBase64() ?: throw IOException("malformed base64")
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy