Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORE-18720: Add session state #5291

Merged
merged 9 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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