diff --git a/components/link-manager/build.gradle b/components/link-manager/build.gradle index 8772df9ea02..027fd803c39 100644 --- a/components/link-manager/build.gradle +++ b/components/link-manager/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation project(":components:membership:membership-persistence-client") implementation project(":libs:metrics") implementation project(":libs:tracing") + implementation project(':libs:schema-registry:schema-registry') runtimeOnly "org.apache.felix:org.apache.felix.scr:$felixScrVersion" runtimeOnly "org.osgi:org.osgi.service.component:$osgiServiceComponentVersion" @@ -89,6 +90,7 @@ dependencies { nonOsgiIntegrationTestImplementation project(':libs:platform-info') nonOsgiIntegrationTestImplementation "net.corda:corda-avro-schema" nonOsgiIntegrationTestImplementation project(":libs:schema-registry:schema-registry-impl") + nonOsgiIntegrationTestImplementation project(':libs:schema-registry:schema-registry-impl') nonOsgiIntegrationTestRuntimeOnly 'org.osgi:osgi.core' integrationTestImplementation project(":components:configuration:configuration-read-service-impl") diff --git a/components/link-manager/src/main/kotlin/net/corda/p2p/linkmanager/state/SessionState.kt b/components/link-manager/src/main/kotlin/net/corda/p2p/linkmanager/state/SessionState.kt new file mode 100644 index 00000000000..bb1c6c2dfe0 --- /dev/null +++ b/components/link-manager/src/main/kotlin/net/corda/p2p/linkmanager/state/SessionState.kt @@ -0,0 +1,74 @@ +package net.corda.p2p.linkmanager.state + +import net.corda.crypto.client.SessionEncryptionOpsClient +import net.corda.data.p2p.LinkOutMessage +import net.corda.data.p2p.crypto.protocol.AuthenticationProtocolInitiatorDetails +import net.corda.data.p2p.crypto.protocol.AuthenticationProtocolResponderDetails +import net.corda.data.p2p.crypto.protocol.Session +import net.corda.p2p.crypto.protocol.api.AuthenticationProtocolInitiator.Companion.toCorda +import net.corda.p2p.crypto.protocol.api.AuthenticationProtocolResponder.Companion.toCorda +import net.corda.p2p.crypto.protocol.api.CheckRevocation +import net.corda.p2p.crypto.protocol.api.SerialisableSessionData +import net.corda.p2p.crypto.protocol.api.Session.Companion.toCorda +import net.corda.schema.registry.AvroSchemaRegistry +import net.corda.v5.base.exceptions.CordaRuntimeException +import java.nio.ByteBuffer +import net.corda.data.p2p.state.SessionState as AvroSessionData + +internal data class SessionState( + val message: LinkOutMessage, + val sessionData: SerialisableSessionData, +) { + companion object { + fun AvroSessionData.toCorda( + avroSchemaRegistry: AvroSchemaRegistry, + encryptionClient: SessionEncryptionOpsClient, + checkRevocation: CheckRevocation, + ): SessionState { + val rawData = ByteBuffer.wrap( + encryptionClient.decryptSessionData(this.encryptedSessionData.array()), + ) + val sessionData = when (val type = avroSchemaRegistry.getClassType(rawData)) { + AuthenticationProtocolInitiatorDetails::class.java -> { + avroSchemaRegistry.deserialize( + rawData, + AuthenticationProtocolInitiatorDetails::class.java, + null, + ).toCorda(checkRevocation) + } + AuthenticationProtocolResponderDetails::class.java -> { + avroSchemaRegistry.deserialize( + rawData, + AuthenticationProtocolResponderDetails::class.java, + null, + ).toCorda() + } + Session::class.java -> { + avroSchemaRegistry.deserialize( + rawData, + Session::class.java, + null, + ).toCorda() + } + else -> throw CordaRuntimeException("Unexpected type: $type") + } + return SessionState( + message = this.message, + sessionData = sessionData, + ) + } + } + + fun toAvro( + avroSchemaRegistry: AvroSchemaRegistry, + encryptionClient: SessionEncryptionOpsClient, + ): AvroSessionData { + val sessionAvroData = sessionData.toAvro() + val rawData = avroSchemaRegistry.serialize(sessionAvroData) + val encryptedData = encryptionClient.encryptSessionData(rawData.array()) + return AvroSessionData( + message, + ByteBuffer.wrap(encryptedData), + ) + } +} diff --git a/components/link-manager/src/test/kotlin/net/corda/p2p/linkmanager/state/SessionStateTest.kt b/components/link-manager/src/test/kotlin/net/corda/p2p/linkmanager/state/SessionStateTest.kt new file mode 100644 index 00000000000..d5a183423f8 --- /dev/null +++ b/components/link-manager/src/test/kotlin/net/corda/p2p/linkmanager/state/SessionStateTest.kt @@ -0,0 +1,258 @@ +package net.corda.p2p.linkmanager.state + +import net.corda.crypto.client.SessionEncryptionOpsClient +import net.corda.data.p2p.LinkOutMessage +import net.corda.data.p2p.crypto.ProtocolMode +import net.corda.data.p2p.crypto.protocol.AuthenticatedEncryptionSessionDetails +import net.corda.data.p2p.crypto.protocol.AuthenticatedSessionDetails +import net.corda.data.p2p.crypto.protocol.AuthenticationProtocolCommonDetails +import net.corda.data.p2p.crypto.protocol.AuthenticationProtocolInitiatorDetails +import net.corda.data.p2p.crypto.protocol.AuthenticationProtocolResponderDetails +import net.corda.data.p2p.crypto.protocol.InitiatorStep +import net.corda.data.p2p.crypto.protocol.ResponderStep +import net.corda.data.p2p.crypto.protocol.SecretKeySpec +import net.corda.data.p2p.crypto.protocol.Session +import net.corda.p2p.crypto.protocol.api.AuthenticatedSession +import net.corda.p2p.crypto.protocol.api.Session.Companion.toCorda +import net.corda.p2p.linkmanager.state.SessionState.Companion.toCorda +import net.corda.schema.registry.AvroSchemaRegistry +import net.corda.v5.base.exceptions.CordaRuntimeException +import org.apache.avro.specific.SpecificRecordBase +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.SoftAssertions.assertSoftly +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.nio.ByteBuffer +import java.security.Security +import net.corda.data.p2p.state.SessionState as AvroSessionData + +class SessionStateTest { + companion object { + private val publicKeyPem = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw0wYaKXc3M/aU8YfgdP +lnxOEsUbYuroh0S5OypTY9Fid75bdpV8auJCPZYOErBAtH9iD3t347ZhEyH6IXiS +Mw/tcN4ZGE4MGK7whR7HJ9560VbzS8RCUTjWYEHyRD4zvR7zk73tvKUUnP20a9ox +X3ObYFwFpIaQ+sq06qffEASk0S3sTWXQwXPfxNgrGcsyeDzztjjbQI1lXl5/N1Z+ +sP1IEWgiH9eVcdsYcS2qn858tq+YFRZeMV2JRPHxiLylZA5u0T3GXQ4Bm95mkJmz +oPrD4+MHOuE9mzdCly9ZCUTU21tziQ2XlLQtlB4+IQJV5XM5VGyP3n+JrFgsF79x +YQIDAQAB +-----END PUBLIC KEY----- + """.trimIndent().replace("\n", System.lineSeparator()) + + @BeforeAll + @JvmStatic + fun setup() { + Security.addProvider(BouncyCastleProvider()) + } + } + private val decrypted = byteArrayOf(1) + private val encrypted = byteArrayOf(2) + private val serialized = ByteBuffer.wrap(decrypted) + private val encryptionClient = mock { + on { decryptSessionData(eq(encrypted), anyOrNull()) } doReturn decrypted + on { encryptSessionData(eq(decrypted), anyOrNull()) } doReturn encrypted + } + private val avroSchemaRegistry = mock() + private val message = mock() + + private fun testToCorda( + avroObject: SpecificRecordBase, + ) { + whenever(avroSchemaRegistry.getClassType(serialized)).doReturn(avroObject::class.java) + whenever(avroSchemaRegistry.deserialize(serialized, avroObject::class.java, null)).doReturn(avroObject) + val sessionData = AvroSessionData( + message, + ByteBuffer.wrap(encrypted), + ) + + val data = sessionData.toCorda( + avroSchemaRegistry, + encryptionClient, + mock(), + ) + + assertSoftly { + assertThat(data.sessionData.toAvro()).isEqualTo(avroObject) + assertThat(data.message).isEqualTo(message) + } + } + + @Test + fun `test toCorda for AuthenticationProtocolInitiator`() { + val testObject = AuthenticationProtocolInitiatorDetails( + AuthenticationProtocolCommonDetails( + "sessionId", + 500000, + Session( + "sessionId", + 300, + AuthenticatedEncryptionSessionDetails( + SecretKeySpec( + "alg", + ByteBuffer.wrap(byteArrayOf(1)), + ), + ByteBuffer.wrap(byteArrayOf(2)), + SecretKeySpec( + "alg-2", + ByteBuffer.wrap(byteArrayOf(3)), + ), + ByteBuffer.wrap(byteArrayOf(3)), + ), + ), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ), + InitiatorStep.SESSION_ESTABLISHED, + listOf(ProtocolMode.AUTHENTICATION_ONLY, ProtocolMode.AUTHENTICATED_ENCRYPTION), + "$publicKeyPem${System.lineSeparator()}", + "groupId", + null, + null, + ) + testToCorda(testObject) + } + + @Test + fun `test toCorda for AuthenticationProtocolResponder`() { + val testObject = AuthenticationProtocolResponderDetails( + AuthenticationProtocolCommonDetails( + "sessionId", + 500000, + Session( + "sessionId", + 300, + AuthenticatedSessionDetails( + SecretKeySpec( + "alg", + ByteBuffer.wrap(byteArrayOf(1)), + ), + SecretKeySpec( + "alg-2", + ByteBuffer.wrap(byteArrayOf(3)), + ), + ), + ), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + ), + ResponderStep.SESSION_ESTABLISHED, + null, + null, + null, + null, + ) + + testToCorda(testObject) + } + + @Test + fun `test toCorda for AuthenticatedSession`() { + val testObject = Session( + "sessionId", + 300, + AuthenticatedSessionDetails( + SecretKeySpec( + "alg", + ByteBuffer.wrap(byteArrayOf(1)), + ), + SecretKeySpec( + "alg-2", + ByteBuffer.wrap(byteArrayOf(3)), + ), + ), + ) + + testToCorda(testObject) + } + + @Test + fun `test toCorda for AuthenticatedEncryptionSession`() { + val testObject = Session( + "sessionId", + 300, + AuthenticatedEncryptionSessionDetails( + SecretKeySpec( + "alg", + ByteBuffer.wrap(byteArrayOf(1)), + ), + ByteBuffer.wrap(byteArrayOf(2)), + SecretKeySpec( + "alg-2", + ByteBuffer.wrap(byteArrayOf(3)), + ), + ByteBuffer.wrap(byteArrayOf(3)), + ), + ) + + testToCorda(testObject) + } + + @Test + fun `test toCorda with unexpected type`() { + assertThrows { + testToCorda(mock()) + } + } + + @Test + fun `test toAvro`() { + val avroSession = Session( + "sessionId", + 300, + AuthenticatedSessionDetails( + SecretKeySpec( + "alg", + ByteBuffer.wrap(byteArrayOf(1)), + ), + SecretKeySpec( + "alg-2", + ByteBuffer.wrap(byteArrayOf(3)), + ), + ), + ) + val data = avroSession.toCorda() as AuthenticatedSession + whenever(avroSchemaRegistry.serialize(avroSession)).thenReturn(serialized) + val sessionState = SessionState( + message = message, + sessionData = data, + ) + + val avroSessionData = sessionState.toAvro( + avroSchemaRegistry = avroSchemaRegistry, + encryptionClient = encryptionClient, + ) + + assertSoftly { + assertThat(avroSessionData.message).isSameAs(message) + assertThat(avroSessionData.encryptedSessionData.array()).isEqualTo(encrypted) + } + } +} diff --git a/gradle.properties b/gradle.properties index e6018dc3a67..695e37789aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,7 +44,7 @@ commonsLangVersion = 3.12.0 commonsTextVersion = 1.10.0 # Corda API libs revision (change in 4th digit indicates a breaking change) # Change to 5.2.0.xx-SNAPSHOT to pick up maven local published copy -cordaApiVersion=5.2.0.18-beta+ +cordaApiVersion=5.2.0.19-beta+ disruptorVersion=3.4.4 felixConfigAdminVersion=1.9.26 diff --git a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolInitiator.kt b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolInitiator.kt index f9a74a1387a..6a2f2ede296 100644 --- a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolInitiator.kt +++ b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolInitiator.kt @@ -68,7 +68,7 @@ class AuthenticationProtocolInitiator( { revocationCheckMode, pemTrustStore, checkRevocation -> CertificateValidator(revocationCheckMode, pemTrustStore, checkRevocation) } -): AuthenticationProtocol(certificateValidatorFactory) { +): AuthenticationProtocol(certificateValidatorFactory), SerialisableSessionData { companion object { fun AuthenticationProtocolInitiatorDetails.toCorda( @@ -279,7 +279,7 @@ class AuthenticationProtocolInitiator( } } - fun toAvro(): AuthenticationProtocolInitiatorDetails { + override fun toAvro(): AuthenticationProtocolInitiatorDetails { return AuthenticationProtocolInitiatorDetails( toAvro( sessionId, diff --git a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolResponder.kt b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolResponder.kt index f9582002f82..98085364aab 100644 --- a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolResponder.kt +++ b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/AuthenticationProtocolResponder.kt @@ -69,7 +69,7 @@ class AuthenticationProtocolResponder( { revocationCheckMode, pemTrustStore, checkRevocation -> CertificateValidator(revocationCheckMode, pemTrustStore, checkRevocation) } -): AuthenticationProtocol(certificateValidatorFactory) { +): AuthenticationProtocol(certificateValidatorFactory), SerialisableSessionData { init { require(ourMaxMessageSize >= MIN_PACKET_SIZE) { "max message size needs to be at least $MIN_PACKET_SIZE bytes." } @@ -319,7 +319,7 @@ class AuthenticationProtocolResponder( } } - fun toAvro(): AuthenticationProtocolResponderDetails { + override fun toAvro(): AuthenticationProtocolResponderDetails { return AuthenticationProtocolResponderDetails( toAvro( sessionId, diff --git a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/SerialisableSessionData.kt b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/SerialisableSessionData.kt new file mode 100644 index 00000000000..0757fc74bf5 --- /dev/null +++ b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/SerialisableSessionData.kt @@ -0,0 +1,7 @@ +package net.corda.p2p.crypto.protocol.api + +import org.apache.avro.specific.SpecificRecordBase + +sealed interface SerialisableSessionData { + fun toAvro(): SpecificRecordBase +} diff --git a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/Session.kt b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/Session.kt index ee18348c3dc..43c993c377d 100644 --- a/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/Session.kt +++ b/libs/p2p-crypto/src/main/kotlin/net/corda/p2p/crypto/protocol/api/Session.kt @@ -12,10 +12,10 @@ import net.corda.data.p2p.crypto.protocol.SecretKeySpec as AvroSecretKeySpec /** * A marker interface supposed to be implemented by the different types of sessions supported by the authentication protocol. */ -interface Session { +interface Session: SerialisableSessionData { val sessionId: String - fun toAvro(): AvroSession + override fun toAvro(): AvroSession companion object { diff --git a/processors/link-manager-processor/build.gradle b/processors/link-manager-processor/build.gradle index 49d439515cd..76b91a87002 100644 --- a/processors/link-manager-processor/build.gradle +++ b/processors/link-manager-processor/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation project(":components:membership:membership-persistence-client") implementation project(':components:virtual-node:virtual-node-info-read-service') implementation project(":components:virtual-node:cpi-info-read-service") + implementation project(':libs:schema-registry:schema-registry') runtimeOnly project(":components:crypto:crypto-client-impl") runtimeOnly project(':components:configuration:configuration-read-service-impl') @@ -44,4 +45,5 @@ dependencies { runtimeOnly project(":libs:crypto:crypto-impl") runtimeOnly project(":components:membership:membership-group-read-impl") runtimeOnly project(":libs:web:web-impl") + runtimeOnly project(':libs:schema-registry:schema-registry-impl') } diff --git a/processors/link-manager-processor/src/main/kotlin/net/corda/processors/p2p/linkmanager/internal/LinkManagerProcessorImpl.kt b/processors/link-manager-processor/src/main/kotlin/net/corda/processors/p2p/linkmanager/internal/LinkManagerProcessorImpl.kt index 00b75dd44be..840a7689ded 100644 --- a/processors/link-manager-processor/src/main/kotlin/net/corda/processors/p2p/linkmanager/internal/LinkManagerProcessorImpl.kt +++ b/processors/link-manager-processor/src/main/kotlin/net/corda/processors/p2p/linkmanager/internal/LinkManagerProcessorImpl.kt @@ -24,6 +24,7 @@ import net.corda.messaging.api.subscription.factory.SubscriptionFactory import net.corda.p2p.linkmanager.LinkManager import net.corda.processors.p2p.linkmanager.LinkManagerProcessor import net.corda.schema.configuration.MessagingConfig.Subscription.POLL_TIMEOUT +import net.corda.schema.registry.AvroSchemaRegistry import net.corda.utilities.debug import net.corda.virtualnode.read.VirtualNodeInfoReadService import org.bouncycastle.jce.provider.BouncyCastleProvider @@ -61,6 +62,8 @@ class LinkManagerProcessorImpl @Activate constructor( private val membershipQueryClient: MembershipQueryClient, @Reference(service = GroupParametersReaderService::class) private val groupParametersReaderService: GroupParametersReaderService, + @Reference(service = AvroSchemaRegistry::class) + private val avroSchemaRegistry: AvroSchemaRegistry, ) : LinkManagerProcessor { private companion object {