From c5b9d192e96342505ac5ba2bb2b7746ca006aae9 Mon Sep 17 00:00:00 2001 From: slisson Date: Thu, 19 Dec 2024 18:25:29 +0100 Subject: [PATCH 1/2] fix(authorization): existing access control data couldn't be read --- .../modelix/authorization/permissions/AccessControlData.kt | 6 +++--- .../org/modelix/model/server/DBAccessControlPersistence.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/authorization/src/main/kotlin/org/modelix/authorization/permissions/AccessControlData.kt b/authorization/src/main/kotlin/org/modelix/authorization/permissions/AccessControlData.kt index 1d06e14248..e65fbfa55c 100644 --- a/authorization/src/main/kotlin/org/modelix/authorization/permissions/AccessControlData.kt +++ b/authorization/src/main/kotlin/org/modelix/authorization/permissions/AccessControlData.kt @@ -110,9 +110,9 @@ fun IAccessControlPersistence.recordKnownRoles(roles: List) { } class FileSystemAccessControlPersistence(val file: File) : IAccessControlPersistence { - + private val json = Json { ignoreUnknownKeys = true } private var data: AccessControlData = if (file.exists()) { - Json.decodeFromString(file.readText()) + json.decodeFromString(file.readText()) } else { AccessControlData() }.withLegacyRoles() @@ -131,7 +131,7 @@ class FileSystemAccessControlPersistence(val file: File) : IAccessControlPersist } private fun writeFile() { - file.writeText(Json.encodeToString(data)) + file.writeText(json.encodeToString(data)) } } diff --git a/model-server/src/main/kotlin/org/modelix/model/server/DBAccessControlPersistence.kt b/model-server/src/main/kotlin/org/modelix/model/server/DBAccessControlPersistence.kt index 58364ff1ce..68085e4e52 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/DBAccessControlPersistence.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/DBAccessControlPersistence.kt @@ -8,7 +8,7 @@ import org.modelix.model.server.store.IGenericStoreClient import org.modelix.model.server.store.RequiresTransaction class DBAccessControlPersistence(val store: IGenericStoreClient, val key: E) : IAccessControlPersistence { - private val json = Json { ignoreUnknownKeys } + private val json = Json { ignoreUnknownKeys = true } override fun read(): AccessControlData { @OptIn(RequiresTransaction::class) return store.runReadTransaction { From 0ba1639257e6571f9fe9348135f326fce6c06c32 Mon Sep 17 00:00:00 2001 From: slisson Date: Thu, 19 Dec 2024 18:28:11 +0100 Subject: [PATCH 2/2] fix(authorization): verification against private keys from disk failed --- .../modelix/authorization/ModelixJWTUtil.kt | 27 +++++++++++- .../org/modelix/authorization/RSATest.kt | 43 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/authorization/src/main/kotlin/org/modelix/authorization/ModelixJWTUtil.kt b/authorization/src/main/kotlin/org/modelix/authorization/ModelixJWTUtil.kt index 1145e7a296..6dc5f3d9e3 100644 --- a/authorization/src/main/kotlin/org/modelix/authorization/ModelixJWTUtil.kt +++ b/authorization/src/main/kotlin/org/modelix/authorization/ModelixJWTUtil.kt @@ -6,6 +6,7 @@ import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.JWSObject import com.nimbusds.jose.JWSSigner import com.nimbusds.jose.JWSVerifier +import com.nimbusds.jose.KeySourceException import com.nimbusds.jose.crypto.MACSigner import com.nimbusds.jose.crypto.RSASSASigner import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory @@ -69,7 +70,11 @@ class ModelixJWTUtil { val keySelectors: List> = hmacKeys.map { it.toPair() }.map { SingleKeyJWSKeySelector(it.first, SecretKeySpec(it.second, it.first.name)) } + jwkSources.map { - JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(it) + try { + JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(it) + } catch (ex: KeySourceException) { + throw KeySourceException("Couldn't retrieve JWKs from $it", ex) + } } processor.jwsKeySelector = if (keySelectors.size == 1) keySelectors.single() else CompositeJWSKeySelector(keySelectors) @@ -328,6 +333,10 @@ class ModelixJWTUtil { override fun readFile(): JWKSet { return JWKSet(ensureValidKey(JWK.parseFromPEMEncodedObjects(file.readText()))) } + + override fun toString(): String { + return "PemFileJWKSet[$file]" + } } private open inner class FileJWKSet(val file: File) : JWKSource { @@ -340,12 +349,26 @@ class ModelixJWTUtil { override fun get(jwkSelector: JWKSelector, context: C?): List? { val jwks = cached.takeIf { System.currentTimeMillis() - loadedAt < fileRefreshTime.inWholeMilliseconds } - ?: readFile().also { + ?: readFile().let { jwks -> + JWKSet( + jwks.keys.flatMap { key -> + if (key.isPrivate) { + listOf(key, key.toPublicJWK()) + } else { + listOf(key) + } + }, + ) + }.also { cached = it loadedAt = System.currentTimeMillis() } return jwkSelector.select(jwks) } + + override fun toString(): String { + return "FileJWKSet[$file]" + } } companion object { diff --git a/authorization/src/test/kotlin/org/modelix/authorization/RSATest.kt b/authorization/src/test/kotlin/org/modelix/authorization/RSATest.kt index da71031885..32732af9e7 100644 --- a/authorization/src/test/kotlin/org/modelix/authorization/RSATest.kt +++ b/authorization/src/test/kotlin/org/modelix/authorization/RSATest.kt @@ -318,4 +318,47 @@ class RSATest { verifyingUtil.verifyToken(tokenString) } } + + @Test + fun `can verify own tokens`() { + val privateKeyPem1 = """ + -----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEAyB+2c/hRX7lhcTKHOom13F7dVnujy1XndcYp4y42NIxRZDui + mOU/inkH6tJsclIftPeYSWnSTWRc5ZG268pRMjD6rMCxCTyo1S7VGuXtdPbfL1ma + kCYfpKALBZdLgrYVkor49CP2cBdKPldYUT7+EpqFxXkaeL073bS3vPPdxN/riuYu + 3df3tLe9+st6Tr6+rv1+HK+dRegPok8ryMOogT96QyF7ygLDQ1WW/v/CZI5y+jW1 + xEpWnHRkRqHWTtIMjWN6WK+ez1kg4tlQDWmMn4bywmTPRs38weLEMnTUrjfrOxOc + 59rWOyE7b186RrDf1F1ezLiVUlLA9L7ThydM3QIDAQABAoIBAEXspsCgrDYpPP3j + bNKsWWn1j5rvOo0KqARDyFEDzZbQzIOcPrTzrR8CKR0IhzHutftyY7iLDBtUjQz9 + vA9pMrO532zLK1CR7GAIrBdo7W5n8BXIVjQ1zeqkrRU4Bv9WBfWdL12Gz03dJWjg + 9g/1VatEaKdWKES1whw2T9jq0Ls/7/uRTtL31g6SnI/UW5RnZe4TQhNtnTltts6T + eHUU7MjKIlB4VQrHx8up/QdsMIvXihv72jm374nZe6U3e8HmuGb71qXA4YPFju5c + Aict16PVNUTb2ZAylH33NB0k1LlHaCbkQM+Cy3jhhtb1XERXt7tDyS/hiC++HG6b + jlAvqzUCgYEA27OjEbEbw60ca9goC/mafZoDofZWA3aNI+TR15EsFAYQHtoE4DLy + Nrlm0syqqJJwf117jLhu+KpKrJtb36XqfUqnwwISAilnr6OnPT47qs8dbrRIxnap + COh9yw0YerLFPuJ9HTPZMCWs7ufDcXJyuRfjL25lq/kv7jGD6jHRvnMCgYEA6TAG + PK/OyIizT4OtdzNbejQ7W+9wi4tfhjF8OMmgQb6kpsmSmhoaFCQ5SAg9MwqbL2q1 + 3XSEkPXljONqWmkQZ/2Eo4WHveOKoKj/07LiRucs5jjHyr5pea80z5lTnE8i7MJX + eNSTqi3b9WnV0J0EHhg7qgAbH/q+c5gtiqgkI28CgYB9z0ONSQdmKUaCNzjPirK+ + RCjaYW7l8shmCo1jzT0ZhlNK53wtSt9LGSZZhlwfxiPnu4eZkK/zc8jpSNn2m1NJ + RiwFTrUzSbSXbrbBKlcOvCXVlCWsiJzJfiEy2p/u+1paZWZSB7PSj3CVKmDQIUKy + 3Yv6SFSugzbARtiMjtTWIwKBgGFKDyAcvap/FkjTiHkWLVFkH2vxD0S5RoaHeOt8 + e+dSMgIAUbEHuN+0aU27WkVEZJC49d3KclDEtxw7+bB060pnxIIxAPxhxgHX4Lyj + grLQWrRG9lyJaxpA1kjTEMZDYi/juXkJP/6dmYrfuDyMdh5UP/hiiO6jv/gcgsu5 + 8THzAoGAUGCnccd4JAXK3/rmkCLT++M18G6ik+qaTMdhGnC9opTDWDvxWF6jHj7w + 4/wol7RQf0qmWZr6sSg+dg/cEOvAxBDiayl7WALnEpGhh2+aKkDVIy7JSTOm3fkO + P1Z2sotIDXrYJrdKl/BvWh80ifVYjHp9J/cOhMSyj/HCMhxexhY= + -----END RSA PRIVATE KEY----- + """.trimIndent().trim() + + val privateKeyFile = File.createTempFile("modelix_rsa_test", ".pem") + privateKeyFile.deleteOnExit() + privateKeyFile.writeText(privateKeyPem1) + + val util = ModelixJWTUtil() + util.loadKeysFromFiles(privateKeyFile) + + val tokenString = util.createAccessToken("units-test@example.com", listOf()) + util.verifyToken(tokenString) + } }