Skip to content

Commit

Permalink
use explicit private key/id
Browse files Browse the repository at this point in the history
  • Loading branch information
tonisives committed Nov 13, 2023
1 parent 012f2d8 commit c882106
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 81 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ subprojects {
}

ext {
kotlin_version = "1.8.20"
kotlin_version = "1.9.20"

depLocation = 0
coroutinesVersion = '1.7.3'
koinVersion = '3.4.3'
koinVersion = '3.5.0'
ver = [
"hmkit-crypto-telematics": "0.1",
"hmkit-auto-api" : "13.1.1",
Expand Down
2 changes: 1 addition & 1 deletion hmkit-fleet-consumer
21 changes: 8 additions & 13 deletions hmkit-fleet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion"

implementation('org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0')

if (!project.hasProperty('depLocation') || project.depLocation > 0) {
Expand All @@ -37,28 +36,24 @@ dependencies {
api project(":hmkit-crypto-telematics")
}

// web
api('com.squareup.okhttp3:okhttp:4.10.0')
api('com.squareup.okhttp3:okhttp:4.12.0')

// Koin
implementation "io.insert-koin:koin-core:$koinVersion"

// logging
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'org.slf4j:slf4j-api:2.0.9'

// test
testImplementation deps.autoApi
testImplementation "io.insert-koin:koin-test:$koinVersion"

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1'

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

testImplementation 'org.slf4j:slf4j-simple:1.7.36'
testImplementation 'io.mockk:mockk:1.12.5'
testImplementation 'com.charleskorn.kaml:kaml:0.47.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0'
testImplementation 'org.slf4j:slf4j-simple:2.0.9'
testImplementation 'io.mockk:mockk:1.13.7'
testImplementation 'com.charleskorn.kaml:kaml:0.55.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'

detektPlugins('io.gitlab.arturbosch.detekt:detekt-formatting:1.23.0')
}
Expand Down
45 changes: 12 additions & 33 deletions hmkit-fleet/src/main/kotlin/HMKitCredentials.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ package com.highmobility.hmkitfleet
import com.highmobility.crypto.Crypto
import com.highmobility.crypto.value.PrivateKey
import com.highmobility.utils.Base64
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put

abstract class HMKitCredentials {
Expand Down Expand Up @@ -52,17 +48,20 @@ data class HMKitPrivateKeyCredentials(
*/
val clientId: String,
/**
* The full contents of the private key JSON file
* The PKCS8 formatted private key. It is included in the .json file downloaded from the developer console after creating a new private key in the App>OAuth section.
*/
val privateKey: String,
/**
* The private key ID. It is included in the .json file downloaded from the developer console after creating a new private key in the App>OAuth section.
*/
val privateKeyId: String
) : HMKitCredentials() {
override fun getTokenRequestBody(
jwtProvider: JwtProvider?
): String {
val credentials = OAuthPrivateKey(privateKey)

val jwt = getJwt(
credentials,
privateKey,
privateKeyId,
jwtProvider!!
)

Expand All @@ -76,7 +75,8 @@ data class HMKitPrivateKeyCredentials(
}

internal fun getJwt(
privateKey: OAuthPrivateKey,
privateKey: String,
privateKeyId: String,
jwtProvider: HMKitCredentials.JwtProvider
): String {
val crypto: Crypto = jwtProvider.getCrypto()
Expand All @@ -91,38 +91,17 @@ internal fun getJwt(

val jwtBody = buildJsonObject {
put("ver", 2)
put("iss", privateKey.serviceAccountPrivateKeyId)
put("iss", privateKeyId)
put("aud", baseUrl)
put("jti", uuid)
put("iat", timestamp / 1000)
}.toString()

val hmPrivateKey = PrivateKey(privateKey, PrivateKey.Format.PKCS8)
val headerBase64 = Base64.encodeUrlSafe(header.toByteArray())
val bodyBase64 = Base64.encodeUrlSafe(jwtBody.toByteArray())
val jwtContent = String.format("%s.%s", headerBase64, bodyBase64)
val cryptoPrivateKey = privateKey.getServiceAccountHmPrivateKey()
val jwtSignature = crypto.signJWT(jwtContent.toByteArray(), cryptoPrivateKey)
val jwtSignature = crypto.signJWT(jwtContent.toByteArray(), hmPrivateKey)

return String.format("%s.%s", jwtContent, jwtSignature.base64UrlSafe)
}

@Serializable
class OAuthPrivateKey {
@SerialName("private_key")
private val serviceAccountPrivateKey: String

@SerialName("id")
val serviceAccountPrivateKeyId: String

constructor(privateKeyJson: String) {
val json = Json.decodeFromString<JsonObject>(privateKeyJson)

// PKCS 8 format
serviceAccountPrivateKey = json["private_key"]!!.jsonPrimitive.content
serviceAccountPrivateKeyId = json["id"]!!.jsonPrimitive.content
}

internal fun getServiceAccountHmPrivateKey(): PrivateKey {
return PrivateKey(serviceAccountPrivateKey, PrivateKey.Format.PKCS8)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,13 @@ import org.slf4j.Logger
import java.nio.file.Files
import java.nio.file.Paths

const val testVin = "C0NNECT0000000001"

internal fun notExpiredAccessToken(): AccessToken {
return AccessToken(
"e903cb43-27b1-4e47-8922-c04ecd5d2019",
360
)
}

internal fun expiredAccessToken(): AccessToken {
return AccessToken(
"e903cb43-27b1-4e47-8922-c04ecd5d2019",
-1
)
}

open class BaseTest : KoinTest {
val privateKeyConfiguration = readPrivateKeyConfiguration()

Expand Down Expand Up @@ -92,17 +83,6 @@ open class BaseTest : KoinTest {

val mockLogger = mockk<Logger>()

// have to be in base so they are created again for each test class
internal val mockSignature = mockk<Signature> {
every {
base64UrlSafe
} returns "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg=="
every { base64 } returns "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg=="
every {
hex
} returns "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}

@BeforeEach
fun before() {
// mockk the logs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.highmobility.hmkitfleet.com.highmobility.hmkitfleet

import com.highmobility.crypto.Crypto
import com.highmobility.crypto.value.Signature
import com.highmobility.hmkitfleet.BaseTest
import com.highmobility.hmkitfleet.HMKitCredentials
import com.highmobility.hmkitfleet.HMKitOAuthCredentials
import com.highmobility.hmkitfleet.HMKitPrivateKeyCredentials
import com.highmobility.hmkitfleet.OAuthPrivateKey
import com.highmobility.utils.Base64
import io.mockk.every
import io.mockk.mockk
Expand Down Expand Up @@ -38,11 +36,14 @@ class HMKitCredentialsTest : BaseTest() {
val timestamp = 1000L
val uuid = "00000000-0000-0000-0000-000000000000"

val privateKey = readPrivateKeyJsonString()
val privateKeyJson = Json.decodeFromString<JsonObject>(readPrivateKeyJsonString())
val privateKey = privateKeyJson["private_key"]?.jsonPrimitive?.content ?: ""
val privateKeyId = privateKeyJson["id"]?.jsonPrimitive?.content ?: ""

val credentials: HMKitCredentials = HMKitPrivateKeyCredentials(
"client_id",
privateKey
privateKey,
privateKeyId
)

val crypto = mockk<Crypto> {
Expand All @@ -51,7 +52,7 @@ class HMKitCredentialsTest : BaseTest() {
any<ByteArray>(),
any()
)
} returns mockk<Signature> {
} returns mockk {
every { base64UrlSafe } returns "base64Signature"
}
}
Expand All @@ -64,7 +65,7 @@ class HMKitCredentialsTest : BaseTest() {
}

val requestBody = credentials.getTokenRequestBody(jwtProvider)
val expected = expectedHeaderAndBody(privateKey, jwtProvider)
val expected = expectedHeaderAndBody(privateKeyId, jwtProvider)

verify { crypto.signJWT(any<ByteArray>(), any()) }

Expand All @@ -79,19 +80,17 @@ class HMKitCredentialsTest : BaseTest() {
}

private fun expectedHeaderAndBody(
privateKey: String,
privateKeyId: String,
jwtProvider: HMKitCredentials.JwtProvider,
): Pair<String, String> {
val credentials = OAuthPrivateKey(privateKey)

val header = buildJsonObject {
put("alg", "ES256")
put("typ", "JWT")
}

val body = buildJsonObject {
put("ver", 2)
put("iss", credentials.serviceAccountPrivateKeyId)
put("iss", privateKeyId)
put("aud", jwtProvider.getBaseUrl())
put("jti", jwtProvider.generateUuid())
put("iat", jwtProvider.getTimestamp() / 1000)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ internal class AccessTokenRequestsTest : BaseTest() {
every { cache setProperty "accessToken" value any<AccessToken>() } just Runs

crypto = mockk()
every { crypto.signJWT(any<ByteArray>(), any()) } returns mockSignature

mockWebServer.start()
}
Expand Down

0 comments on commit c882106

Please sign in to comment.