270 lines
8.7 KiB
Go
270 lines
8.7 KiB
Go
package wire
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"net"
|
|
|
|
"github.com/Indra-Labs/indra"
|
|
"github.com/Indra-Labs/indra/pkg/ciph"
|
|
"github.com/Indra-Labs/indra/pkg/key/address"
|
|
"github.com/Indra-Labs/indra/pkg/key/prv"
|
|
"github.com/Indra-Labs/indra/pkg/key/pub"
|
|
"github.com/Indra-Labs/indra/pkg/nonce"
|
|
"github.com/Indra-Labs/indra/pkg/sha256"
|
|
"github.com/Indra-Labs/indra/pkg/slice"
|
|
log2 "github.com/cybriq/proc/pkg/log"
|
|
)
|
|
|
|
var (
|
|
log = log2.GetLogger(indra.PathBase)
|
|
check = log.E.Chk
|
|
)
|
|
|
|
// MagicLen is 3 to make it nearly impossible that the wrong cipher will yield a
|
|
// valid Magic string as listed below.
|
|
const MagicLen = 3
|
|
|
|
type Onion interface {
|
|
Encode(o slice.Bytes, c *slice.Cursor)
|
|
Len() int
|
|
}
|
|
|
|
var (
|
|
ForwardMagic = slice.Bytes("fwd")
|
|
ExitMagic = slice.Bytes("exi")
|
|
ReturnMagic = slice.Bytes("rtn")
|
|
CipherMagic = slice.Bytes("cif")
|
|
PurchaseMagic = slice.Bytes("prc")
|
|
SessionMagic = slice.Bytes("ses")
|
|
AcknowledgementMagic = slice.Bytes("ack")
|
|
ResponseMagic = slice.Bytes("res")
|
|
TokenMagic = slice.Bytes("tok")
|
|
)
|
|
|
|
// Message is the generic top level wrapper for an Onion. All following messages
|
|
// are wrapped inside this.
|
|
type Message struct {
|
|
To *address.Sender
|
|
From *prv.Key
|
|
// The following field is only populated in the outermost layer.
|
|
slice.Bytes
|
|
Onion
|
|
}
|
|
|
|
var _ Onion = &Message{}
|
|
|
|
const OnionHeaderLen = 4 + nonce.IVLen + address.Len + pub.KeyLen
|
|
|
|
func (on *Message) Len() int { return MagicLen + OnionHeaderLen + on.Onion.Len() }
|
|
|
|
func (on *Message) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
// The first level message contains the Bytes, but the inner layers do
|
|
// not. The inner layers will be passed this buffer, but the first needs
|
|
// to have it copied from its original location.
|
|
if o == nil {
|
|
o = on.Bytes
|
|
}
|
|
// We write the checksum last so save the cursor position here.
|
|
checkStart := *c
|
|
checkEnd := checkStart + 4
|
|
// Generate a new nonce and copy it in.
|
|
n := nonce.New()
|
|
copy(o[c.Inc(4):c.Inc(nonce.IVLen)], n[:])
|
|
// Derive the cloaked key and copy it in.
|
|
to := on.To.GetCloak()
|
|
copy(o[*c:c.Inc(address.Len)], to[:])
|
|
// Call the tree of onions to perform their encoding.
|
|
on.Onion.Encode(o, c)
|
|
// Then we can encrypt the message segment
|
|
var e error
|
|
var blk cipher.Block
|
|
if blk = ciph.GetBlock(on.From, on.To.Key); check(e) {
|
|
panic(e)
|
|
}
|
|
ciph.Encipher(blk, n, o[checkEnd:])
|
|
// Get the hash of the message and truncate it to the checksum at the
|
|
// start of the message. Every layer of the onion has a Message and an
|
|
// onion inside it, the Message takes care of the encryption. This saves
|
|
// on complications as every layer is header first, message after, with
|
|
// wrapped messages inside each message afterwards.
|
|
hash := sha256.Single(o[checkEnd:])
|
|
copy(o[checkStart:checkEnd], hash[:4])
|
|
}
|
|
|
|
// Forward is just an IP address and a wrapper for another message.
|
|
type Forward struct {
|
|
net.IP
|
|
Onion
|
|
}
|
|
|
|
func (fw *Forward) Len() int { return MagicLen + len(fw.IP) + 1 + fw.Onion.Len() }
|
|
|
|
func (fw *Forward) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], ForwardMagic)
|
|
o[*c] = byte(len(fw.IP))
|
|
copy(o[c.Inc(1):c.Inc(len(fw.IP))], fw.IP)
|
|
fw.Onion.Encode(o, c)
|
|
}
|
|
|
|
// Exit messages are the layer of a message after two Forward packets that
|
|
// provides an exit address and
|
|
type Exit struct {
|
|
// Port identifies the type of service as well as being the port used by
|
|
// the service to be relayed to. Notice there is no IP address, this is
|
|
// because Indranet only forwards to exits of decentralised services
|
|
// also running on the same machine. This service could be a proxy, of
|
|
// course, if configured this way. This could be done by tunneling from
|
|
// a local Socks5 proxy into Indranet and the exit node also having
|
|
// this.
|
|
Port uint16
|
|
// Cipher is a set of 3 symmetric ciphers that are to be used in their
|
|
// given order over the reply message from the service.
|
|
Cipher [3]sha256.Hash
|
|
// Bytes are the message to be passed to the exit service.
|
|
slice.Bytes
|
|
// Return is the encoded message with the three hops using the Return
|
|
// keys for the relevant relays, encrypted progressively. Note that this
|
|
// message uses a different Cipher than the one above
|
|
Return Onion
|
|
}
|
|
|
|
func (ex *Exit) Len() int {
|
|
return MagicLen + slice.Uint16Len + 3*sha256.Len + ex.Bytes.Len() +
|
|
ex.Return.Len()
|
|
}
|
|
|
|
func (ex *Exit) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], ExitMagic)
|
|
port := slice.NewUint16()
|
|
slice.EncodeUint16(port, int(ex.Port))
|
|
copy(o[*c:c.Inc(slice.Uint16Len)], port)
|
|
copy(o[*c:c.Inc(sha256.Len)], ex.Cipher[0][:])
|
|
copy(o[*c:c.Inc(sha256.Len)], ex.Cipher[1][:])
|
|
copy(o[*c:c.Inc(sha256.Len)], ex.Cipher[1][:])
|
|
bytesLen := slice.NewUint32()
|
|
slice.EncodeUint32(bytesLen, len(ex.Bytes))
|
|
copy(o[*c:c.Inc(slice.Uint32Len)], bytesLen)
|
|
copy(o[*c:c.Inc(len(ex.Bytes))], ex.Bytes)
|
|
ex.Return.Encode(o, c)
|
|
|
|
}
|
|
|
|
// Return messages are distinct from Forward messages in that the header
|
|
// encryption uses a different secret than the payload. The magic bytes signal
|
|
// this to the relay that receives this, which then looks up the Return key
|
|
// matching the To address in the message header.
|
|
type Return struct {
|
|
// IP is the address of the next relay in the return leg of a circuit.
|
|
net.IP
|
|
Forward, Return pub.Key
|
|
Onion
|
|
}
|
|
|
|
func (rt *Return) Len() int { return MagicLen + len(rt.IP) + 1 + rt.Onion.Len() }
|
|
|
|
func (rt *Return) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], ReturnMagic)
|
|
o[*c] = byte(len(rt.IP))
|
|
copy(o[c.Inc(1):c.Inc(len(rt.IP))], rt.IP)
|
|
rt.Onion.Encode(o, c)
|
|
}
|
|
|
|
// Cipher delivers a public key to be used in association with a Return
|
|
// specifically in the situation of a node bootstrapping that doesn't have
|
|
// sessions yet. The ID allows the client to associate the Cipher to the
|
|
// Purchase.
|
|
type Cipher struct {
|
|
nonce.ID
|
|
Key pub.Bytes
|
|
Forward Onion
|
|
}
|
|
|
|
func (ci *Cipher) Len() int { return MagicLen + pub.KeyLen + ci.Forward.Len() }
|
|
|
|
func (ci *Cipher) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(nonce.IDLen)], ci.ID[:])
|
|
copy(o[*c:c.Inc(MagicLen)], CipherMagic)
|
|
copy(o[c.Inc(1):c.Inc(sha256.Len)], ci.Key[:])
|
|
ci.Forward.Encode(o, c)
|
|
}
|
|
|
|
// Purchase is a message that is sent after first forwarding a Lighting payment
|
|
// of an amount corresponding to the number of bytes requested based on the
|
|
// price advertised for Exit traffic by a relay. The Receipt is the confirmation
|
|
// after requesting an Invoice for the amount and then paying it.
|
|
//
|
|
// This message contains a Return message, which enables payments to proxy
|
|
// forwards through two hops to the router that will issue the Session, plus two
|
|
// more Return layers for carrying the Session back to the client.
|
|
//
|
|
// Purchases have an ID created by the client.
|
|
type Purchase struct {
|
|
Value uint64
|
|
Return Onion
|
|
}
|
|
|
|
func (pr *Purchase) Len() int {
|
|
return MagicLen + slice.Uint64Len + pr.Return.Len()
|
|
}
|
|
|
|
func (pr *Purchase) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], PurchaseMagic)
|
|
value := slice.NewUint64()
|
|
slice.EncodeUint64(value, pr.Value)
|
|
copy(o[*c:c.Inc(slice.Uint64Len)], value)
|
|
pr.Return.Encode(o, c)
|
|
}
|
|
|
|
// Session is a message containing two public keys which identify to a relay the
|
|
// session to account bytes on, this is wrapped in two Return message layers by
|
|
// the seller. Forward keys are used for encryption in Forward and Exit
|
|
// messages, and Return keys are separate and are only known to the client and
|
|
// relay that issues a Session, ensuring that the Exit cannot see the inner
|
|
// layers of the Return messages.
|
|
type Session struct {
|
|
ForwardKey pub.Bytes
|
|
ReturnKey pub.Bytes
|
|
Return
|
|
}
|
|
|
|
func (se *Session) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], SessionMagic)
|
|
copy(o[*c:c.Inc(pub.KeyLen)], se.ForwardKey[:])
|
|
copy(o[*c:c.Inc(pub.KeyLen)], se.ReturnKey[:])
|
|
se.Return.Encode(o, c)
|
|
}
|
|
|
|
// Acknowledgement messages just contain a nonce ID, these are used to terminate
|
|
// ping and Cipher onion messages that confirm relaying was successful.
|
|
type Acknowledgement struct {
|
|
nonce.ID
|
|
}
|
|
|
|
func (ak *Acknowledgement) Len() int { return MagicLen + nonce.IDLen }
|
|
|
|
func (ak *Acknowledgement) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], AcknowledgementMagic)
|
|
copy(o[*c:c.Inc(pub.KeyLen)], ak.ID[:])
|
|
}
|
|
|
|
type Response slice.Bytes
|
|
|
|
func (rs Response) Len() int { return MagicLen + len(rs) + 4 }
|
|
|
|
func (rs Response) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], ResponseMagic)
|
|
bytesLen := slice.NewUint32()
|
|
slice.EncodeUint32(bytesLen, len(rs))
|
|
copy(o[*c:c.Inc(slice.Uint32Len)], bytesLen)
|
|
copy(o[*c:c.Inc(len(rs))], rs)
|
|
}
|
|
|
|
type Token sha256.Hash
|
|
|
|
func (tk Token) Len() int { return MagicLen + sha256.Len }
|
|
|
|
func (tk Token) Encode(o slice.Bytes, c *slice.Cursor) {
|
|
copy(o[*c:c.Inc(MagicLen)], TokenMagic)
|
|
copy(o[*c:c.Inc(sha256.Len)], tk[:])
|
|
}
|