Skip to content

Commit

Permalink
systembruker (#6)
Browse files Browse the repository at this point in the history
Systembruker konfigurasjon
  • Loading branch information
mettok authored Dec 13, 2024
1 parent 14c2984 commit 561c5ba
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 136 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
id("maven-publish")
}
group = "no.nav.helsearbeidsgiver"
version = "0.2.0.9"
version = "0.2.1"

kotlin {
compilerOptions {
Expand Down
147 changes: 15 additions & 132 deletions src/main/kotlin/MaskinportenClientConfig.kt
Original file line number Diff line number Diff line change
@@ -1,145 +1,28 @@
package no.nav.helsearbeidsgiver.maskinporten

import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.time.Instant
import java.util.Base64
import java.util.Date
import java.util.UUID

interface MaskinportenClientConfig {
val scope: String
val clientId: String
val issuer: String
val endpoint: String
val additionalClaims: Map<String, Any>?
fun getJwtAssertion(): String
fun currentTime(): Date = Date.from(Instant.now())
}

/**
* MaskinportenClientConfigPkey er en implementasjon av MaskinportenClientConfig med privatekey som autentiseringsmetode for maskinporten
*
* @param kid Det er id-en til nøkkelen key-id (kid).
* @param privateKey Det er privatekey som skal brukes til å signere JWT tokenet
* @param clientId Din egen client_id. Sendes videre til Maskinporten som en issuer claim.
* @param issuer Issuer-identifikatoren til Maskinporten. 'https://maskinporten.no/' i produksjon. Sendes videre til Maskinporten som en audience claim.
* @param consumerOrgNr Det er organisasjonsnummeret til virksomheten som skal bruke maskinporten på vegne av
* @param scope Space-separert liste over scopes som klienten forespør.
* @param endpoint Det er endepunktet til maskinporten
*
*/
class MaskinportenClientConfigPkey(
val kid: String,
val privateKey: String,
override val clientId: String,
override val issuer: String,
val consumerOrgNr: String,
override val scope: String,
override val endpoint: String
) : MaskinportenClientConfig {

private fun loadPrivateKey(key: String): PrivateKey {
val keyText =
key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
.replace("\\s".toRegex(), "")

val encoded = Base64.getDecoder().decode(keyText)
return KeyFactory
.getInstance("RSA")
.generatePrivate(PKCS8EncodedKeySpec(encoded))
}

override fun getJwtAssertion(): String {
val currentTimestamp = System.currentTimeMillis() / 1000

val header =
JWSHeader
.Builder(JWSAlgorithm.RS256)
.keyID(kid)
.type(JOSEObjectType.JWT)
.build()

val claims =
JWTClaimsSet
.Builder()
.issuer(clientId)
.audience(issuer)
.issueTime(Date(currentTimestamp * 1000))
.expirationTime(Date((currentTimestamp + 60) * 1000))
.claim("scope", scope)
.claim("consumer_org", consumerOrgNr)
.jwtID(UUID.randomUUID().toString())
.build()

val signer = RSASSASigner(loadPrivateKey(privateKey))
val signedJWT = SignedJWT(header, claims)
signedJWT.sign(signer)

return signedJWT.serialize()
}
}

/**
* MaskinportenSimpleAssertion er en implementasjon av MaskinportenClientConfig med assertion som autentiseringsmetode
* for maskinporten Denne brukes for å authentisere mot maskinporten for eksample for Altinn
*
* @param scope Space-separert liste over scopes som klienten forespør.
* @param clientId issuer - Din egen client_id.
* @param issuer Audience - issuer-identifikatoren til Maskinporten. Verdi for aktuelt miljø finner du på .well-known-endpunkt.
* @param endpoint Endepunktet til maskinporten
* @param clientJwk JWK som skal brukes til å signere JWT tokenet
*
*/
class MaskinportenClientConfigSimpleAssertion(
override val scope: String,
override val clientId: String,
override val issuer: String,
override val endpoint: String,
val clientJwk: String
) : MaskinportenClientConfig {

private val rsaKey: RSAKey by lazy {
try {
RSAKey.parse(clientJwk)
} catch (e: Exception) {
throw IllegalArgumentException("Invalid JWK format", e)
}
}
private val signer: RSASSASigner by lazy {
RSASSASigner(rsaKey.toPrivateKey())
}
private val header: JWSHeader by lazy {
JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(rsaKey.keyID)
.type(JOSEObjectType.JWT)
.build()
}

private fun currentTime(): Date = Date.from(Instant.now())

private fun claims(): JWTClaimsSet {
val now = currentTime()
return JWTClaimsSet.Builder()
.issuer(clientId)
.audience(issuer)
.issueTime(now)
.claim("scope", scope)
.expirationTime(Date.from(now.toInstant().plusSeconds(60)))
.jwtID(UUID.randomUUID().toString())
.build()
}

override fun getJwtAssertion(): String {
return SignedJWT(header, claims()).apply { sign(signer) }.serialize()
}
}
fun getConsumerOrgClaim(orgnr: String) = mapOf("consumer_orgno" to orgnr)

fun getSystembrukerClaim(orgNr: String) = mapOf(
"authorization_details" to listOf(
mapOf(
"type" to "urn:altinn:systemuser",
"systemuser_org" to mapOf(
"authority" to "iso6523-actorid-upis",
"ID" to "0192:$orgNr"
)
)
)
)
59 changes: 59 additions & 0 deletions src/main/kotlin/MaskinportenClientConfigJwk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package no.nav.helsearbeidsgiver.maskinporten

import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import java.util.Date
import java.util.UUID

class MaskinportenClientConfigJwk(
override val scope: String,
override val clientId: String,
override val issuer: String,
override val endpoint: String,
val clientJwk: String,
override val additionalClaims: Map<String, Any>? = null
) : MaskinportenClientConfig {

private val rsaKey: RSAKey by lazy {
try {
RSAKey.parse(clientJwk)
} catch (e: Exception) {
throw IllegalArgumentException("Invalid JWK format", e)
}
}
private val signer: RSASSASigner by lazy {
RSASSASigner(rsaKey.toPrivateKey())
}
private val header: JWSHeader by lazy {
JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(rsaKey.keyID)
.type(JOSEObjectType.JWT)
.build()
}

private fun claims(): JWTClaimsSet {
val now = currentTime()
val builder = JWTClaimsSet.Builder()
.issuer(clientId)
.audience(issuer)
.issueTime(now)
.claim("scope", scope)
.expirationTime(Date.from(now.toInstant().plusSeconds(60)))
.jwtID(UUID.randomUUID().toString())

additionalClaims?.forEach { (key, value) ->
builder.claim(key, value)
}

return builder.build()
}

override fun getJwtAssertion(): String {
return SignedJWT(header, claims()).apply { sign(signer) }.serialize()
}
}
85 changes: 85 additions & 0 deletions src/main/kotlin/MaskinportenClientConfigPkey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package no.nav.helsearbeidsgiver.maskinporten

import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.util.Base64
import java.util.Date
import java.util.UUID

/**
* MaskinportenClientConfigPkey er en implementasjon av MaskinportenClientConfig med privatekey som autentiseringsmetode for maskinporten
*
* @param kid Det er id-en til Nøkkelen key-id (kid)
* @param privateKey Det er privatekey som skal brukes til å signere JWT tokenet
* @param clientId issuer - Din egen client_id.
* @param issuer Audience - issuer-identifikatoren til Maskinporten. Verdi for aktuelt miljø finner du på .well-known-endpunkt.
* @param consumerOrgNr Det er organisasjonsnummeret til virksomheten som skal bruke maskinporten på vegne av
* @param scope Space-separert liste over scopes som klienten forespør.
* @param endpoint Det er endepunktet til maskinporten
*
*/
class MaskinportenClientConfigPkey(
val kid: String,
val privateKey: String,
override val clientId: String,
override val issuer: String,
override val scope: String,
override val endpoint: String,
override val additionalClaims: Map<String, Any>? = null
) : MaskinportenClientConfig {

val currentTimestamp = System.currentTimeMillis() / 1000
override fun getJwtAssertion(): String {
val header =
JWSHeader
.Builder(JWSAlgorithm.RS256)
.keyID(kid)
.type(JOSEObjectType.JWT)
.build()

val signer = RSASSASigner(loadPrivateKey(privateKey))
val signedJWT = SignedJWT(header, claims())
signedJWT.sign(signer)

return signedJWT.serialize()
}

private fun claims(): JWTClaimsSet {
val now = currentTime()
val builder = JWTClaimsSet
.Builder()
.issuer(clientId)
.audience(issuer)
.issueTime(now)
.expirationTime(Date.from(now.toInstant().plusSeconds(60)))
.claim("scope", scope)
.jwtID(UUID.randomUUID().toString())

additionalClaims?.forEach { (key, value) ->
builder.claim(key, value)
}

return builder.build()
}
}

private fun loadPrivateKey(key: String): PrivateKey {
val keyText =
key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
.replace("\\s".toRegex(), "")

val encoded = Base64.getDecoder().decode(keyText)
return KeyFactory
.getInstance("RSA")
.generatePrivate(PKCS8EncodedKeySpec(encoded))
}
5 changes: 2 additions & 3 deletions src/test/kotlin/MaskinportenClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import io.ktor.serialization.kotlinx.json.json
import io.mockk.every
import kotlinx.coroutines.runBlocking
import no.nav.helsearbeidsgiver.maskinporten.MaskinportenClient
import no.nav.helsearbeidsgiver.maskinporten.MaskinportenClientConfigJwk
import no.nav.helsearbeidsgiver.maskinporten.MaskinportenClientConfigPkey
import no.nav.helsearbeidsgiver.maskinporten.MaskinportenClientConfigSimpleAssertion
import no.nav.helsearbeidsgiver.maskinporten.createHttpClient
import no.nav.helsearbeidsgiver.utils.test.mock.mockStatic
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -84,7 +84,6 @@ class MaskinportenClientTest {
kid = "test_kid",
privateKey = generatePkey(),
clientId = "test_issuer",
consumerOrgNr = "test_consumer_org_nr",
scope = "test_scope",
issuer = "https://test.test.no/",
endpoint = "https://test.test.no/token"
Expand All @@ -99,7 +98,7 @@ class MaskinportenClientTest {
}
}

private fun getMaskinportenClientConfig() = MaskinportenClientConfigSimpleAssertion(
private fun getMaskinportenClientConfig() = MaskinportenClientConfigJwk(
scope = "test_scope",
clientId = "test_client_id",
clientJwk = generateJWK(),
Expand Down

0 comments on commit 561c5ba

Please sign in to comment.