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 plaintext encryption v2 #821

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
16 changes: 12 additions & 4 deletions libp2p/builders.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,6 +40,7 @@ type

SecureProtocol* {.pure.} = enum
Noise,
PlainText,
Secio {.deprecated.}

SwitchBuilder* = ref object
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand All @@ -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()

Expand Down
53 changes: 45 additions & 8 deletions libp2p/protocols/secure/plaintext.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,57 @@ 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

PlainTextConnection* = ref object of SecureConn

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

method init*(p: PlainText) {.gcsafe.} =
procCall Secure(p).init()
p.codec = PlainTextCodec
p.handler = handle

proc new*(T: typedesc[PlainText]): T =
let plainText = T()
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
1 change: 1 addition & 0 deletions tests/testnative.nim
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import testtcptransport,
testconnmngr,
testswitch,
testnoise,
testplaintext,
testpeerinfo,
testpeerstore,
testping,
Expand Down
111 changes: 111 additions & 0 deletions tests/testplaintext.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# 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.writeLp("Hello 1!")
await sconn.writeLp("Hello 2!")
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))

discard await sconn.readLp(100)
var msg = await sconn.readLp(100)

await sconn.close()
await conn.close()
await acceptFut
await transport1.stop()
await transport2.stop()

check string.fromBytes(msg) == "Hello 2!"

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()
40 changes: 40 additions & 0 deletions tests/testswitch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.} =
Expand Down