diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroup.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroup.kt index 083cf18c08..710089098f 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroup.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroup.kt @@ -2001,7 +2001,7 @@ class MlsGroup private constructor( // the signer authorized. val lifetime = updatePath.leafNode.lifetime if (lifetime != null) { - val now = TimeUtils.now() + val now = policy.now() require(now >= lifetime.notBefore && now <= lifetime.notAfter) { "LeafNode lifetime expired or not yet valid in UpdatePath" } @@ -2809,7 +2809,7 @@ class MlsGroup private constructor( // Validate lifetime val lifetime = leafNode.lifetime if (lifetime != null) { - val now = TimeUtils.now() + val now = policy.now() require(now >= lifetime.notBefore && now <= lifetime.notAfter) { "KeyPackage lifetime expired or not yet valid" } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroupPolicy.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroupPolicy.kt index 33ea399729..092c66143d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroupPolicy.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/mls/group/MlsGroupPolicy.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.mls.group import com.vitorpamplona.quartz.mls.messages.CommitResult import com.vitorpamplona.quartz.mls.tree.Capabilities import com.vitorpamplona.quartz.mls.tree.Extension +import com.vitorpamplona.quartz.utils.TimeUtils /** * The application's rules about who in a group may do what. @@ -128,6 +129,14 @@ interface MlsGroupPolicy { */ val knownExtensionTypes: Set get() = emptySet() + /** + * The clock, in unix seconds, that LeafNode lifetimes (RFC 9420 ยง7.2) are + * checked against. Wall time in production; a test pins it to a moment + * inside a recorded fixture's validity window, so fixtures signed by + * another implementation don't expire out from under the suite. + */ + fun now(): Long = TimeUtils.now() + companion object { /** RFC 9420 exactly as written: any member may commit anything valid. */ val Permissive: MlsGroupPolicy = object : MlsGroupPolicy {} diff --git a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/CordnLifecycleInteropTest.kt b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/CordnLifecycleInteropTest.kt index 5915d75df1..20f07d40f5 100644 --- a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/CordnLifecycleInteropTest.kt +++ b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/CordnLifecycleInteropTest.kt @@ -260,7 +260,7 @@ class CordnLifecycleInteropTest { val group = MlsGroup.create( identity = CordnCredential.of(alice).identity, - policy = CordnGroupPolicy, + policy = TsMlsFixtures.cordnPolicy, initialExtensions = listOf(CordnGroupMetadata(name = "from Kotlin").toExtension()), ) diff --git a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/KotlinArtifactProducerTest.kt b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/KotlinArtifactProducerTest.kt index 73c726fe72..b50583a525 100644 --- a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/KotlinArtifactProducerTest.kt +++ b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/KotlinArtifactProducerTest.kt @@ -83,7 +83,7 @@ class KotlinArtifactProducerTest { val group = MlsGroup.create( identity = CordnCredential.of(alice).identity, - policy = CordnGroupPolicy, + policy = TsMlsFixtures.cordnPolicy, initialExtensions = listOf(metadata.toExtension()), ) write("k-alice.pk", alice) diff --git a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/TsMlsFixtures.kt b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/TsMlsFixtures.kt index 5628e53cd9..f915bd36bd 100644 --- a/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/TsMlsFixtures.kt +++ b/quartz/src/jvmAndroidTest/kotlin/com/vitorpamplona/quartz/cordn/interop/TsMlsFixtures.kt @@ -20,9 +20,11 @@ */ package com.vitorpamplona.quartz.cordn.interop +import com.vitorpamplona.quartz.cordn.groups.CordnGroupPolicy import com.vitorpamplona.quartz.mls.codec.TlsReader import com.vitorpamplona.quartz.mls.codec.TlsWriter import com.vitorpamplona.quartz.mls.crypto.Ed25519 +import com.vitorpamplona.quartz.mls.group.MlsGroupPolicy import com.vitorpamplona.quartz.mls.messages.KeyPackageBundle import com.vitorpamplona.quartz.mls.messages.MlsKeyPackage import kotlin.io.encoding.Base64 @@ -43,6 +45,19 @@ object TsMlsFixtures { fun b64(name: String): ByteArray = Base64.decode(text(name)) fun hex(name: String): ByteArray = text(name).chunked(2).map { it.toInt(16).toByte() }.toByteArray() + + /** + * 2026-09-21T09:46:40Z, inside the fixtures' KeyPackage lifetime + * (2026-09-17T03:34:16Z to 2026-10-03T08:34:16Z). ts-mls signed those + * lifetimes, so they cannot be extended here; the clock moves instead. + */ + const val NOW: Long = 1_790_000_000L + + /** [CordnGroupPolicy] with its clock pinned to [NOW]. */ + val cordnPolicy: MlsGroupPolicy = + object : MlsGroupPolicy by CordnGroupPolicy { + override fun now(): Long = NOW + } } /**