From e608764f5c409672e85d0e67949df551c413c755 Mon Sep 17 00:00:00 2001 From: Swordsteel Date: Fri, 22 Nov 2024 13:21:30 +0100 Subject: [PATCH] add PrivateKeyProvider --- .editorconfig | 4 ++ .../hlaeja/exception/KeyProviderException.kt | 23 +++++++++ .../ltd/hlaeja/util/PrivateKeyProvider.kt | 35 +++++++++++++ .../ltd/hlaeja/util/PrivateKeyProviderTest.kt | 51 +++++++++++++++++++ .../resources/keys/invalid-private-key.pem | 28 ++++++++++ src/test/resources/keys/valid-private-key.pem | 28 ++++++++++ 6 files changed, 169 insertions(+) create mode 100644 src/main/kotlin/ltd/hlaeja/exception/KeyProviderException.kt create mode 100644 src/main/kotlin/ltd/hlaeja/util/PrivateKeyProvider.kt create mode 100644 src/test/kotlin/ltd/hlaeja/util/PrivateKeyProviderTest.kt create mode 100644 src/test/resources/keys/invalid-private-key.pem create mode 100644 src/test/resources/keys/valid-private-key.pem diff --git a/.editorconfig b/.editorconfig index 7e75b00..fc8945f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,10 @@ tab_width = 2 [*.bat] end_of_line = crlf +[*.pem] +max_line_length = 64 +insert_final_newline = false + # noinspection EditorConfigKeyCorrectness [*.{kt,kts}] ij_kotlin_packages_to_use_import_on_demand = unset diff --git a/src/main/kotlin/ltd/hlaeja/exception/KeyProviderException.kt b/src/main/kotlin/ltd/hlaeja/exception/KeyProviderException.kt new file mode 100644 index 0000000..7d9473b --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/exception/KeyProviderException.kt @@ -0,0 +1,23 @@ +package ltd.hlaeja.exception + +@Suppress("unused") +class KeyProviderException : RuntimeException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor( + message: String, + cause: Throwable, + ) : super(message, cause) + + constructor( + message: String, + cause: Throwable, + enableSuppression: Boolean, + writableStackTrace: Boolean, + ) : super(message, cause, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/ltd/hlaeja/util/PrivateKeyProvider.kt b/src/main/kotlin/ltd/hlaeja/util/PrivateKeyProvider.kt new file mode 100644 index 0000000..4454af9 --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/util/PrivateKeyProvider.kt @@ -0,0 +1,35 @@ +package ltd.hlaeja.util + +import java.security.KeyFactory +import java.security.interfaces.RSAPrivateKey +import java.security.spec.PKCS8EncodedKeySpec +import java.util.Base64.getDecoder +import ltd.hlaeja.exception.KeyProviderException + +object PrivateKeyProvider { + + fun load( + pemFile: String, + ): RSAPrivateKey = readPrivatePemFile(pemFile) + .let(::makePrivateKey) + + private fun makePrivateKey( + privateKeyBytes: ByteArray, + ): RSAPrivateKey = KeyFactory.getInstance("RSA") + .generatePrivate(PKCS8EncodedKeySpec(privateKeyBytes)) as RSAPrivateKey + + private fun readPrivatePemFile( + privateKey: String, + ): ByteArray = javaClass.classLoader + .getResource(privateKey) + ?.readText() + ?.let(::getPrivateKeyByteArray) + ?: throw KeyProviderException("Could not load private key") + + private fun getPrivateKeyByteArray( + keyText: String, + ): ByteArray = keyText.replace(Regex("[\r\n]+"), "") + .removePrefix("-----BEGIN PRIVATE KEY-----") + .removeSuffix("-----END PRIVATE KEY-----") + .let { getDecoder().decode(it) } +} diff --git a/src/test/kotlin/ltd/hlaeja/util/PrivateKeyProviderTest.kt b/src/test/kotlin/ltd/hlaeja/util/PrivateKeyProviderTest.kt new file mode 100644 index 0000000..63239da --- /dev/null +++ b/src/test/kotlin/ltd/hlaeja/util/PrivateKeyProviderTest.kt @@ -0,0 +1,51 @@ +package ltd.hlaeja.util + +import java.security.interfaces.RSAPrivateKey +import ltd.hlaeja.exception.KeyProviderException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class PrivateKeyProviderTest { + + @Test + fun `load private key - success`() { + // given + val pemFilePath = "keys/valid-private-key.pem" + + // when + val privateKey: RSAPrivateKey = PrivateKeyProvider.load(pemFilePath) + + // then + assertThat(privateKey).isNotNull + assertThat(privateKey.algorithm).isEqualTo("RSA") + } + + @Test + fun `load private key - file does not exist`() { + // given + val nonExistentPemFilePath = "keys/non-existent.pem" + + // when exception + val exception = assertThrows { + PrivateKeyProvider.load(nonExistentPemFilePath) + } + + // then + assertThat(exception.message).isEqualTo("Could not load private key") + } + + @Test + fun `load private key - file is invalid`() { + // given + val invalidPemFilePath = "keys/invalid-private-key.pem" + + // when exception + val exception = assertThrows { + PrivateKeyProvider.load(invalidPemFilePath) + } + + // then + assertThat(exception.message).contains("Input byte array has wrong 4-byte ending unit") + } +} diff --git a/src/test/resources/keys/invalid-private-key.pem b/src/test/resources/keys/invalid-private-key.pem new file mode 100644 index 0000000..2eb0903 --- /dev/null +++ b/src/test/resources/keys/invalid-private-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +VEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBK +VU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMg +SVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBU +SElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpV +TksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJ +UyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRI +SVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVO +SyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElT +IEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJ +UyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5L +IFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMg +SlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElT +IElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksg +VEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBK +VU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMg +SVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBU +SElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpV +TksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJ +UyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRI +SVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVO +SyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElT +IEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJ +UyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5L +IFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMg +SlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElT +IElTIEpVTksg== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/src/test/resources/keys/valid-private-key.pem b/src/test/resources/keys/valid-private-key.pem new file mode 100644 index 0000000..3314ab6 --- /dev/null +++ b/src/test/resources/keys/valid-private-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj +MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu +NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ +qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg +p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR +ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi +VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV +laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8 +sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H +mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY +dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw +ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ +DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T +N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t +0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv +t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU +AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk +48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL +DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK +xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA +mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh +2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz +et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr +VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD +TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc +dn/RsYEONbwQSjIfMPkvxF+8HQ== +-----END PRIVATE KEY----- \ No newline at end of file