/*
NNCP -- Node to Node copy, utilities for store-and-forward data exchange
-Copyright (C) 2016-2019 Sergey Matveev <stargrave@stargrave.org>
+Copyright (C) 2016-2022 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
"bytes"
"crypto/cipher"
"crypto/rand"
- "encoding/binary"
"errors"
"io"
- "github.com/davecgh/go-xdr/xdr2"
- "golang.org/x/crypto/blake2b"
+ xdr "github.com/davecgh/go-xdr/xdr2"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/poly1305"
+ "lukechampine.com/blake3"
)
type PktType uint8
const (
EncBlkSize = 128 * (1 << 10)
- KDFXOFSize = chacha20poly1305.KeySize * 2
- PktTypeFile PktType = iota
- PktTypeFreq PktType = iota
- PktTypeExec PktType = iota
- PktTypeTrns PktType = iota
+ PktTypeFile PktType = iota
+ PktTypeFreq PktType = iota
+ PktTypeExec PktType = iota
+ PktTypeTrns PktType = iota
+ PktTypeExecFat PktType = iota
+ PktTypeArea PktType = iota
MaxPathSize = 1<<8 - 1
)
var (
- MagicNNCPPv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 3}
- MagicNNCPEv4 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 4}
- BadMagic error = errors.New("Unknown magic number")
- BadPktType error = errors.New("Unknown packet type")
+ BadPktType error = errors.New("Unknown packet type")
+
+ DeriveKeyFullCtx = string(MagicNNCPEv6.B[:]) + " FULL"
+ DeriveKeySizeCtx = string(MagicNNCPEv6.B[:]) + " SIZE"
+ DeriveKeyPadCtx = string(MagicNNCPEv6.B[:]) + " PAD"
PktOverhead int64
PktEncOverhead int64
- PktSizeOverhead int64 = 8 + poly1305.TagSize
+ PktSizeOverhead int64
+
+ TooBig = errors.New("Too big than allowed")
)
type Pkt struct {
Type PktType
Nice uint8
PathLen uint8
- Path *[MaxPathSize]byte
+ Path [MaxPathSize]byte
}
type PktTbs struct {
Nice uint8
Sender *NodeId
Recipient *NodeId
- ExchPub *[32]byte
+ ExchPub [32]byte
}
type PktEnc struct {
Nice uint8
Sender *NodeId
Recipient *NodeId
- ExchPub *[32]byte
- Sign *[ed25519.SignatureSize]byte
+ ExchPub [32]byte
+ Sign [ed25519.SignatureSize]byte
}
-func init() {
+type PktSize struct {
+ Payload uint64
+ Pad uint64
+}
+
+func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
+ if len(path) > MaxPathSize {
+ return nil, errors.New("Too long path")
+ }
pkt := Pkt{
- Type: PktTypeFile,
- Path: new([MaxPathSize]byte),
+ Magic: MagicNNCPPv3.B,
+ Type: typ,
+ Nice: nice,
+ PathLen: uint8(len(path)),
}
+ copy(pkt.Path[:], path)
+ return &pkt, nil
+}
+
+func init() {
var buf bytes.Buffer
+ pkt := Pkt{Type: PktTypeFile}
n, err := xdr.Marshal(&buf, pkt)
if err != nil {
panic(err)
PktOverhead = int64(n)
buf.Reset()
- dummyId, err := NodeIdFromString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+ dummyId, err := NodeIdFromString(DummyB32Id)
if err != nil {
panic(err)
}
pktEnc := PktEnc{
- Magic: MagicNNCPEv4,
- Nice: 123,
+ Magic: MagicNNCPEv6.B,
Sender: dummyId,
Recipient: dummyId,
- ExchPub: new([32]byte),
- Sign: new([ed25519.SignatureSize]byte),
}
n, err = xdr.Marshal(&buf, pktEnc)
if err != nil {
panic(err)
}
PktEncOverhead = int64(n)
-}
+ buf.Reset()
-func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
- if len(path) > MaxPathSize {
- return nil, errors.New("Too long path")
+ size := PktSize{}
+ n, err = xdr.Marshal(&buf, size)
+ if err != nil {
+ panic(err)
}
- pkt := Pkt{
- Magic: MagicNNCPPv3,
- Type: typ,
- Nice: nice,
- PathLen: uint8(len(path)),
- Path: new([MaxPathSize]byte),
+ PktSizeOverhead = int64(n)
+}
+
+func ctrIncr(b []byte) {
+ for i := len(b) - 1; i >= 0; i-- {
+ b[i]++
+ if b[i] != 0 {
+ return
+ }
}
- copy(pkt.Path[:], path)
- return &pkt, nil
+ panic("counter overflow")
}
-func aeadProcess(
- aead cipher.AEAD,
- nonce []byte,
- doEncrypt bool,
- r io.Reader,
- w io.Writer,
-) (int, error) {
- var blkCtr uint64
- ciphCtr := nonce[len(nonce)-8:]
- buf := make([]byte, EncBlkSize+aead.Overhead())
- var toRead []byte
- var toWrite []byte
- var n int
- var readBytes int
- var err error
- if doEncrypt {
- toRead = buf[:EncBlkSize]
- } else {
- toRead = buf
+func TbsPrepare(our *NodeOur, their *Node, pktEnc *PktEnc) []byte {
+ tbs := PktTbs{
+ Magic: MagicNNCPEv6.B,
+ Nice: pktEnc.Nice,
+ Sender: their.Id,
+ Recipient: our.Id,
+ ExchPub: pktEnc.ExchPub,
}
- for {
- n, err = io.ReadFull(r, toRead)
- if err != nil {
- if err == io.EOF {
- break
- }
- if err != io.ErrUnexpectedEOF {
- return readBytes + n, err
- }
- }
- readBytes += n
- blkCtr++
- binary.BigEndian.PutUint64(ciphCtr, blkCtr)
- if doEncrypt {
- toWrite = aead.Seal(buf[:0], nonce, buf[:n], nil)
- } else {
- toWrite, err = aead.Open(buf[:0], nonce, buf[:n], nil)
- if err != nil {
- return readBytes, err
- }
- }
- if _, err = w.Write(toWrite); err != nil {
- return readBytes, err
- }
+ var tbsBuf bytes.Buffer
+ if _, err := xdr.Marshal(&tbsBuf, &tbs); err != nil {
+ panic(err)
}
- return readBytes, nil
+ return tbsBuf.Bytes()
+}
+
+func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) ([]byte, bool, error) {
+ tbs := TbsPrepare(our, their, pktEnc)
+ return tbs, ed25519.Verify(their.SignPub, tbs, pktEnc.Sign[:]), nil
}
func sizeWithTags(size int64) (fullSize int64) {
+ size += PktSizeOverhead
fullSize = size + (size/EncBlkSize)*poly1305.TagSize
if size%EncBlkSize != 0 {
fullSize += poly1305.TagSize
return
}
+func sizePadCalc(sizePayload, minSize int64, wrappers int) (sizePad int64) {
+ expectedSize := sizePayload - PktOverhead
+ for i := 0; i < wrappers; i++ {
+ expectedSize = PktEncOverhead + sizeWithTags(PktOverhead+expectedSize)
+ }
+ sizePad = minSize - expectedSize
+ if sizePad < 0 {
+ sizePad = 0
+ }
+ return
+}
+
func PktEncWrite(
- our *NodeOur,
- their *Node,
- pkt *Pkt,
- nice uint8,
- size, padSize int64,
- data io.Reader,
- out io.Writer,
-) error {
- pubEph, prvEph, err := box.GenerateKey(rand.Reader)
+ our *NodeOur, their *Node,
+ pkt *Pkt, nice uint8,
+ minSize, maxSize int64, wrappers int,
+ r io.Reader, w io.Writer,
+) (pktEncRaw []byte, size int64, err error) {
+ pub, prv, err := box.GenerateKey(rand.Reader)
if err != nil {
- return err
+ return nil, 0, err
}
- var pktBuf bytes.Buffer
- if _, err := xdr.Marshal(&pktBuf, pkt); err != nil {
- return err
+
+ var buf bytes.Buffer
+ _, err = xdr.Marshal(&buf, pkt)
+ if err != nil {
+ return
}
+ pktRaw := make([]byte, buf.Len())
+ copy(pktRaw, buf.Bytes())
+ buf.Reset()
+
tbs := PktTbs{
- Magic: MagicNNCPEv4,
+ Magic: MagicNNCPEv6.B,
Nice: nice,
Sender: our.Id,
Recipient: their.Id,
- ExchPub: pubEph,
+ ExchPub: *pub,
}
- var tbsBuf bytes.Buffer
- if _, err = xdr.Marshal(&tbsBuf, &tbs); err != nil {
- return err
+ _, err = xdr.Marshal(&buf, &tbs)
+ if err != nil {
+ return
}
signature := new([ed25519.SignatureSize]byte)
- copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes()))
+ copy(signature[:], ed25519.Sign(our.SignPrv, buf.Bytes()))
+ ad := blake3.Sum256(buf.Bytes())
+ buf.Reset()
+
pktEnc := PktEnc{
- Magic: MagicNNCPEv4,
+ Magic: MagicNNCPEv6.B,
Nice: nice,
Sender: our.Id,
Recipient: their.Id,
- ExchPub: pubEph,
- Sign: signature,
+ ExchPub: *pub,
+ Sign: *signature,
}
- if _, err = xdr.Marshal(out, &pktEnc); err != nil {
- return err
- }
- sharedKey := new([32]byte)
- curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)
- kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
+ _, err = xdr.Marshal(&buf, &pktEnc)
if err != nil {
- return err
+ return
}
- if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
- return err
+ pktEncRaw = make([]byte, buf.Len())
+ copy(pktEncRaw, buf.Bytes())
+ buf.Reset()
+ _, err = w.Write(pktEncRaw)
+ if err != nil {
+ return
}
- key := make([]byte, chacha20poly1305.KeySize)
- if _, err = io.ReadFull(kdf, key); err != nil {
- return err
+ sharedKey := new([32]byte)
+ curve25519.ScalarMult(sharedKey, prv, their.ExchPub)
+ keyFull := make([]byte, chacha20poly1305.KeySize)
+ keySize := make([]byte, chacha20poly1305.KeySize)
+ blake3.DeriveKey(keyFull, DeriveKeyFullCtx, sharedKey[:])
+ blake3.DeriveKey(keySize, DeriveKeySizeCtx, sharedKey[:])
+ aeadFull, err := chacha20poly1305.New(keyFull)
+ if err != nil {
+ return
}
- aead, err := chacha20poly1305.New(key)
+ aeadSize, err := chacha20poly1305.New(keySize)
if err != nil {
- return err
+ return
}
- nonce := make([]byte, aead.NonceSize())
+ nonce := make([]byte, aeadFull.NonceSize())
- fullSize := pktBuf.Len() + int(size)
- sizeBuf := make([]byte, 8+aead.Overhead())
- binary.BigEndian.PutUint64(sizeBuf, uint64(sizeWithTags(int64(fullSize))))
- if _, err = out.Write(aead.Seal(sizeBuf[:0], nonce, sizeBuf[:8], nil)); err != nil {
- return err
+ data := make([]byte, EncBlkSize, EncBlkSize+aeadFull.Overhead())
+ mr := io.MultiReader(bytes.NewReader(pktRaw), r)
+ var sizePayload int64
+ var n int
+ var ct []byte
+ for {
+ n, err = io.ReadFull(mr, data)
+ sizePayload += int64(n)
+ if sizePayload > maxSize {
+ err = TooBig
+ return
+ }
+ if err == nil {
+ ct = aeadFull.Seal(data[:0], nonce, data[:n], ad[:])
+ _, err = w.Write(ct)
+ if err != nil {
+ return
+ }
+ ctrIncr(nonce)
+ continue
+ }
+ if !(err == io.EOF || err == io.ErrUnexpectedEOF) {
+ return
+ }
+ break
}
- lr := io.LimitedReader{R: data, N: size}
- mr := io.MultiReader(&pktBuf, &lr)
- written, err := aeadProcess(aead, nonce, true, mr, out)
+ sizePad := sizePadCalc(sizePayload, minSize, wrappers)
+ _, err = xdr.Marshal(&buf, &PktSize{uint64(sizePayload), uint64(sizePad)})
if err != nil {
- return err
- }
- if written != fullSize {
- return io.ErrUnexpectedEOF
+ return
}
- if padSize > 0 {
- if _, err = io.ReadFull(kdf, key); err != nil {
- return err
- }
- kdf, err = blake2b.NewXOF(blake2b.OutputLengthUnknown, key)
+
+ var aeadLast cipher.AEAD
+ if n+int(PktSizeOverhead) > EncBlkSize {
+ left := make([]byte, (n+int(PktSizeOverhead))-EncBlkSize)
+ copy(left, data[n-len(left):])
+ copy(data[PktSizeOverhead:], data[:n-len(left)])
+ copy(data[:PktSizeOverhead], buf.Bytes())
+ ct = aeadSize.Seal(data[:0], nonce, data[:EncBlkSize], ad[:])
+ _, err = w.Write(ct)
if err != nil {
- return err
- }
- if _, err = io.CopyN(out, kdf, padSize); err != nil {
- return err
+ return
}
+ ctrIncr(nonce)
+ copy(data, left)
+ n = len(left)
+ aeadLast = aeadFull
+ } else {
+ copy(data[PktSizeOverhead:], data[:n])
+ copy(data[:PktSizeOverhead], buf.Bytes())
+ n += int(PktSizeOverhead)
+ aeadLast = aeadSize
}
- return nil
-}
-func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
- tbs := PktTbs{
- Magic: MagicNNCPEv4,
- Nice: pktEnc.Nice,
- Sender: their.Id,
- Recipient: our.Id,
- ExchPub: pktEnc.ExchPub,
+ var sizeBlockPadded int
+ var sizePadLeft int64
+ if sizePad > EncBlkSize-int64(n) {
+ sizeBlockPadded = EncBlkSize
+ sizePadLeft = sizePad - (EncBlkSize - int64(n))
+ } else {
+ sizeBlockPadded = n + int(sizePad)
+ sizePadLeft = 0
}
- var tbsBuf bytes.Buffer
- if _, err := xdr.Marshal(&tbsBuf, &tbs); err != nil {
- return false, err
+ for i := n; i < sizeBlockPadded; i++ {
+ data[i] = 0
+ }
+ ct = aeadLast.Seal(data[:0], nonce, data[:sizeBlockPadded], ad[:])
+ _, err = w.Write(ct)
+ if err != nil {
+ return
}
- return ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil
+
+ size = sizePayload
+ if sizePadLeft > 0 {
+ keyPad := make([]byte, chacha20poly1305.KeySize)
+ blake3.DeriveKey(keyPad, DeriveKeyPadCtx, sharedKey[:])
+ _, err = io.CopyN(w, blake3.New(32, keyPad).XOF(), sizePadLeft)
+ }
+ return
}
func PktEncRead(
- our *NodeOur,
- nodes map[NodeId]*Node,
- data io.Reader,
- out io.Writer,
-) (*Node, int64, error) {
+ our *NodeOur, nodes map[NodeId]*Node,
+ r io.Reader, w io.Writer,
+ signatureVerify bool,
+ sharedKeyCached []byte,
+) (sharedKey []byte, their *Node, size int64, err error) {
var pktEnc PktEnc
- _, err := xdr.Unmarshal(data, &pktEnc)
+ _, err = xdr.Unmarshal(r, &pktEnc)
if err != nil {
- return nil, 0, err
+ return
+ }
+ switch pktEnc.Magic {
+ case MagicNNCPEv1.B:
+ err = MagicNNCPEv1.TooOld()
+ case MagicNNCPEv2.B:
+ err = MagicNNCPEv2.TooOld()
+ case MagicNNCPEv3.B:
+ err = MagicNNCPEv3.TooOld()
+ case MagicNNCPEv4.B:
+ err = MagicNNCPEv4.TooOld()
+ case MagicNNCPEv5.B:
+ err = MagicNNCPEv5.TooOld()
+ case MagicNNCPEv6.B:
+ default:
+ err = BadMagic
}
- if pktEnc.Magic != MagicNNCPEv4 {
- return nil, 0, BadMagic
- }
- their, known := nodes[*pktEnc.Sender]
- if !known {
- return nil, 0, errors.New("Unknown sender")
+ if err != nil {
+ return
}
if *pktEnc.Recipient != *our.Id {
- return nil, 0, errors.New("Invalid recipient")
+ err = errors.New("Invalid recipient")
+ return
}
- verified, err := TbsVerify(our, their, &pktEnc)
- if err != nil {
- return nil, 0, err
+
+ var tbsRaw []byte
+ if signatureVerify {
+ their = nodes[*pktEnc.Sender]
+ if their == nil {
+ err = errors.New("Unknown sender")
+ return
+ }
+ var verified bool
+ tbsRaw, verified, err = TbsVerify(our, their, &pktEnc)
+ if err != nil {
+ return
+ }
+ if !verified {
+ err = errors.New("Invalid signature")
+ return
+ }
+ } else {
+ tbsRaw = TbsPrepare(our, &Node{Id: pktEnc.Sender}, &pktEnc)
}
- if !verified {
- return their, 0, errors.New("Invalid signature")
+ ad := blake3.Sum256(tbsRaw)
+ if sharedKeyCached == nil {
+ key := new([32]byte)
+ curve25519.ScalarMult(key, our.ExchPrv, &pktEnc.ExchPub)
+ sharedKey = key[:]
+ } else {
+ sharedKey = sharedKeyCached
}
- sharedKey := new([32]byte)
- curve25519.ScalarMult(sharedKey, our.ExchPrv, pktEnc.ExchPub)
- kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
+
+ keyFull := make([]byte, chacha20poly1305.KeySize)
+ keySize := make([]byte, chacha20poly1305.KeySize)
+ blake3.DeriveKey(keyFull, DeriveKeyFullCtx, sharedKey[:])
+ blake3.DeriveKey(keySize, DeriveKeySizeCtx, sharedKey[:])
+ aeadFull, err := chacha20poly1305.New(keyFull)
if err != nil {
- return their, 0, err
+ return
}
- if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
- return their, 0, err
+ aeadSize, err := chacha20poly1305.New(keySize)
+ if err != nil {
+ return
}
+ nonce := make([]byte, aeadFull.NonceSize())
- key := make([]byte, chacha20poly1305.KeySize)
- if _, err = io.ReadFull(kdf, key); err != nil {
- return their, 0, err
+ ct := make([]byte, EncBlkSize+aeadFull.Overhead())
+ pt := make([]byte, EncBlkSize)
+ var n int
+FullRead:
+ for {
+ n, err = io.ReadFull(r, ct)
+ switch err {
+ case nil:
+ pt, err = aeadFull.Open(pt[:0], nonce, ct, ad[:])
+ if err != nil {
+ break FullRead
+ }
+ size += EncBlkSize
+ _, err = w.Write(pt)
+ if err != nil {
+ return
+ }
+ ctrIncr(nonce)
+ continue
+ case io.ErrUnexpectedEOF:
+ break FullRead
+ default:
+ return
+ }
}
- aead, err := chacha20poly1305.New(key)
+
+ pt, err = aeadSize.Open(pt[:0], nonce, ct[:n], ad[:])
if err != nil {
- return their, 0, err
+ return
}
- nonce := make([]byte, aead.NonceSize())
+ var pktSize PktSize
+ _, err = xdr.Unmarshal(bytes.NewReader(pt), &pktSize)
+ if err != nil {
+ return
+ }
+ pt = pt[PktSizeOverhead:]
- sizeBuf := make([]byte, 8+aead.Overhead())
- if _, err = io.ReadFull(data, sizeBuf); err != nil {
- return their, 0, err
+ left := int64(pktSize.Payload) - size
+ for left > int64(len(pt)) {
+ size += int64(len(pt))
+ left -= int64(len(pt))
+ _, err = w.Write(pt)
+ if err != nil {
+ return
+ }
+ n, err = io.ReadFull(r, ct)
+ if err != nil && err != io.ErrUnexpectedEOF {
+ return
+ }
+ ctrIncr(nonce)
+ pt, err = aeadFull.Open(pt[:0], nonce, ct[:n], ad[:])
+ if err != nil {
+ return
+ }
}
- sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, nil)
+ size += left
+ _, err = w.Write(pt[:left])
if err != nil {
- return their, 0, err
+ return
}
- size := int64(binary.BigEndian.Uint64(sizeBuf))
+ pt = pt[left:]
- lr := io.LimitedReader{R: data, N: size}
- written, err := aeadProcess(aead, nonce, false, &lr, out)
- if err != nil {
- return their, int64(written), err
+ if pktSize.Pad < uint64(len(pt)) {
+ err = errors.New("unexpected pad")
+ return
+ }
+ for i := 0; i < len(pt); i++ {
+ if pt[i] != 0 {
+ err = errors.New("non-zero pad byte")
+ return
+ }
}
- if written != int(size) {
- return their, int64(written), io.ErrUnexpectedEOF
+ sizePad := int64(pktSize.Pad) - int64(len(pt))
+ if sizePad == 0 {
+ return
}
- return their, size, nil
+
+ keyPad := make([]byte, chacha20poly1305.KeySize)
+ blake3.DeriveKey(keyPad, DeriveKeyPadCtx, sharedKey[:])
+ xof := blake3.New(32, keyPad).XOF()
+ pt = make([]byte, len(ct))
+ for sizePad > 0 {
+ n, err = io.ReadFull(r, ct)
+ if err != nil && err != io.ErrUnexpectedEOF {
+ return
+ }
+ _, err = io.ReadFull(xof, pt[:n])
+ if err != nil {
+ panic(err)
+ }
+ if bytes.Compare(ct[:n], pt[:n]) != 0 {
+ err = errors.New("wrong pad value")
+ return
+ }
+ sizePad -= int64(n)
+ }
+ if sizePad < 0 {
+ err = errors.New("excess pad")
+ }
+ return
}