]> Cypherpunks.ru repositories - nncp.git/commitdiff
Replace Twofish/HKDF with ChaCha20/BLAKE2X for speed and simplicity
authorSergey Matveev <stargrave@stargrave.org>
Thu, 28 Dec 2017 18:28:46 +0000 (21:28 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 28 Dec 2017 21:06:36 +0000 (00:06 +0300)
19 files changed:
VERSION
doc/cmds.texi
doc/eblob.texi
doc/news.ru.texi
doc/news.texi
doc/pkt.texi
makedist.sh
ports/nncp/Makefile
src/chacha20 [new symlink]
src/cypherpunks.ru/nncp/cfg.go
src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go
src/cypherpunks.ru/nncp/cmd/nncp-cfgenc/main.go
src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go
src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go
src/cypherpunks.ru/nncp/eblob.go
src/cypherpunks.ru/nncp/jobs.go
src/cypherpunks.ru/nncp/pkt.go
src/cypherpunks.ru/nncp/toss_test.go
src/cypherpunks.ru/nncp/tx.go

diff --git a/VERSION b/VERSION
index d3827e75a5cadb9fe4a27e1cb9b6d192e7323120..cd5ac039d67e0bdadb17976e4ac39f0ffe6bb6e4 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0
+2.0
index 1de83a4e8dc361f1ac0fade948fa853a905dddda..9868443f47a1c46f55717f8fb5a4f9b3cedac4fb 100644 (file)
@@ -252,11 +252,9 @@ packet creation. Pay attention that if you want to send 1 GiB of data
 taken from stdin, then you have to have 2 GiB of disk space for that
 temporary file and resulting encrypted packet. You can control where
 temporary file will be stored using @env{TMPDIR} environment variable.
-Encryption is performed with
-@url{https://www.schneier.com/academic/twofish/, Twofish} algorithm, 256
-bit random key, zero IV, in
-@url{https://en.wikipedia.org/wiki/Counter_mode#Counter_.28CTR.29, CTR}
-mode.
+Encryption is performed with @url{https://cr.yp.to/chacha.html,
+ChaCha20} algorithm. Data is splitted on 128 KiB blocks. Each block is
+encrypted with increasing nonce counter.
 
 If @option{-chunked} is specified, then source file will be split
 @ref{Chunked, on chunks}. @option{INT} is the desired chunk size in
index baaa718907e820411b32e324bb11711b3475c71f..06c4a452a2bdbf2f2de9ede3d744af1330d34b0c 100644 (file)
@@ -56,12 +56,9 @@ Eblob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure:
     BLAKE2b-256 MAC of encrypted blob
 @end multitable
 
-Blob's encryption is done using
-@url{https://www.schneier.com/academic/twofish/, Twofish} algorithm with
-256-bit key in
-@url{https://en.wikipedia.org/wiki/Counter_mode#Counter_.28CTR.29, CTR}
-mode of operation with zero initialization vector.
-@code{balloon(BLAKE2b-256, S, T, P, salt, password)} gives the main key,
-that is fed to @url{https://en.wikipedia.org/wiki/HKDF,
-HKDF}-BLAKE2b-256 KDF. Actual encryption key for Twofish and
-authentication key for MAC are derived from that KDF.
+Blob's encryption is done using @url{https://cr.yp.to/chacha.html,
+ChaCha20} algorithm. Data is splitted on 128 KiB blocks. Each block is
+encrypted with increasing nonce counter. @code{balloon(BLAKE2b-256, S,
+T, P, salt, password)} gives the main key, that is fed to
+@url{https://blake2.net/, BLAKE2Xb} XOF Actual encryption key for
+ChaCha20 and authentication key for MAC are derived from that XOF.
index ce983f37e2ee634d1d15e6cac71559b60c82cfa7..3325a196eaf6ea4e05d4eeb145e061dd85ac16d6 100644 (file)
@@ -1,6 +1,21 @@
 @node Новости
 @section Новости
 
+@node Релиз 2.0
+@subsection Релиз 2.0
+@itemize
+@item
+@strong{Несовместимое} изменение формата зашифрованных и eblob пакетов.
+Работа со старыми версиями не поддерживается.
+@item
+Алгоритм шифрования Twofish заменён на ChaCha20. Он намного быстрее.
+Одним криптографическим примитивом меньше.
+@item
+HKDF-BLAKE2b-256 KDF алгоритм заменён на BLAKE2Xb XOF. Ещё одним
+криптографическим примитивом меньше (предполагая, что BLAKE2X
+практически идентичен BLAKE2).
+@end itemize
+
 @node Релиз 1.0
 @subsection Релиз 1.0
 @itemize
index 189311da4cee414f8984e567e70fc900d7464bcf..23112e29c27719d3245821a398699b6b91179d63 100644 (file)
@@ -3,6 +3,21 @@
 
 See also this page @ref{Новости, on russian}.
 
+@node Release 2.0
+@section Release 2.0
+@itemize
+@item
+@strong{Incompatible} encrypted/eblob packet format changes. Older
+versions are not supported.
+@item
+Twofish encryption algorithm is replaced with ChaCha20. It is much more
+faster. One cryptographic primitive less.
+@item
+HKDF-BLAKE2b-256 KDF algorithm is replaced with BLAKE2Xb XOF. Yet
+another cryptographic primitive less (assuming that BLAKE2X is nearly
+identical to BLAKE2).
+@end itemize
+
 @node Release 1.0
 @section Release 1.0
 @itemize
index c0103994ee8a24180a19d8ed4527af3d666cb55a..3f8cd92177a1456eef54f4cf592bf6380af10af2 100644 (file)
@@ -103,12 +103,9 @@ Each encrypted packet has the following header:
 
 Signature is calculated over all previous fields.
 
-All following encryption is done using
-@url{https://www.schneier.com/academic/twofish/, Twofish} algorithm with
-256-bit key in
-@url{https://en.wikipedia.org/wiki/Counter_mode#Counter_.28CTR.29, CTR}
-mode of operation with zero initialization vector (because each
-encrypted packet has ephemeral exchange key). @url{https://blake2.net/,
+All following encryption is done using @url{https://cr.yp.to/chacha.html,
+ChaCha20} algorithm. Data is splitted on 128 KiB blocks. Each block is
+encrypted with increasing nonce counter. @url{https://blake2.net/,
 BLAKE2b-256} MAC is appended to the ciphertext.
 
 After the headers comes an encrypted payload size and MAC of that size.
@@ -133,15 +130,15 @@ When node A want to send encrypted packet to node B, it:
 @item takes remote node's exchange public key and performs
     Diffie-Hellman computation on this remote static public key and
     private ephemeral one
-@item derived ephemeral key is used as an input to
-    @url{https://en.wikipedia.org/wiki/HKDF, HKDF}-BLAKE2b-256 KDF
-@item derives four session keys using
-    @url{https://en.wikipedia.org/wiki/HKDF, HKDF}-BLAKE2b-256 KDF:
+@item derived ephemeral key is used as a key input to
+    @url{https://blake2.net/, BLAKE2Xb} XOF
+@item derives five session keys using output from the XOF above:
     @enumerate
-    @item "Size" encryption (for Twofish) key
+    @item "Size" encryption (for ChaCha20) key
     @item "Size" authentication (for BLAKE2b-MAC) key
     @item Payload encryption key
     @item Payload authentication key
+    @item Optional pad generation key (for ChaCha20)
     @end enumerate
 @item encrypts size, appends its ciphertext to the header
 @item appends MAC tag over that ciphertext
index 03870daf529fca70fcab2c631fa2a13c5d1d8eb1..ac000723b35336d11ed6f0990284829ec9f44fb7 100755 (executable)
@@ -34,12 +34,10 @@ golang.org/x/crypto/blake2s
 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
 golang.org/x/crypto/poly1305
 golang.org/x/crypto/salsa20
 golang.org/x/crypto/ssh/terminal
-golang.org/x/crypto/twofish
 golang.org/x/net/AUTHORS
 golang.org/x/net/CONTRIBUTORS
 golang.org/x/net/LICENSE
index d86a5a5c8c64dee2b82fc9e82e78f61958d31ad4..2b3805f0e9439b0dc30701956db911eb73a44ffe 100644 (file)
@@ -1,7 +1,7 @@
 # $FreeBSD$
 
 PORTNAME=      nncp
-PORTVERSION=   1.0
+PORTVERSION=   2.0
 CATEGORIES=    net
 MASTER_SITES=  http://www.nncpgo.org/download/
 
diff --git a/src/chacha20 b/src/chacha20
new file mode 120000 (symlink)
index 0000000..95f85aa
--- /dev/null
@@ -0,0 +1 @@
+golang.org/x/crypto/chacha20poly1305/internal/chacha20
\ No newline at end of file
index 0a1b1d8a0a86d73874e221b8087f5d82ac4734c6..d948a274086438719aa6e09ae36c08f27405c93a 100644 (file)
@@ -339,7 +339,7 @@ func (nodeOur *NodeOur) ToYAML() string {
 
 func CfgParse(data []byte) (*Ctx, error) {
        var err error
-       if bytes.Compare(data[:8], MagicNNCPBv1[:]) == 0 {
+       if bytes.Compare(data[:8], MagicNNCPBv2[:]) == 0 {
                os.Stderr.WriteString("Passphrase:")
                password, err := terminal.ReadPassword(0)
                if err != nil {
index e79f9e14d195d5f5e2879b939524e586e7f45dda..bfa48169e6edd8dc872968b07287738c3019cbeb 100644 (file)
@@ -212,7 +212,7 @@ func main() {
                                ctx.LogD("nncp-bundle", sds, "Bad packet structure")
                                continue
                        }
-                       if pktEnc.Magic != nncp.MagicNNCPEv2 {
+                       if pktEnc.Magic != nncp.MagicNNCPEv3 {
                                ctx.LogD("nncp-bundle", sds, "Bad packet magic number")
                                continue
                        }
index 9376d33256128726939e28eb131c80c7a1b3900f..30f4137b4024980b6bb5470dc6c9769b9a20ea3f 100644 (file)
@@ -79,7 +79,7 @@ func main() {
                if _, err := xdr.Unmarshal(bytes.NewReader(data), &eblob); err != nil {
                        log.Fatalln(err)
                }
-               if eblob.Magic != nncp.MagicNNCPBv1 {
+               if eblob.Magic != nncp.MagicNNCPBv2 {
                        log.Fatalln(errors.New("Unknown eblob type"))
                }
                fmt.Println("Strengthening function: Balloon with BLAKE2b-256")
index a7b346f4042d1b938f9d9e06c3b137895fdd7fef..891298bf3ebde2f3b4d9077aba50f3050c91e39d 100644 (file)
@@ -111,7 +111,7 @@ func main() {
        }
        var pktEnc nncp.PktEnc
        _, err = xdr.Unmarshal(bytes.NewReader(beginning), &pktEnc)
-       if err == nil && pktEnc.Magic == nncp.MagicNNCPEv2 {
+       if err == nil && pktEnc.Magic == nncp.MagicNNCPEv3 {
                if *dump {
                        ctx, err := nncp.CtxFromCmdline(*cfgPath, "", "", false, false)
                        if err != nil {
index ffd1c0c9ae930ede4cc6d8bcd2dcac81fdd5e861..28d2eb27ff40bfa81db7d7d6f5e69b747a1df5d5 100644 (file)
@@ -170,7 +170,7 @@ func main() {
                        }
                        var pktEnc nncp.PktEnc
                        _, err = xdr.Unmarshal(fd, &pktEnc)
-                       if err != nil || pktEnc.Magic != nncp.MagicNNCPEv2 {
+                       if err != nil || pktEnc.Magic != nncp.MagicNNCPEv3 {
                                ctx.LogD("nncp-xfer", sds, "is not a packet")
                                fd.Close()
                                continue
index cb241ab391bc2c862e1168667b30c5808e1d4a1c..5fb402206a173c0ed6a64640125d8a23f9d81ab8 100644 (file)
@@ -20,17 +20,16 @@ package nncp
 
 import (
        "bytes"
-       "crypto/cipher"
        "crypto/rand"
        "crypto/subtle"
        "errors"
+       "hash"
        "io"
 
+       "chacha20"
        "cypherpunks.ru/balloon"
        "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
-       "golang.org/x/crypto/hkdf"
-       "golang.org/x/crypto/twofish"
 )
 
 const (
@@ -40,7 +39,7 @@ const (
 )
 
 var (
-       MagicNNCPBv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 1}
+       MagicNNCPBv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 2}
 )
 
 type EBlob struct {
@@ -53,6 +52,14 @@ type EBlob struct {
        MAC   *[blake2b.Size256]byte
 }
 
+func blake256() hash.Hash {
+       h, err := blake2b.New256(nil)
+       if err != nil {
+               panic(err)
+       }
+       return h
+}
+
 // Create an encrypted blob. sCost -- memory space requirements, number
 // of hash-output sized (32 bytes) blocks. tCost -- time requirements,
 // number of rounds. pCost -- number of parallel jobs.
@@ -63,39 +70,38 @@ func NewEBlob(sCost, tCost, pCost int, password, data []byte) ([]byte, error) {
                return nil, err
        }
        key := balloon.H(blake256, password, salt[:], sCost, tCost, pCost)
-       kdf := hkdf.New(blake256, key, nil, MagicNNCPBv1[:])
-       keyEnc := make([]byte, 32)
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+       kdf, err := blake2b.NewXOF(32+64, key)
+       if err != nil {
                return nil, err
        }
-       keyAuth := make([]byte, 64)
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
+       if _, err = kdf.Write(MagicNNCPBv2[:]); err != nil {
                return nil, err
        }
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       keyEnc := new([32]byte)
+       if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
+               return nil, err
+       }
+       keyAuth := make([]byte, 64)
+       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
                return nil, err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
        mac, err := blake2b.New256(keyAuth)
        if err != nil {
                return nil, err
        }
-       var blob bytes.Buffer
-       mw := io.MultiWriter(&blob, mac)
-       ae := &cipher.StreamWriter{S: ctr, W: mw}
-       if _, err = ae.Write(data); err != nil {
+       chacha20.XORKeyStream(data, data, new([16]byte), keyEnc)
+       if _, err = mac.Write(data); err != nil {
                return nil, err
        }
        macTag := new([blake2b.Size256]byte)
        mac.Sum(macTag[:0])
        eblob := EBlob{
-               Magic: MagicNNCPBv1,
+               Magic: MagicNNCPBv2,
                SCost: uint32(sCost),
                TCost: uint32(tCost),
                PCost: uint32(pCost),
                Salt:  salt,
-               Blob:  blob.Bytes(),
+               Blob:  data,
                MAC:   macTag,
        }
        var eblobRaw bytes.Buffer
@@ -111,7 +117,7 @@ func DeEBlob(eblobRaw, password []byte) ([]byte, error) {
        if _, err = xdr.Unmarshal(bytes.NewReader(eblobRaw), &eblob); err != nil {
                return nil, err
        }
-       if eblob.Magic != MagicNNCPBv1 {
+       if eblob.Magic != MagicNNCPBv2 {
                return nil, BadMagic
        }
        key := balloon.H(
@@ -122,32 +128,31 @@ func DeEBlob(eblobRaw, password []byte) ([]byte, error) {
                int(eblob.TCost),
                int(eblob.PCost),
        )
-       kdf := hkdf.New(blake256, key, nil, MagicNNCPBv1[:])
-       keyEnc := make([]byte, 32)
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+       kdf, err := blake2b.NewXOF(32+64, key)
+       if err != nil {
                return nil, err
        }
-       keyAuth := make([]byte, 64)
-       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
+       if _, err = kdf.Write(MagicNNCPBv2[:]); err != nil {
                return nil, err
        }
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       keyEnc := new([32]byte)
+       if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
+               return nil, err
+       }
+       keyAuth := make([]byte, 64)
+       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
                return nil, err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
        mac, err := blake2b.New256(keyAuth)
        if err != nil {
                return nil, err
        }
-       var blob bytes.Buffer
-       tr := io.TeeReader(bytes.NewReader(eblob.Blob), mac)
-       ae := &cipher.StreamReader{S: ctr, R: tr}
-       if _, err = io.Copy(&blob, ae); err != nil {
+       if _, err = mac.Write(eblob.Blob); err != nil {
                return nil, err
        }
        if subtle.ConstantTimeCompare(mac.Sum(nil), eblob.MAC[:]) != 1 {
                return nil, errors.New("Unauthenticated blob")
        }
-       return blob.Bytes(), nil
+       chacha20.XORKeyStream(eblob.Blob, eblob.Blob, new([16]byte), keyEnc)
+       return eblob.Blob, nil
 }
index 4a59db02d5f60874230c549fea2df61bacef8e18..8630846d254747e1b59e5c75b94b17720389b844 100644 (file)
@@ -64,7 +64,7 @@ func (ctx *Ctx) Jobs(nodeId *NodeId, xx TRxTx) chan Job {
                                continue
                        }
                        var pktEnc PktEnc
-                       if _, err = xdr.Unmarshal(fd, &pktEnc); err != nil || pktEnc.Magic != MagicNNCPEv2 {
+                       if _, err = xdr.Unmarshal(fd, &pktEnc); err != nil || pktEnc.Magic != MagicNNCPEv3 {
                                fd.Close()
                                continue
                        }
index aa7368d96036e7387423ae83e7ee8762875e18cd..c24dad39cca7ed510fe4cad1fe857f805804c69b 100644 (file)
@@ -20,25 +20,26 @@ package nncp
 
 import (
        "bytes"
-       "crypto/cipher"
        "crypto/rand"
        "crypto/subtle"
+       "encoding/binary"
        "errors"
-       "hash"
        "io"
 
+       "chacha20"
        "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
        "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"
 )
 
 type PktType uint8
 
 const (
+       EncBlkSize = 128 * (1 << 10)
+       KDFXOFSize = 2*(32+64) + 32
+
        PktTypeFile PktType = iota
        PktTypeFreq PktType = iota
        PktTypeMail PktType = iota
@@ -55,7 +56,7 @@ const (
 
 var (
        MagicNNCPPv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 1}
-       MagicNNCPEv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 2}
+       MagicNNCPEv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 3}
        BadMagic     error   = errors.New("Unknown magic number")
        BadPktType   error   = errors.New("Unknown packet type")
 
@@ -105,7 +106,7 @@ func init() {
                panic(err)
        }
        pktEnc := PktEnc{
-               Magic:     MagicNNCPEv2,
+               Magic:     MagicNNCPEv3,
                Nice:      123,
                Sender:    dummyId,
                Recipient: dummyId,
@@ -134,14 +135,6 @@ func NewPkt(typ PktType, path string) (*Pkt, error) {
        return &pkt, nil
 }
 
-func blake256() hash.Hash {
-       h, err := blake2b.New256(nil)
-       if err != nil {
-               panic(err)
-       }
-       return h
-}
-
 type DevZero struct{}
 
 func (d DevZero) Read(b []byte) (n int, err error) {
@@ -151,6 +144,35 @@ func (d DevZero) Read(b []byte) (n int, err error) {
        return
 }
 
+func ae(keyEnc *[32]byte, r io.Reader, w io.Writer) (int, error) {
+       var blkCtr uint64
+       ciphNonce := new([16]byte)
+       ciphCtr := ciphNonce[8:]
+       buf := make([]byte, EncBlkSize)
+       var n int
+       var written int
+       var err error
+       for {
+               n, err = io.ReadFull(r, buf)
+               if err != nil {
+                       if err == io.EOF {
+                               break
+                       }
+                       if err != io.ErrUnexpectedEOF {
+                               return written + n, err
+                       }
+               }
+               written += n
+               blkCtr++
+               binary.BigEndian.PutUint64(ciphCtr, blkCtr)
+               chacha20.XORKeyStream(buf[:n], buf[:n], ciphNonce, keyEnc)
+               if _, err = w.Write(buf[:n]); err != nil {
+                       return written, err
+               }
+       }
+       return written, nil
+}
+
 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 {
@@ -161,7 +183,7 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize
                return err
        }
        tbs := PktTbs{
-               Magic:     MagicNNCPEv2,
+               Magic:     MagicNNCPEv3,
                Nice:      nice,
                Sender:    our.Id,
                Recipient: their.Id,
@@ -174,7 +196,7 @@ 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:     MagicNNCPEv2,
+               Magic:     MagicNNCPEv3,
                Nice:      nice,
                Sender:    our.Id,
                Recipient: their.Id,
@@ -186,83 +208,83 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize
        }
        sharedKey := new([32]byte)
        curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)
-       kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv2[:])
-
-       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(MagicNNCPEv3[:]); err != nil {
                return err
        }
 
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       keyEnc := new([32]byte)
+       if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
+               return err
+       }
+       keyAuth := make([]byte, 64)
+       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
                return err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
        mac, err := blake2b.New256(keyAuth)
        if err != nil {
                return err
        }
 
-       mw := io.MultiWriter(out, mac)
-       ae := &cipher.StreamWriter{S: ctr, W: mw}
-       usize := uint64(size)
-       if _, err = xdr.Marshal(ae, &usize); err != nil {
+       sizeBuf := make([]byte, 8)
+       binary.BigEndian.PutUint64(sizeBuf, uint64(size))
+       chacha20.XORKeyStream(sizeBuf, sizeBuf, new([16]byte), keyEnc)
+       if _, err = out.Write(sizeBuf); err != nil {
+               return err
+       }
+       if _, err = mac.Write(sizeBuf); err != nil {
+               return err
+       }
+       if _, err = out.Write(mac.Sum(nil)); err != nil {
                return err
        }
-       ae.Close()
-       out.Write(mac.Sum(nil))
 
-       if _, err = io.ReadFull(kdf, keyEnc); err != 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)
+       mac, err = blake2b.New256(keyAuth)
        if err != nil {
                return err
        }
-       ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-       mac, err = blake2b.New256(keyAuth)
+       lr := io.LimitedReader{data, size}
+       mr := io.MultiReader(&pktBuf, &lr)
+       mw := io.MultiWriter(out, mac)
+       fullSize := pktBuf.Len() + int(size)
+       written, err := ae(keyEnc, mr, mw)
        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 {
+       if written != fullSize {
+               return io.ErrUnexpectedEOF
+       }
+       if _, err = out.Write(mac.Sum(nil)); err != nil {
                return err
        }
-       ae.Close()
-       out.Write(mac.Sum(nil))
-
        if padSize > 0 {
-               if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+               if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
                        return err
                }
-               ciph, err = twofish.NewCipher(keyEnc)
+               lr = io.LimitedReader{DevZero{}, padSize}
+               written, err = ae(keyEnc, &lr, out)
                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 {
-                       return err
+               if written != int(padSize) {
+                       return io.ErrUnexpectedEOF
                }
-               ae.Close()
        }
        return nil
 }
 
 func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
        tbs := PktTbs{
-               Magic:     MagicNNCPEv2,
+               Magic:     MagicNNCPEv3,
                Nice:      pktEnc.Nice,
                Sender:    their.Id,
                Recipient: our.Id,
@@ -281,7 +303,7 @@ func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Wri
        if err != nil {
                return nil, 0, err
        }
-       if pktEnc.Magic != MagicNNCPEv2 {
+       if pktEnc.Magic != MagicNNCPEv3 {
                return nil, 0, BadMagic
        }
        their, known := nodes[*pktEnc.Sender]
@@ -300,31 +322,32 @@ 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, MagicNNCPEv2[:])
-
-       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(MagicNNCPEv3[:]); err != nil {
                return their, 0, err
        }
 
-       ciph, err := twofish.NewCipher(keyEnc)
-       if err != nil {
+       keyEnc := new([32]byte)
+       if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
+               return their, 0, err
+       }
+       keyAuth := make([]byte, 64)
+       if _, err = io.ReadFull(kdf, keyAuth); err != nil {
                return their, 0, err
        }
-       ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
        mac, err := blake2b.New256(keyAuth)
        if err != nil {
                return their, 0, err
        }
 
-       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)
+       if _, err = io.ReadFull(data, sizeBuf); err != nil {
+               return their, 0, err
+       }
+       if _, err = mac.Write(sizeBuf); err != nil {
                return their, 0, err
        }
        tag := make([]byte, blake2b.Size256)
@@ -334,29 +357,29 @@ func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Wri
        if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 {
                return their, 0, errors.New("Unauthenticated size")
        }
-       size := int64(usize)
+       chacha20.XORKeyStream(sizeBuf, sizeBuf, new([16]byte), keyEnc)
+       size := int64(binary.BigEndian.Uint64(sizeBuf))
 
-       if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+       if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil {
                return their, size, err
        }
        if _, err = io.ReadFull(kdf, keyAuth); err != nil {
                return their, size, err
        }
-
-       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)
        if err != nil {
-               return their, size, err
+               return their, 0, 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
+       fullSize := PktOverhead + size - 8 - 2*blake2b.Size256
+       lr := io.LimitedReader{data, fullSize}
+       tr := io.TeeReader(&lr, mac)
+       written, err := ae(keyEnc, tr, out)
+       if err != nil {
+               return their, int64(written), err
+       }
+       if written != int(fullSize) {
+               return their, int64(written), io.ErrUnexpectedEOF
        }
        if _, err = io.ReadFull(data, tag); err != nil {
                return their, size, err
index 47a4ba48a0e5b18ada946cbb250c35520e4bbe73..a5687a72c0a18b5663b432fd06ac019f56c760ac 100644 (file)
@@ -63,7 +63,8 @@ func TestTossEmail(t *testing.T) {
                defer os.RemoveAll(spool)
                nodeOur, err := NewNodeGenerate()
                if err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                ctx := Ctx{
                        Spool:   spool,
@@ -82,7 +83,8 @@ func TestTossEmail(t *testing.T) {
                        }
                        our, err := NewNodeGenerate()
                        if err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                        privates[recipient] = our
                        ctx.Neigh[*our.Id] = our.Their()
@@ -95,7 +97,8 @@ func TestTossEmail(t *testing.T) {
                                []byte{123},
                                1<<15,
                        ); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                }
                for _, recipient := range recipients {
@@ -158,7 +161,8 @@ func TestTossFile(t *testing.T) {
                defer os.RemoveAll(spool)
                nodeOur, err := NewNodeGenerate()
                if err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                ctx := Ctx{
                        Spool:   spool,
@@ -185,7 +189,8 @@ func TestTossFile(t *testing.T) {
                                fileName,
                                1<<15,
                        ); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                }
                rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
@@ -227,7 +232,8 @@ func TestTossFileSameName(t *testing.T) {
                defer os.RemoveAll(spool)
                nodeOur, err := NewNodeGenerate()
                if err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                ctx := Ctx{
                        Spool:   spool,
@@ -245,7 +251,8 @@ func TestTossFileSameName(t *testing.T) {
                        []byte("doesnotmatter"),
                        os.FileMode(0600),
                ); err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                incomingPath := filepath.Join(spool, "incoming")
                for i := 0; i < files; i++ {
@@ -256,7 +263,8 @@ func TestTossFileSameName(t *testing.T) {
                                "samefile",
                                1<<15,
                        ); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                }
                rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
@@ -296,7 +304,8 @@ func TestTossFreq(t *testing.T) {
                defer os.RemoveAll(spool)
                nodeOur, err := NewNodeGenerate()
                if err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                ctx := Ctx{
                        Spool:   spool,
@@ -323,7 +332,8 @@ func TestTossFreq(t *testing.T) {
                                fileName,
                                1<<15,
                        ); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                }
                rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
@@ -356,11 +366,13 @@ func TestTossFreq(t *testing.T) {
                        var buf bytes.Buffer
                        _, _, err := PktEncRead(ctx.Self, ctx.Neigh, job.Fd, &buf)
                        if err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                        var pkt Pkt
                        if _, err = xdr.Unmarshal(&buf, &pkt); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                        dst := string(pkt.Path[:int(pkt.PathLen)])
                        if bytes.Compare(buf.Bytes(), files[dst]) != 0 {
@@ -395,7 +407,8 @@ func TestTossTrns(t *testing.T) {
                defer os.RemoveAll(spool)
                nodeOur, err := NewNodeGenerate()
                if err != nil {
-                       panic(err)
+                       t.Error(err)
+                       return false
                }
                ctx := Ctx{
                        Spool:   spool,
@@ -430,7 +443,8 @@ func TestTossTrns(t *testing.T) {
                                bytes.NewReader(data),
                                &dst,
                        ); err != nil {
-                               panic(err)
+                               t.Error(err)
+                               return false
                        }
                        checksum := blake2b.Sum256(dst.Bytes())
                        if err := ioutil.WriteFile(
index 7faef491cf0f279de5e239a7c77e9c32ee4e11d6..5dff27c3be2aa5dcd030eabb0ea8d1bc20191da6 100644 (file)
@@ -22,7 +22,6 @@ import (
        "bufio"
        "bytes"
        "compress/zlib"
-       "crypto/cipher"
        "crypto/rand"
        "errors"
        "hash"
@@ -35,7 +34,6 @@ import (
 
        "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
-       "golang.org/x/crypto/twofish"
 )
 
 func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size, minSize int64, src io.Reader) (*Node, error) {
@@ -119,25 +117,20 @@ func prepareTxFile(srcPath string) (io.Reader, *os.File, int64, error) {
                }
                os.Remove(src.Name())
                tmpW := bufio.NewWriter(src)
-
-               tmpKey := make([]byte, 32)
-               if _, err = rand.Read(tmpKey); err != nil {
-                       return nil, nil, 0, err
-               }
-               ciph, err := twofish.NewCipher(tmpKey)
-               if err != nil {
+               tmpKey := new([32]byte)
+               if _, err = rand.Read(tmpKey[:]); err != nil {
                        return nil, nil, 0, err
                }
-               ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-               encrypter := &cipher.StreamWriter{S: ctr, W: tmpW}
-               fileSize, err = io.Copy(encrypter, bufio.NewReader(os.Stdin))
+               written, err := ae(tmpKey, bufio.NewReader(os.Stdin), tmpW)
                if err != nil {
                        return nil, nil, 0, err
                }
+               fileSize = int64(written)
                tmpW.Flush()
                src.Seek(0, 0)
-               ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
-               reader = &cipher.StreamReader{S: ctr, R: bufio.NewReader(src)}
+               r, w := io.Pipe()
+               go ae(tmpKey, bufio.NewReader(src), w)
+               reader = r
        } else {
                src, err = os.Open(srcPath)
                if err != nil {