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

Implement TLS security protocol including early muxer negotiation #283

Merged
merged 38 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
98b97da
Add key type tests
ianopolous Feb 14, 2023
b634975
Initial attempt at libp2p-tls implementation
ianopolous Feb 22, 2023
1c1edba
Update to Java 17 minimum, which Ed25519 asn1 needs
ianopolous Feb 22, 2023
ff32b9c
Improve tls cert test
ianopolous Feb 22, 2023
5d946d1
Linting
ianopolous Feb 22, 2023
cab2522
More linting
ianopolous Feb 22, 2023
ddaece6
Lint tests
ianopolous Feb 22, 2023
3a2cbb0
First successful libp2p-tls connections!! (Java-Java)
ianopolous Feb 23, 2023
923a043
Add TLS tests
ianopolous Feb 23, 2023
52fc181
Linting
ianopolous Feb 23, 2023
c85991e
Use jdk17 in jitpack
ianopolous Feb 23, 2023
aa867f6
Implement early muxer negiation in TLS using ALPN
ianopolous Feb 24, 2023
c27715f
Linting
ianopolous Feb 24, 2023
d56d880
Support ECDSA certificate keys in TLS as well as Ed25519
ianopolous Feb 24, 2023
933dba3
Remove UShortLengthCodec in tls.
ianopolous Feb 24, 2023
38b4b46
Hooray first successful TLS handshake with Kubo!
ianopolous Feb 25, 2023
66353e5
Push muxer channel initializer rather than directly initialise channe…
ianopolous Feb 27, 2023
f5fd8f2
Use context allocator in TLS
ianopolous Feb 27, 2023
fde75d0
Ping over TLS working to kubo!!
ianopolous Mar 3, 2023
8d54096
Reenable early muxer negotiation in TLS (It works!)
ianopolous Mar 3, 2023
647afe3
Change test to use tls and yamux
ianopolous Mar 3, 2023
ec080fe
Expose EventLoop on Stream
ianopolous Apr 5, 2023
ea86489
Expose EventLoop on Kotlin Stream
ianopolous Apr 5, 2023
0a359bc
Fix TLS test
ianopolous May 24, 2023
bdf4690
Lint test
ianopolous May 24, 2023
d3ed41e
Fix simulation test classes
ianopolous May 24, 2023
f1dd5bf
Revert "Expose EventLoop on Kotlin Stream"
ianopolous May 30, 2023
4324ccb
Revert "Expose EventLoop on Stream"
ianopolous May 30, 2023
61e630e
Remove other eventLoop() impl
ianopolous May 30, 2023
8cf6610
Revert java17 usage that somehow cherry-picked back in
ianopolous May 30, 2023
115dbac
Reinstate Java 11 support which somehow got lost
ianopolous May 30, 2023
1e4467b
SecureChannel: rename `nextProto` to `muxerProtocol` and make it opti…
Nashatyrev May 31, 2023
d5ba11b
Add more type safety: since SecureChannel may early negotiate just a …
Nashatyrev May 31, 2023
7390327
Remove unused variable
Nashatyrev May 31, 2023
e74723a
Remove accident star import optimization
Nashatyrev May 31, 2023
3071e67
Return back the "libp2p" entry for the TLS ALPN
Nashatyrev May 31, 2023
2ee67b0
Formatting
Nashatyrev May 31, 2023
34992ac
Merge pull request #9 from Nashatyrev/feature/upstream-tls-early-muxer
ianopolous May 31, 2023
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 jitpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jdk:
- openjdk11
1 change: 1 addition & 0 deletions libp2p/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {

implementation("org.bouncycastle:bcprov-jdk15on")
implementation("org.bouncycastle:bcpkix-jdk15on")
implementation("org.bouncycastle:bctls-jdk15on")

testImplementation(project(":tools:schedulers"))

Expand Down
11 changes: 5 additions & 6 deletions libp2p/src/main/java/io/libp2p/core/dsl/HostBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
import io.libp2p.core.Host;
import io.libp2p.core.crypto.PrivKey;
import io.libp2p.core.multistream.ProtocolBinding;
import io.libp2p.core.mux.StreamMuxerProtocol;
import io.libp2p.core.mux.*;
import io.libp2p.core.security.SecureChannel;
import io.libp2p.core.transport.Transport;
import io.libp2p.transport.ConnectionUpgrader;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.*;

public class HostBuilder {
public HostBuilder() { this(DefaultMode.Standard); }
Expand Down Expand Up @@ -41,7 +40,7 @@ public final HostBuilder transport(

@SafeVarargs
public final HostBuilder secureChannel(
Function<PrivKey, SecureChannel>... secureChannels) {
BiFunction<PrivKey, List<StreamMuxer>, SecureChannel>... secureChannels) {
secureChannels_.addAll(Arrays.asList(secureChannels));
return this;
}
Expand Down Expand Up @@ -76,7 +75,7 @@ public Host build() {
b.getTransports().add(t::apply)
);
secureChannels_.forEach(sc ->
b.getSecureChannels().add(sc::apply)
b.getSecureChannels().add((k, m) -> sc.apply(k, (List<StreamMuxer>)m))
);
muxers_.forEach(m ->
b.getMuxers().add(m.get())
Expand All @@ -91,7 +90,7 @@ public Host build() {

private DefaultMode defaultMode_;
private List<Function<ConnectionUpgrader, Transport>> transports_ = new ArrayList<>();
private List<Function<PrivKey, SecureChannel>> secureChannels_ = new ArrayList<>();
private List<BiFunction<PrivKey, List<StreamMuxer>, SecureChannel>> secureChannels_ = new ArrayList<>();
private List<Supplier<StreamMuxerProtocol>> muxers_ = new ArrayList<>();
private List<ProtocolBinding<?>> protocols_ = new ArrayList<>();
private List<String> listenAddresses_ = new ArrayList<>();
Expand Down
10 changes: 5 additions & 5 deletions libp2p/src/main/kotlin/io/libp2p/core/dsl/Builders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ import io.libp2p.host.HostImpl
import io.libp2p.host.MemoryAddressBook
import io.libp2p.network.NetworkImpl
import io.libp2p.protocol.IdentifyBinding
import io.libp2p.security.secio.SecIoSecureChannel
import io.libp2p.security.noise.NoiseXXSecureChannel
import io.libp2p.transport.ConnectionUpgrader
import io.libp2p.transport.tcp.TcpTransport
import io.netty.channel.ChannelHandler
import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler

typealias TransportCtor = (ConnectionUpgrader) -> Transport
typealias SecureChannelCtor = (PrivKey) -> SecureChannel
typealias SecureChannelCtor = (PrivKey, List<StreamMuxer>) -> SecureChannel
typealias IdentityFactory = () -> PrivKey

class HostConfigurationException(message: String) : RuntimeException(message)
Expand Down Expand Up @@ -131,7 +131,7 @@ open class Builder {
if (def == Defaults.Standard) {
if (identity.factory == null) identity.random()
if (transports.values.isEmpty()) transports { add(::TcpTransport) }
if (secureChannels.values.isEmpty()) secureChannels { add(::SecIoSecureChannel) }
if (secureChannels.values.isEmpty()) secureChannels { add(::NoiseXXSecureChannel) }
if (muxers.values.isEmpty()) muxers { add(StreamMuxerProtocol.Mplex) }
}

Expand Down Expand Up @@ -160,8 +160,6 @@ open class Builder {

val privKey = identity.factory!!()

val secureChannels = secureChannels.values.map { it(privKey) }

protocols.values.mapNotNull { (it as? IdentifyBinding) }.map { it.protocol }.find { it.idMessage == null }?.apply {
// initializing Identify with appropriate values
IdentifyOuterClass.Identify.newBuilder().apply {
Expand All @@ -177,6 +175,8 @@ open class Builder {

val muxers = muxers.map { it.createMuxer(streamMultistreamProtocol, protocols.values) }

val secureChannels = secureChannels.values.map { it(privKey, muxers) }

if (debug.muxFramesHandler.handlers.isNotEmpty()) {
val broadcast = ChannelVisitor.createBroadcast(*debug.muxFramesHandler.handlers.toTypedArray())
muxers.mapNotNull { it as? StreamMuxerDebug }.forEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.libp2p.core.multistream

import io.libp2p.core.P2PChannel
import java.util.concurrent.CompletableFuture

/**
* Represents [ProtocolBinding] with exact protocol version which was agreed on
*/
open class NegotiatedProtocol<TController, TBinding : ProtocolBinding<TController>> (
val binding: TBinding,
val protocol: ProtocolId
) {
open fun initChannel(ch: P2PChannel): CompletableFuture<out TController> =
binding.initChannel(ch, protocol)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface ProtocolBinding<out TController> {
/**
* Returns initializer for this protocol on the provided channel, together with an optional controller object.
*/
fun initChannel(ch: P2PChannel, selectedProtocol: String): CompletableFuture<out TController>
fun initChannel(ch: P2PChannel, selectedProtocol: ProtocolId): CompletableFuture<out TController>

/**
* If the [matcher] of this binding is not [Mode.STRICT] then it can't play initiator role since
Expand All @@ -56,7 +56,7 @@ interface ProtocolBinding<out TController> {
val srcBinding = this
return object : ProtocolBinding<TController> {
override val protocolDescriptor = ProtocolDescriptor(protocols, srcBinding.protocolDescriptor.protocolMatcher)
override fun initChannel(ch: P2PChannel, selectedProtocol: String): CompletableFuture<out TController> =
override fun initChannel(ch: P2PChannel, selectedProtocol: ProtocolId): CompletableFuture<out TController> =
srcBinding.initChannel(ch, selectedProtocol)
}
}
Expand All @@ -68,7 +68,7 @@ interface ProtocolBinding<out TController> {
fun <T> createSimple(protocolName: ProtocolId, handler: P2PChannelHandler<T>): ProtocolBinding<T> {
return object : ProtocolBinding<T> {
override val protocolDescriptor = ProtocolDescriptor(protocolName)
override fun initChannel(ch: P2PChannel, selectedProtocol: String): CompletableFuture<out T> {
override fun initChannel(ch: P2PChannel, selectedProtocol: ProtocolId): CompletableFuture<out T> {
return handler.initChannel(ch)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.libp2p.core.mux

import io.libp2p.core.multistream.NegotiatedProtocol

typealias NegotiatedStreamMuxer = NegotiatedProtocol<StreamMuxer.Session, StreamMuxer>
13 changes: 11 additions & 2 deletions libp2p/src/main/kotlin/io/libp2p/core/security/SecureChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package io.libp2p.core.security
import io.libp2p.core.PeerId
import io.libp2p.core.crypto.PubKey
import io.libp2p.core.multistream.ProtocolBinding
import io.libp2p.core.mux.NegotiatedStreamMuxer

/**
* The SecureChannel interface is implemented by all security channels, such as SecIO, TLS 1.3, Noise, and so on.
*/
interface SecureChannel : ProtocolBinding<SecureChannel.Session> {

open class Session(
/**
* The peer ID of the local peer.
Expand All @@ -20,8 +22,15 @@ interface SecureChannel : ProtocolBinding<SecureChannel.Session> {
val remoteId: PeerId,

/**
* The public key of the
* The public key of the remote peer.
*/
val remotePubKey: PubKey,

/**
* Contains muxer if security protocol supports
* [Early Multiplexer Negotiation](https://docs.libp2p.io/concepts/multiplex/early-negotiation/)
* and the protocol was successfully negotiated. Else contains `null`
*/
val remotePubKey: PubKey
val earlyMuxer: NegotiatedStreamMuxer?
)
}
4 changes: 4 additions & 0 deletions libp2p/src/main/kotlin/io/libp2p/crypto/keys/Ecdsa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ class EcdsaPublicKey(val pub: JavaECPublicKey) : PubKey(Crypto.KeyType.ECDSA) {

override fun raw(): ByteArray = pub.encoded

fun javaKey(): JavaECPublicKey {
return pub
}

fun toUncompressedBytes(): ByteArray =
byteArrayOf(0x04) + pub.w.affineX.toBytes(32) + pub.w.affineY.toBytes(32)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class ProtocolSelect<TController>(val protocols: List<ProtocolBinding<TControlle

val selectedFuture = CompletableFuture<TController>()
var activeFired = false
var userEvent = false

override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
// when protocol data immediately follows protocol id in the same packet
Expand All @@ -43,7 +42,6 @@ class ProtocolSelect<TController>(val protocols: List<ProtocolBinding<TControlle
}

override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
userEvent = true
when (evt) {
is ProtocolNegotiationSucceeded -> {
val protocolBinding = protocols.find { it.protocolDescriptor.protocolMatcher.matches(evt.proto) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ class NoiseSecureChannelSession(
remotePubKey: PubKey,
val aliceCipher: CipherState,
val bobCipher: CipherState
) : SecureChannel.Session(localId, remoteId, remotePubKey)
) : SecureChannel.Session(localId, remoteId, remotePubKey, null)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.libp2p.core.crypto.PubKey
import io.libp2p.core.crypto.marshalPublicKey
import io.libp2p.core.crypto.unmarshalPublicKey
import io.libp2p.core.multistream.ProtocolDescriptor
import io.libp2p.core.mux.StreamMuxer
import io.libp2p.core.security.SecureChannel
import io.libp2p.etc.REMOTE_PEER_ID
import io.libp2p.etc.types.toByteArray
Expand Down Expand Up @@ -48,6 +49,9 @@ class UShortLengthCodec : CombinedChannelDuplexHandler<LengthFieldBasedFrameDeco
class NoiseXXSecureChannel(private val localKey: PrivKey) :
SecureChannel {

@Suppress("UNUSED_PARAMETER")
constructor(localKey: PrivKey, muxerProtocols: List<StreamMuxer>) : this(localKey)

companion object {
const val protocolName = "Noise_XX_25519_ChaChaPoly_SHA256"
const val announce = "/noise"
Expand All @@ -63,10 +67,6 @@ class NoiseXXSecureChannel(private val localKey: PrivKey) :

override val protocolDescriptor = ProtocolDescriptor(announce)

fun initChannel(ch: P2PChannel): CompletableFuture<SecureChannel.Session> {
return initChannel(ch, "")
} // initChannel

override fun initChannel(
ch: P2PChannel,
selectedProtocol: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.crypto.PubKey
import io.libp2p.core.crypto.unmarshalPublicKey
import io.libp2p.core.multistream.ProtocolDescriptor
import io.libp2p.core.mux.StreamMuxer
import io.libp2p.core.security.SecureChannel
import io.libp2p.etc.types.toProtobuf
import io.libp2p.security.InvalidInitialPacket
Expand All @@ -23,6 +24,10 @@ import plaintext.pb.Plaintext
import java.util.concurrent.CompletableFuture

class PlaintextInsecureChannel(private val localKey: PrivKey) : SecureChannel {

@Suppress("UNUSED_PARAMETER")
constructor(localKey: PrivKey, muxerProtocols: List<StreamMuxer>) : this(localKey)

override val protocolDescriptor = ProtocolDescriptor("/plaintext/2.0.0")

override fun initChannel(ch: P2PChannel, selectedProtocol: String): CompletableFuture<out SecureChannel.Session> {
Expand Down Expand Up @@ -107,7 +112,8 @@ class PlaintextHandshakeHandler(
val session = SecureChannel.Session(
localPeerId,
remotePeerId,
remotePubKey
remotePubKey,
null
)

handshakeCompleted.complete(session)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.libp2p.core.P2PChannel
import io.libp2p.core.PeerId
import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.multistream.ProtocolDescriptor
import io.libp2p.core.mux.StreamMuxer
import io.libp2p.core.security.SecureChannel
import io.libp2p.etc.REMOTE_PEER_ID
import io.netty.buffer.ByteBuf
Expand All @@ -19,6 +20,10 @@ private val log = LoggerFactory.getLogger(SecIoSecureChannel::class.java)
private val HandshakeHandlerName = "SecIoHandshake"

class SecIoSecureChannel(private val localKey: PrivKey) : SecureChannel {

@Suppress("UNUSED_PARAMETER")
constructor(localKey: PrivKey, muxerProtocols: List<StreamMuxer>) : this(localKey)

override val protocolDescriptor = ProtocolDescriptor("/secio/1.0.0")

override fun initChannel(ch: P2PChannel, selectedProtocol: String): CompletableFuture<SecureChannel.Session> {
Expand Down Expand Up @@ -68,7 +73,8 @@ private class SecIoHandshake(
val session = SecureChannel.Session(
PeerId.fromPubKey(secIoCodec.local.permanentPubKey),
PeerId.fromPubKey(secIoCodec.remote.permanentPubKey),
secIoCodec.remote.permanentPubKey
secIoCodec.remote.permanentPubKey,
null
)
handshakeComplete.complete(session)
ctx.channel().pipeline().remove(HandshakeHandlerName)
Expand Down
Loading