From 1d17859eeb734aca2829268e895766182722a80d Mon Sep 17 00:00:00 2001 From: Tanguy Date: Fri, 16 Dec 2022 11:58:40 +0100 Subject: [PATCH 1/3] Implement plaintext encryption v2 --- libp2p/builders.nim | 16 +++- libp2p/protocols/secure/plaintext.nim | 51 +++++++++--- tests/testnative.nim | 1 + tests/testplaintext.nim | 110 ++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 tests/testplaintext.nim diff --git a/libp2p/builders.nim b/libp2p/builders.nim index fdff75ba0a..cde78b71a1 100644 --- a/libp2p/builders.nim +++ b/libp2p/builders.nim @@ -26,7 +26,7 @@ import switch, peerid, peerinfo, stream/connection, multiaddress, crypto/crypto, transports/[transport, tcptransport], muxers/[muxer, mplex/mplex, yamux/yamux], - protocols/[identify, secure/secure, secure/noise, rendezvous], + protocols/[identify, secure/secure, secure/noise, secure/plaintext, rendezvous], protocols/connectivity/[autonat, relay/relay, relay/client, relay/rtransport], connmanager, upgrademngrs/muxedupgrade, nameresolving/nameresolver, @@ -40,6 +40,7 @@ type SecureProtocol* {.pure.} = enum Noise, + PlainText, Secio {.deprecated.} SwitchBuilder* = ref object @@ -134,6 +135,11 @@ proc withNoise*(b: SwitchBuilder): SwitchBuilder {.public.} = b.secureManagers.add(SecureProtocol.Noise) b +proc withPlainText*(b: SwitchBuilder): SwitchBuilder {.public.} = + warn "Using plain text encryption!" + b.secureManagers.add(SecureProtocol.PlainText) + b + proc withTransport*(b: SwitchBuilder, prov: TransportProvider): SwitchBuilder {.public.} = ## Use a custom transport runnableExamples: @@ -209,8 +215,13 @@ proc build*(b: SwitchBuilder): Switch let seckey = b.privKey.get(otherwise = pkRes.expect("Expected default Private Key")) + if b.secureManagers.len == 0: + b.secureManagers &= SecureProtocol.Noise + var secureManagerInstances: seq[Secure] + if SecureProtocol.PlainText in b.secureManagers: + secureManagerInstances.add(PlainText.new(seckey)) if SecureProtocol.Noise in b.secureManagers: secureManagerInstances.add(Noise.new(b.rng, seckey).Secure) @@ -234,9 +245,6 @@ proc build*(b: SwitchBuilder): Switch transports.add(tProvider(muxedUpgrade)) transports - if b.secureManagers.len == 0: - b.secureManagers &= SecureProtocol.Noise - if isNil(b.rng): b.rng = newRng() diff --git a/libp2p/protocols/secure/plaintext.nim b/libp2p/protocols/secure/plaintext.nim index 151c7e6b01..e9b99b3515 100644 --- a/libp2p/protocols/secure/plaintext.nim +++ b/libp2p/protocols/secure/plaintext.nim @@ -15,20 +15,53 @@ else: import chronos import secure, ../../stream/connection -const PlainTextCodec* = "/plaintext/1.0.0" +const PlainTextCodec* = "/plaintext/2.0.0" type PlainText* = ref object of Secure + localPublicKey: PublicKey -method init(p: PlainText) {.gcsafe.} = - proc handle(conn: Connection, proto: string) - {.async, gcsafe.} = discard - ## plain text doesn't do anything + PlainTextError* = object of LPError - p.codec = PlainTextCodec - p.handler = handle + PlainTextConnection* = ref object of SecureConn -proc new*(T: typedesc[PlainText]): T = - let plainText = T() +method readMessage*(sconn: PlainTextConnection): Future[seq[byte]] {.async.} = + var buffer: array[32768, byte] + let length = await sconn.stream.readOnce(addr buffer[0], buffer.len) + return @(buffer[0..length]) + +method write*(sconn: PlainTextConnection, message: seq[byte]): Future[void] = + sconn.stream.write(message) + +method handshake*(p: PlainText, conn: Connection, initiator: bool, peerId: Opt[PeerId]): Future[SecureConn] {.async.} = + var exchange = initProtoBuffer() + exchange.write(2, p.localPublicKey) + + await conn.writeLp(exchange.buffer) + let + remoteData = await conn.readLp(1024) + remotePb = initProtoBuffer(remoteData) + var remotePk: PublicKey + remotePb.getRequiredField(2, remotePk).tryGet() + + let remotePeerId = PeerId.init(remotePk).valueOr: + raise newException(PlainTextError, "Invalid remote peer id: " & $error) + + if peerId.isSome: + if peerId.get() != remotePeerId: + raise newException(PlainTextError, "Plain text handshake, peer id don't match! " & $remotePeerId & " != " & $peerId) + + var res = PlainTextConnection.new(conn, conn.peerId, conn.observedAddr) + return res + +proc new*( + T: typedesc[PlainText], + privateKey: PrivateKey + ): T = + + let pk = privateKey.getPublicKey() + .expect("Expected valid Private Key") + + let plainText = T(localPublicKey: pk) plainText.init() plainText diff --git a/tests/testnative.nim b/tests/testnative.nim index cc04fab66c..3789470676 100644 --- a/tests/testnative.nim +++ b/tests/testnative.nim @@ -32,6 +32,7 @@ import testtcptransport, testconnmngr, testswitch, testnoise, + testplaintext, testpeerinfo, testpeerstore, testping, diff --git a/tests/testplaintext.nim b/tests/testplaintext.nim new file mode 100644 index 0000000000..92a195fbd4 --- /dev/null +++ b/tests/testplaintext.nim @@ -0,0 +1,110 @@ +# Nim-LibP2P +# Copyright (c) 2022 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +{.used.} + +import tables +import chronos, stew/byteutils +import chronicles +import ../libp2p/[switch, + errors, + multistream, + stream/bufferstream, + protocols/identify, + stream/connection, + transports/transport, + transports/tcptransport, + multiaddress, + peerinfo, + crypto/crypto, + protocols/protocol, + muxers/muxer, + muxers/mplex/mplex, + protocols/secure/plaintext, + protocols/secure/secure, + upgrademngrs/muxedupgrade, + connmanager] +import ./helpers + +suite "Plain text": + teardown: + checkTrackers() + + asyncTest "e2e: handle write & read": + let + server = @[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()] + serverPrivKey = PrivateKey.random(ECDSA, rng[]).get() + serverInfo = PeerInfo.new(serverPrivKey, server) + serverPlainText = PlainText.new(serverPrivKey) + + let transport1: TcpTransport = TcpTransport.new(upgrade = Upgrade()) + await transport1.start(server) + + proc acceptHandler() {.async.} = + let conn = await transport1.accept() + let sconn = await serverPlainText.secure(conn, false, Opt.none(PeerId)) + try: + await sconn.write("Hello!") + finally: + await sconn.close() + await conn.close() + + let + acceptFut = acceptHandler() + transport2: TcpTransport = TcpTransport.new(upgrade = Upgrade()) + clientPrivKey = PrivateKey.random(ECDSA, rng[]).get() + clientInfo = PeerInfo.new(clientPrivKey, transport1.addrs) + clientPlainText = PlainText.new(clientPrivKey) + conn = await transport2.dial(transport1.addrs[0]) + + let sconn = await clientPlainText.secure(conn, true, Opt.some(serverInfo.peerId)) + + var msg = newSeq[byte](6) + await sconn.readExactly(addr msg[0], 6) + + await sconn.close() + await conn.close() + await acceptFut + await transport1.stop() + await transport2.stop() + + check string.fromBytes(msg) == "Hello!" + + asyncTest "e2e: wrong peerid": + let + server = @[MultiAddress.init("/ip4/0.0.0.0/tcp/0").tryGet()] + serverPrivKey = PrivateKey.random(ECDSA, rng[]).get() + serverInfo = PeerInfo.new(serverPrivKey, server) + serverPlainText = PlainText.new(serverPrivKey) + + let transport1: TcpTransport = TcpTransport.new(upgrade = Upgrade()) + await transport1.start(server) + + proc acceptHandler() {.async.} = + let conn = await transport1.accept() + try: + discard await serverPlainText.secure(conn, false, Opt.none(PeerId)) + finally: + await conn.close() + + let + acceptFut = acceptHandler() + transport2: TcpTransport = TcpTransport.new(upgrade = Upgrade()) + clientPrivKey = PrivateKey.random(ECDSA, rng[]).get() + clientInfo = PeerInfo.new(clientPrivKey, transport1.addrs) + clientPlainText = PlainText.new(clientPrivKey) + conn = await transport2.dial(transport1.addrs[0]) + + expect(CatchableError): + discard await clientPlainText.secure(conn, true, Opt.some(clientInfo.peerId)) + + await conn.close() + await acceptFut + await transport1.stop() + await transport2.stop() From 56b284a9646ea367147a4e89f0df0481110d24b1 Mon Sep 17 00:00:00 2001 From: Tanguy Date: Fri, 16 Dec 2022 12:05:55 +0100 Subject: [PATCH 2/3] fix init --- libp2p/protocols/secure/plaintext.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libp2p/protocols/secure/plaintext.nim b/libp2p/protocols/secure/plaintext.nim index e9b99b3515..2075bfa45e 100644 --- a/libp2p/protocols/secure/plaintext.nim +++ b/libp2p/protocols/secure/plaintext.nim @@ -54,6 +54,10 @@ method handshake*(p: PlainText, conn: Connection, initiator: bool, peerId: Opt[P var res = PlainTextConnection.new(conn, conn.peerId, conn.observedAddr) return res +method init*(p: PlainText) {.gcsafe.} = + procCall Secure(p).init() + p.codec = PlainTextCodec + proc new*( T: typedesc[PlainText], privateKey: PrivateKey From a797b5e408d953e3c733b4482768211388365d08 Mon Sep 17 00:00:00 2001 From: Tanguy Date: Fri, 16 Dec 2022 13:45:21 +0100 Subject: [PATCH 3/3] better test --- libp2p/protocols/secure/plaintext.nim | 2 +- tests/testplaintext.nim | 9 +++--- tests/testswitch.nim | 40 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/libp2p/protocols/secure/plaintext.nim b/libp2p/protocols/secure/plaintext.nim index 2075bfa45e..3817ef05eb 100644 --- a/libp2p/protocols/secure/plaintext.nim +++ b/libp2p/protocols/secure/plaintext.nim @@ -28,7 +28,7 @@ type method readMessage*(sconn: PlainTextConnection): Future[seq[byte]] {.async.} = var buffer: array[32768, byte] let length = await sconn.stream.readOnce(addr buffer[0], buffer.len) - return @(buffer[0..length]) + return @(buffer[0 ..< length]) method write*(sconn: PlainTextConnection, message: seq[byte]): Future[void] = sconn.stream.write(message) diff --git a/tests/testplaintext.nim b/tests/testplaintext.nim index 92a195fbd4..97831e1e38 100644 --- a/tests/testplaintext.nim +++ b/tests/testplaintext.nim @@ -50,7 +50,8 @@ suite "Plain text": let conn = await transport1.accept() let sconn = await serverPlainText.secure(conn, false, Opt.none(PeerId)) try: - await sconn.write("Hello!") + await sconn.writeLp("Hello 1!") + await sconn.writeLp("Hello 2!") finally: await sconn.close() await conn.close() @@ -65,8 +66,8 @@ suite "Plain text": let sconn = await clientPlainText.secure(conn, true, Opt.some(serverInfo.peerId)) - var msg = newSeq[byte](6) - await sconn.readExactly(addr msg[0], 6) + discard await sconn.readLp(100) + var msg = await sconn.readLp(100) await sconn.close() await conn.close() @@ -74,7 +75,7 @@ suite "Plain text": await transport1.stop() await transport2.stop() - check string.fromBytes(msg) == "Hello!" + check string.fromBytes(msg) == "Hello 2!" asyncTest "e2e: wrong peerid": let diff --git a/tests/testswitch.nim b/tests/testswitch.nim index 92a7d9f533..9f8dab3176 100644 --- a/tests/testswitch.nim +++ b/tests/testswitch.nim @@ -77,6 +77,46 @@ suite "Switch": check not switch1.isConnected(switch2.peerInfo.peerId) check not switch2.isConnected(switch1.peerInfo.peerId) + asyncTest "e2e plaintext encryption": + let done = newFuture[void]() + proc handle(conn: Connection, proto: string) {.async, gcsafe.} = + try: + let msg = string.fromBytes(await conn.readLp(1024)) + check "Hello!" == msg + await conn.writeLp("Hello!") + finally: + await conn.close() + done.complete() + + let testProto = new TestProto + testProto.codec = TestCodec + testProto.handler = handle + + let switch1 = newStandardSwitch(secureManagers=[PlainText]) + switch1.mount(testProto) + + let switch2 = newStandardSwitch(secureManagers=[PlainText]) + await switch1.start() + await switch2.start() + + let conn = await switch2.dial(switch1.peerInfo.peerId, switch1.peerInfo.addrs, TestCodec) + + check switch1.isConnected(switch2.peerInfo.peerId) + check switch2.isConnected(switch1.peerInfo.peerId) + + await conn.writeLp("Hello!") + let msg = string.fromBytes(await conn.readLp(1024)) + check "Hello!" == msg + await conn.close() + + await allFuturesThrowing( + done.wait(5.seconds), + switch1.stop(), + switch2.stop()) + + check not switch1.isConnected(switch2.peerInfo.peerId) + check not switch2.isConnected(switch1.peerInfo.peerId) + asyncTest "e2e use switch dial proto string with custom matcher": let done = newFuture[void]() proc handle(conn: Connection, proto: string) {.async, gcsafe.} =