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 2 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,65 @@
package net.corda.p2p.linkmanager.state

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.Session.Companion.toCorda
import net.corda.p2p.crypto.protocol.api.SessionData
import net.corda.p2p.linkmanager.stubs.Encryption
import net.corda.schema.registry.AvroSchemaRegistry
import net.corda.v5.base.exceptions.CordaRuntimeException
import org.apache.avro.specific.SpecificRecordBase
import java.nio.ByteBuffer
import net.corda.data.p2p.state.SessionState as AvroSessionData

internal data class SessionState(
val message: LinkOutMessage,
val sessionData: SessionData,
) {
companion object {
fun AvroSessionData.toCorda(
avroSchemaRegistry: AvroSchemaRegistry,
encryption: Encryption,
checkRevocation: CheckRevocation,
): SessionState {
val rawData = encryption.decrypt(this.encryptedSessionData.array())
val avroSessionData = avroSchemaRegistry.deserialize(
yift-r3 marked this conversation as resolved.
Show resolved Hide resolved
ByteBuffer.wrap(rawData),
SpecificRecordBase::class.java,
null,
)
val sessionData = when (avroSessionData) {
is AuthenticationProtocolInitiatorDetails ->
avroSessionData.toCorda(checkRevocation)
is AuthenticationProtocolResponderDetails ->
avroSessionData.toCorda()
is Session -> avroSessionData.toCorda().let {
(it as? SessionData) ?: throw CordaRuntimeException("Unexpected type: ${it.javaClass}")
}
yift-r3 marked this conversation as resolved.
Show resolved Hide resolved
else -> throw CordaRuntimeException("Unexpected type: ${avroSessionData.javaClass}")
}
return SessionState(
message = this.message,
sessionData = sessionData
)
}
}


fun toAvro(
avroSchemaRegistry: AvroSchemaRegistry,
encryption: Encryption,
): AvroSessionData {
val sessionAvroData = sessionData.toAvro()
val rawData = avroSchemaRegistry.serialize(sessionAvroData)
val encryptedData = encryption.encrypt(rawData.array())
return AvroSessionData(
message,
ByteBuffer.wrap(encryptedData)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.corda.p2p.linkmanager.stubs

import org.bouncycastle.util.encoders.Base64

/**
* This is an unsafe encryption stub. It should be replaced.
yift-r3 marked this conversation as resolved.
Show resolved Hide resolved
*/
internal class Encryption {
fun encrypt(data: ByteArray): ByteArray {
return Base64.encode(data)
}
fun decrypt(data: ByteArray): ByteArray {
return Base64.decode(data)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package net.corda.p2p.linkmanager.state

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.p2p.linkmanager.stubs.Encryption
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.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()

@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 encryption = mock<Encryption> {
on { decrypt(eq(encrypted)) } doReturn decrypted
on { encrypt(eq(decrypted)) } doReturn encrypted
}
private val avroSchemaRegistry = mock<AvroSchemaRegistry>()
private val message = mock<LinkOutMessage>()

fun testToCorda(
avroObject: SpecificRecordBase
) {
whenever(avroSchemaRegistry.deserialize(serialized, SpecificRecordBase::class.java, null)).doReturn(avroObject)
val sessionData = AvroSessionData(
message,
ByteBuffer.wrap(encrypted),
)

val data = sessionData.toCorda(
avroSchemaRegistry,
encryption,
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\n",
"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(
net.corda.data.p2p.crypto.protocol.SecretKeySpec(
"alg",
ByteBuffer.wrap(byteArrayOf(1)),
),
net.corda.data.p2p.crypto.protocol.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(
net.corda.data.p2p.crypto.protocol.SecretKeySpec(
yift-r3 marked this conversation as resolved.
Show resolved Hide resolved
"alg",
ByteBuffer.wrap(byteArrayOf(1)),
),
net.corda.data.p2p.crypto.protocol.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,
encryption = encryption,
)

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.17-beta+
cordaApiVersion=5.2.0.19-alpha-1702659351690

disruptorVersion=3.4.4
felixConfigAdminVersion=1.9.26
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AuthenticatedEncryptionSession(override val sessionId: String,
private val outboundNonce: ByteArray,
private val inboundSecretKey: SecretKey,
private val inboundNonce: ByteArray,
val maxMessageSize: Int): Session {
val maxMessageSize: Int): Session, SessionData {

private val provider = BouncyCastleProvider.PROVIDER_NAME
private val encryptionCipher = Cipher.getInstance(CIPHER_ALGO, provider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import kotlin.concurrent.withLock
class AuthenticatedSession(override val sessionId: String,
private val outboundSecretKey: SecretKey,
private val inboundSecretKey: SecretKey,
val maxMessageSize: Int): Session {
val maxMessageSize: Int): Session, SessionData {

private val provider = BouncyCastleProvider.PROVIDER_NAME
private val generationHMac = Mac.getInstance(HMAC_ALGO, provider).apply {
Expand Down
Loading