]> Cypherpunks.ru repositories - nncp.git/blobdiff - src/cypherpunks.ru/nncp/pkt.go
Forbid any later GNU GPL versions autousage
[nncp.git] / src / cypherpunks.ru / nncp / pkt.go
index ce85ec2e853581358bd0ea1fc3671c72f61009f6..cb75c237286383bbd0962cbfdeb01e0445c55acc 100644 (file)
@@ -1,11 +1,10 @@
 /*
 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
-Copyright (C) 2016-2017 Sergey Matveev <stargrave@stargrave.org>
+Copyright (C) 2016-2019 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
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+the Free Software Foundation, version 3 of the License.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -22,48 +21,50 @@ import (
        "bytes"
        "crypto/cipher"
        "crypto/rand"
-       "crypto/subtle"
+       "encoding/binary"
        "errors"
-       "hash"
        "io"
 
        "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
+       "golang.org/x/crypto/chacha20poly1305"
        "golang.org/x/crypto/curve25519"
        "golang.org/x/crypto/ed25519"
-       "golang.org/x/crypto/hkdf"
        "golang.org/x/crypto/nacl/box"
-       "golang.org/x/crypto/twofish"
+       "golang.org/x/crypto/poly1305"
 )
 
 type PktType uint8
 
 const (
+       EncBlkSize = 128 * (1 << 10)
+       KDFXOFSize = chacha20poly1305.KeySize * 2
+
        PktTypeFile PktType = iota
        PktTypeFreq PktType = iota
-       PktTypeMail PktType = iota
+       PktTypeExec PktType = iota
        PktTypeTrns PktType = iota
 
        MaxPathSize = 1<<8 - 1
 
-       DefaultNiceMail = 64
-       DefaultNiceFreq = 196
-       DefaultNiceFile = 196
+       NNCPBundlePrefix = "NNCP"
 )
 
 var (
-       MagicNNCPPv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 1}
-       MagicNNCPEv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 1}
+       MagicNNCPPv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 2}
+       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")
 
-       PktOverhead    int64
-       PktEncOverhead int64
+       PktOverhead     int64
+       PktEncOverhead  int64
+       PktSizeOverhead int64 = 8 + poly1305.TagSize
 )
 
 type Pkt struct {
        Magic   [8]byte
        Type    PktType
+       Nice    uint8
        PathLen uint8
        Path    *[MaxPathSize]byte
 }
@@ -71,17 +72,18 @@ type Pkt struct {
 type PktTbs struct {
        Magic     [8]byte
        Nice      uint8
-       Recipient *NodeId
        Sender    *NodeId
+       Recipient *NodeId
        ExchPub   *[32]byte
 }
 
 type PktEnc struct {
-       Magic   [8]byte
-       Nice    uint8
-       Sender  *NodeId
-       ExchPub *[32]byte
-       Sign    *[ed25519.SignatureSize]byte
+       Magic     [8]byte
+       Nice      uint8
+       Sender    *NodeId
+       Recipient *NodeId
+       ExchPub   *[32]byte
+       Sign      *[ed25519.SignatureSize]byte
 }
 
 func init() {
@@ -94,7 +96,7 @@ func init() {
        if err != nil {
                panic(err)
        }
-       PktOverhead = 8 + blake2b.Size256 + int64(n) + blake2b.Size256
+       PktOverhead = int64(n)
        buf.Reset()
 
        dummyId, err := NodeIdFromString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
@@ -102,11 +104,12 @@ func init() {
                panic(err)
        }
        pktEnc := PktEnc{
-               Magic:   MagicNNCPEv1,
-               Nice:    123,
-               Sender:  dummyId,
-               ExchPub: new([32]byte),
-               Sign:    new([ed25519.SignatureSize]byte),
+               Magic:     MagicNNCPEv4,
+               Nice:      123,
+               Sender:    dummyId,
+               Recipient: dummyId,
+               ExchPub:   new([32]byte),
+               Sign:      new([ed25519.SignatureSize]byte),
        }
        n, err = xdr.Marshal(&buf, pktEnc)
        if err != nil {
@@ -115,39 +118,86 @@ func init() {
        PktEncOverhead = int64(n)
 }
 
-func NewPkt(typ PktType, path string) (*Pkt, error) {
-       pb := []byte(path)
-       if len(pb) > MaxPathSize {
+func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
+       if len(path) > MaxPathSize {
                return nil, errors.New("Too long path")
        }
        pkt := Pkt{
-               Magic:   MagicNNCPPv1,
+               Magic:   MagicNNCPPv2,
                Type:    typ,
-               PathLen: uint8(len(pb)),
+               Nice:    nice,
+               PathLen: uint8(len(path)),
                Path:    new([MaxPathSize]byte),
        }
-       copy(pkt.Path[:], pb)
+       copy(pkt.Path[:], path)
        return &pkt, nil
 }
 
-func blake256() hash.Hash {
-       h, err := blake2b.New256(nil)
-       if err != nil {
-               panic(err)
+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
+       }
+       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
+               }
        }
-       return h
+       return readBytes, nil
 }
 
-type DevZero struct{}
-
-func (d DevZero) Read(b []byte) (n int, err error) {
-       for n = 0; n < len(b); n++ {
-               b[n] = 0
+func sizeWithTags(size int64) (fullSize int64) {
+       fullSize = size + (size/EncBlkSize)*poly1305.TagSize
+       if size%EncBlkSize != 0 {
+               fullSize += poly1305.TagSize
        }
        return
 }
 
-func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize int64, data io.Reader, out io.Writer) error {
+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)
        if err != nil {
                return err
@@ -157,10 +207,10 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize
                return err
        }
        tbs := PktTbs{
-               Magic:     MagicNNCPEv1,
+               Magic:     MagicNNCPEv4,
                Nice:      nice,
-               Recipient: their.Id,
                Sender:    our.Id,
+               Recipient: their.Id,
                ExchPub:   pubEph,
        }
        var tbsBuf bytes.Buffer
@@ -170,97 +220,73 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize
        signature := new([ed25519.SignatureSize]byte)
        copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes()))
        pktEnc := PktEnc{
-               Magic:   MagicNNCPEv1,
-               Nice:    nice,
-               Sender:  our.Id,
-               ExchPub: pubEph,
-               Sign:    signature,
+               Magic:     MagicNNCPEv4,
+               Nice:      nice,
+               Sender:    our.Id,
+               Recipient: their.Id,
+               ExchPub:   pubEph,
+               Sign:      signature,
        }
        if _, err = xdr.Marshal(out, &pktEnc); err != nil {
                return err
        }
        sharedKey := new([32]byte)
        curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)
-       kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:])
-
-       keyEnc := make([]byte, 32)
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+       kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
+       if err != nil {
                return err
        }
-       keyAuth := make([]byte, 64)
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
+       if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
                return err
        }
 
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       key := make([]byte, chacha20poly1305.KeySize)
+       if _, err = io.ReadFull(kdf, key); err != nil {
                return err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-       mac, err := blake2b.New256(keyAuth)
+       aead, err := chacha20poly1305.New(key)
        if err != nil {
                return err
        }
+       nonce := make([]byte, aead.NonceSize())
 
-       mw := io.MultiWriter(out, mac)
-       ae := &cipher.StreamWriter{S: ctr, W: mw}
-       usize := uint64(size)
-       if _, err = xdr.Marshal(ae, &usize); err != nil {
+       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
        }
-       ae.Close()
-       out.Write(mac.Sum(nil))
 
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
-               return err
-       }
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
-               return err
-       }
-
-       ciph, err = twofish.NewCipher(keyEnc)
-       if err != nil {
-               return err
-       }
-       ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-       mac, err = blake2b.New256(keyAuth)
+       lr := io.LimitedReader{R: data, N: size}
+       mr := io.MultiReader(&pktBuf, &lr)
+       written, err := aeadProcess(aead, nonce, true, mr, out)
        if err != nil {
                return err
        }
-
-       mw = io.MultiWriter(out, mac)
-       ae = &cipher.StreamWriter{S: ctr, W: mw}
-       ae.Write(pktBuf.Bytes())
-       if _, err = io.CopyN(ae, data, size); err != nil {
-               return err
+       if written != fullSize {
+               return io.ErrUnexpectedEOF
        }
-       ae.Close()
-       out.Write(mac.Sum(nil))
-
        if padSize > 0 {
-               if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+               if _, err = io.ReadFull(kdf, key); err != nil {
                        return err
                }
-               ciph, err = twofish.NewCipher(keyEnc)
+               kdf, err = blake2b.NewXOF(blake2b.OutputLengthUnknown, key)
                if err != nil {
                        return err
                }
-               ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-               ae = &cipher.StreamWriter{S: ctr, W: out}
-               if _, err = io.CopyN(ae, DevZero{}, padSize); err != nil {
+               if _, err = io.CopyN(out, kdf, padSize); err != nil {
                        return err
                }
-               ae.Close()
        }
        return nil
 }
 
 func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
        tbs := PktTbs{
-               Magic:     MagicNNCPEv1,
+               Magic:     MagicNNCPEv4,
                Nice:      pktEnc.Nice,
-               Recipient: our.Id,
                Sender:    their.Id,
+               Recipient: our.Id,
                ExchPub:   pktEnc.ExchPub,
        }
        var tbsBuf bytes.Buffer
@@ -270,19 +296,27 @@ func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
        return ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil
 }
 
-func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Writer) (*Node, int64, error) {
+func PktEncRead(
+       our *NodeOur,
+       nodes map[NodeId]*Node,
+       data io.Reader,
+       out io.Writer,
+) (*Node, int64, error) {
        var pktEnc PktEnc
        _, err := xdr.Unmarshal(data, &pktEnc)
        if err != nil {
                return nil, 0, err
        }
-       if pktEnc.Magic != MagicNNCPEv1 {
+       if pktEnc.Magic != MagicNNCPEv4 {
                return nil, 0, BadMagic
        }
        their, known := nodes[*pktEnc.Sender]
        if !known {
                return nil, 0, errors.New("Unknown sender")
        }
+       if *pktEnc.Recipient != *our.Id {
+               return nil, 0, errors.New("Invalid recipient")
+       }
        verified, err := TbsVerify(our, their, &pktEnc)
        if err != nil {
                return nil, 0, err
@@ -292,69 +326,41 @@ func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Wri
        }
        sharedKey := new([32]byte)
        curve25519.ScalarMult(sharedKey, our.ExchPrv, pktEnc.ExchPub)
-       kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:])
-
-       keyEnc := make([]byte, 32)
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+       kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
+       if err != nil {
                return their, 0, err
        }
-       keyAuth := make([]byte, 64)
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
+       if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
                return their, 0, err
        }
 
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       key := make([]byte, chacha20poly1305.KeySize)
+       if _, err = io.ReadFull(kdf, key); err != nil {
                return their, 0, err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-       mac, err := blake2b.New256(keyAuth)
+       aead, err := chacha20poly1305.New(key)
        if err != nil {
                return their, 0, err
        }
+       nonce := make([]byte, aead.NonceSize())
 
-       tr := io.TeeReader(data, mac)
-       ae := &cipher.StreamReader{S: ctr, R: tr}
-       var usize uint64
-       if _, err = xdr.Unmarshal(ae, &usize); err != nil {
+       sizeBuf := make([]byte, 8+aead.Overhead())
+       if _, err = io.ReadFull(data, sizeBuf); err != nil {
                return their, 0, err
        }
-       tag := make([]byte, blake2b.Size256)
-       if _, err = io.ReadFull(data, tag); err != nil {
+       sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, nil)
+       if err != nil {
                return their, 0, err
        }
-       if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 {
-               return their, 0, errors.New("Unauthenticated size")
-       }
-       size := int64(usize)
-
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
-               return their, size, err
-       }
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
-               return their, size, err
-       }
+       size := int64(binary.BigEndian.Uint64(sizeBuf))
 
-       ciph, err = twofish.NewCipher(keyEnc)
-       if err != nil {
-               return their, size, err
-       }
-       ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-       mac, err = blake2b.New256(keyAuth)
+       lr := io.LimitedReader{R: data, N: size}
+       written, err := aeadProcess(aead, nonce, false, &lr, out)
        if err != nil {
-               return their, size, err
-       }
-
-       tr = io.TeeReader(data, mac)
-       ae = &cipher.StreamReader{S: ctr, R: tr}
-       if _, err = io.CopyN(out, ae, PktOverhead+size-8-blake2b.Size256-blake2b.Size256); err != nil {
-               return their, size, err
-       }
-       if _, err = io.ReadFull(data, tag); err != nil {
-               return their, size, err
+               return their, int64(written), err
        }
-       if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 {
-               return their, size, errors.New("Unauthenticated payload")
+       if written != int(size) {
+               return their, int64(written), io.ErrUnexpectedEOF
        }
        return their, size, nil
 }