0af35d44f4
Provide the production foundation for Apple identity, immutable credits, referrals, integrity checks, managed providers, and hardened Docker deployment.
75 lines
3.0 KiB
Kotlin
75 lines
3.0 KiB
Kotlin
package com.osglab.account.features.auth
|
|
|
|
import com.nimbusds.jose.JWSAlgorithm
|
|
import com.nimbusds.jose.crypto.ECDSAVerifier
|
|
import com.nimbusds.jwt.SignedJWT
|
|
import com.osglab.account.config.AppleConfig
|
|
import io.kotest.assertions.throwables.shouldThrow
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.shouldBe
|
|
import java.security.KeyPairGenerator
|
|
import java.security.interfaces.ECPublicKey
|
|
import java.security.spec.ECGenParameterSpec
|
|
import java.time.Clock
|
|
import java.time.Instant
|
|
import java.time.ZoneOffset
|
|
import java.util.Base64
|
|
|
|
class AppleClientSecretProviderTest : FunSpec({
|
|
test("creates a verifiable short-lived ES256 Apple client secret") {
|
|
val now = Instant.parse("2026-08-16T00:00:00Z")
|
|
val keyPair = KeyPairGenerator.getInstance("EC").apply {
|
|
initialize(ECGenParameterSpec("secp256r1"))
|
|
}.generateKeyPair()
|
|
val privateKeyPem = Base64.getMimeEncoder(64, "\n".toByteArray())
|
|
.encodeToString(keyPair.private.encoded)
|
|
.let { "-----BEGIN PRIVATE KEY-----\n$it\n-----END PRIVATE KEY-----" }
|
|
val config = AppleConfig(
|
|
teamId = "TEAM123",
|
|
keyId = "KEY123",
|
|
clientId = "com.example.ios",
|
|
privateKeyPem = privateKeyPem,
|
|
jwksUrl = "https://appleid.apple.com/auth/keys",
|
|
tokenUrl = "https://appleid.apple.com/auth/token",
|
|
revokeUrl = "https://appleid.apple.com/auth/revoke",
|
|
)
|
|
|
|
val serialized = AppleClientSecretProvider(
|
|
config,
|
|
Clock.fixed(now, ZoneOffset.UTC),
|
|
).create()
|
|
val jwt = SignedJWT.parse(serialized)
|
|
|
|
jwt.header.algorithm shouldBe JWSAlgorithm.ES256
|
|
jwt.header.keyID shouldBe "KEY123"
|
|
jwt.verify(ECDSAVerifier(keyPair.public as ECPublicKey)) shouldBe true
|
|
jwt.jwtClaimsSet.issuer shouldBe "TEAM123"
|
|
jwt.jwtClaimsSet.subject shouldBe "com.example.ios"
|
|
jwt.jwtClaimsSet.audience shouldBe listOf("https://appleid.apple.com")
|
|
jwt.jwtClaimsSet.issueTime.toInstant() shouldBe now
|
|
jwt.jwtClaimsSet.expirationTime.toInstant() shouldBe now.plusSeconds(300)
|
|
}
|
|
|
|
test("rejects an EC key that is not Apple P-256") {
|
|
val keyPair = KeyPairGenerator.getInstance("EC").apply {
|
|
initialize(ECGenParameterSpec("secp384r1"))
|
|
}.generateKeyPair()
|
|
val privateKeyPem = Base64.getMimeEncoder(64, "\n".toByteArray())
|
|
.encodeToString(keyPair.private.encoded)
|
|
.let { "-----BEGIN PRIVATE KEY-----\n$it\n-----END PRIVATE KEY-----" }
|
|
val config = AppleConfig(
|
|
teamId = "TEAM123",
|
|
keyId = "KEY123",
|
|
clientId = "com.example.ios",
|
|
privateKeyPem = privateKeyPem,
|
|
jwksUrl = "https://appleid.apple.com/auth/keys",
|
|
tokenUrl = "https://appleid.apple.com/auth/token",
|
|
revokeUrl = "https://appleid.apple.com/auth/revoke",
|
|
)
|
|
|
|
shouldThrow<AppleClientUnavailableException> {
|
|
AppleClientSecretProvider(config).create()
|
|
}
|
|
}
|
|
})
|