[noise] Champa tunnel; per-packet Noise

David Fifield david at bamsoftware.com
Mon Feb 14 17:46:18 PST 2022


Last year we had a discussion about different ways to layer Noise with a
reliable stream protocl built upon an unreliable datagram protocol.

https://moderncrypto.org/mail-archive/noise/2021/002109.html

1. "Noise over streams". Use the insecure datagram protocol to build a
   stream abstraction, then encrypt successive chunks of the stream as
   Noise messages (with, e.g., length prefixes). Like TLS over TCP.
2. "Streams over Noise". Encrypt each datagram separately as a Noise
   message, with an attached explicit nonce, as in
   https://noiseprotocol.org/noise.html#out-of-order-transport-messages.
   Build the stream abstraction over these cryptographically protected
   datagrams. Like QUIC.

The conversation last time was in the context of a DNS tunnel that used
approach (1). Because of the limited space for encoding messages in DNS
queries, it is relatively costly to attach a nonce and authentication
tag to every packet. More about the crypto layering in the DNS tunnel:
https://www.bamsoftware.com/software/dnstt/protocol.html#crypto

Now I'm working on another tunnel, Champa, which uses an AMP cache as a
proxy. An AMP cache can be thought of as an HTTP proxy that requires a
specific, restricted HTML encoding. The information capacity per
transaction is a lot less restricted than DNS, but otherwise the
communication model is similar: you don't get a long-lived, reliable
underlying channel; instead you need to make a session abstraction out
of a sequence of independent HTTP request–response pairs. To do this,
I'm using the same idea as before: have the HTTP requests and responses
contain packets of a session/reliability protocol, encapsulated and
encoded to conform with AMP cache requirements.

I had initially implemented Champa using layering approach (1), like the
DNS tunnel. But now I've rearchitected it to use approach (2), and this
is what I'd like to invite comment on.

The existing protocol layering was:
	Noise		confidentiality and integrity
	KCP/smux	sequencing/reliability
	AMP/HTML	network
The new changes push Noise lower in the stack, closer to the "network"
layer:
	KCP/smux	sequencing/reliability
	Noise		confidentiality and integrity
	AMP/HTML	network

Here are the code changes:
	https://repo.or.cz/champa.git/log/3e594d4520c601c991c9121481bd7941cc142b11?hp=136966fc0b0f41897bb64945d43487c0d2b19fd9
	https://repo.or.cz/champa.git/commitdiff/3e594d4520c601c991c9121481bd7941cc142b11?hp=136966fc0b0f41897bb64945d43487c0d2b19fd9

The main data structure change is the removal of a noise.socket type and
its replacement by a noise.Session type.
	https://repo.or.cz/champa.git/commitdiff/3e594d4520c601c991c9121481bd7941cc142b11
	https://repo.or.cz/champa.git/blob/3e594d4520c601c991c9121481bd7941cc142b11:/noise/session.go
Where noise.socket could synchronously exchange handshake messages over
an already established stream, noise.Session manages different message
types (Init, Resp, Transport, as in Wireguard) and breaks the handshake
into steps per message received or sent. The initiator calls
InitiateHandshake to get an Init message and a half-constructed
"PreSession". After receiving the responder's Resp message, the
initiator calls FinishHandshake on the PreSession to promote it to a
full Session.
	pre, initMsg, err := InitiateHandshake(nil, pubkey)
	// err check
	// send initMsg to the responder
	// receive respMsg from the responder
	session, err := pre.FinishHandshake(respMsg)
	// err check
The responder calls AcceptHandshake to immediately get a full Session
and a Resp message to send back to the initiator:
	// receive initMsg from the initiator
	session, respMsg, err := AcceptHandshake(nil, initMsg, privkey)
	// err check
	// send respMsg to the initiator

The Encrypt and Decrypt methods of noise.Session use the convention that
the first 8 bytes of a buffer are an explicit nonce for the actual Noise
message in the remainder. There is a replayWindow type that keeps track
of the highest correctly authenticated nonce yet seen from the peer, and
a bitmap of already uses nonces within a recent window. A nonce is valid
if it is higher than the highest nonce so far, or if it is within the
recent window and not used yet.
	https://repo.or.cz/champa.git/blob/3e594d4520c601c991c9121481bd7941cc142b11:/noise/replay.go

The tunnel client has a simple noiseDial function that does the
handshake exchange. There is not yet any provision for retransmitting
handshake messages.
	https://repo.or.cz/champa.git/blob/3e594d4520c601c991c9121481bd7941cc142b11:/champa-client/main.go#l36
The tunnel server is more complicated, since it must handle sessions
from many clients at once. On receiving an Init message from a client
for which no session yet exists, it sends back a Resp message, creates a
session for that client, and processes future Transport messages in that
session. The Noise session is removed after the termination of the
underlying KCP/smux session, which is driven by an idle timeout.
	https://repo.or.cz/champa.git/blob/3e594d4520c601c991c9121481bd7941cc142b11:/champa-server/main.go#l313


More information about the Noise mailing list