Skip to content

Commit

Permalink
CORE-18720: Add session state (#5291)
Browse files Browse the repository at this point in the history
* CORE-18720: Introduce session state class

* Simpler stub

* Windows

* Apply review comments

* Apply review comment

* Remove stubs

* Apply review comment
  • Loading branch information
yift-r3 authored Dec 19, 2023
1 parent e5a0147 commit 6be8f73
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 7 deletions.
2 changes: 2 additions & 0 deletions components/link-manager/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
)
}
}
Original file line number Diff line number Diff line change
@@ -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<SessionEncryptionOpsClient> {
on { decryptSessionData(eq(encrypted), anyOrNull()) } doReturn decrypted
on { encryptSessionData(eq(decrypted), anyOrNull()) } doReturn encrypted
}
private val avroSchemaRegistry = mock<AvroSchemaRegistry>()
private val message = mock<LinkOutMessage>()

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<CordaRuntimeException> {
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)
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class AuthenticationProtocolInitiator(
{ revocationCheckMode, pemTrustStore, checkRevocation ->
CertificateValidator(revocationCheckMode, pemTrustStore, checkRevocation)
}
): AuthenticationProtocol(certificateValidatorFactory) {
): AuthenticationProtocol(certificateValidatorFactory), SerialisableSessionData {

companion object {
fun AuthenticationProtocolInitiatorDetails.toCorda(
Expand Down Expand Up @@ -279,7 +279,7 @@ class AuthenticationProtocolInitiator(
}
}

fun toAvro(): AuthenticationProtocolInitiatorDetails {
override fun toAvro(): AuthenticationProtocolInitiatorDetails {
return AuthenticationProtocolInitiatorDetails(
toAvro(
sessionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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." }
Expand Down Expand Up @@ -319,7 +319,7 @@ class AuthenticationProtocolResponder(
}
}

fun toAvro(): AuthenticationProtocolResponderDetails {
override fun toAvro(): AuthenticationProtocolResponderDetails {
return AuthenticationProtocolResponderDetails(
toAvro(
sessionId,
Expand Down
Loading

0 comments on commit 6be8f73

Please sign in to comment.