From 535d386941ae38abbaa8e1a6df69a5e739058011 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 28 Dec 2017 21:28:46 +0300 Subject: [PATCH] Replace Twofish/HKDF with ChaCha20/BLAKE2X for speed and simplicity --- VERSION | 2 +- doc/cmds.texi | 8 +- doc/eblob.texi | 15 +- doc/news.ru.texi | 15 ++ doc/news.texi | 15 ++ doc/pkt.texi | 19 +- makedist.sh | 2 - ports/nncp/Makefile | 2 +- src/chacha20 | 1 + src/cypherpunks.ru/nncp/cfg.go | 2 +- .../nncp/cmd/nncp-bundle/main.go | 2 +- .../nncp/cmd/nncp-cfgenc/main.go | 2 +- src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go | 2 +- src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go | 2 +- src/cypherpunks.ru/nncp/eblob.go | 69 +++---- src/cypherpunks.ru/nncp/jobs.go | 2 +- src/cypherpunks.ru/nncp/pkt.go | 181 ++++++++++-------- src/cypherpunks.ru/nncp/toss_test.go | 42 ++-- src/cypherpunks.ru/nncp/tx.go | 21 +- 19 files changed, 230 insertions(+), 174 deletions(-) create mode 120000 src/chacha20 diff --git a/VERSION b/VERSION index d3827e7..cd5ac03 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0 +2.0 diff --git a/doc/cmds.texi b/doc/cmds.texi index 1de83a4..9868443 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -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 diff --git a/doc/eblob.texi b/doc/eblob.texi index baaa718..06c4a45 100644 --- a/doc/eblob.texi +++ b/doc/eblob.texi @@ -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. diff --git a/doc/news.ru.texi b/doc/news.ru.texi index ce983f3..3325a19 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -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 diff --git a/doc/news.texi b/doc/news.texi index 189311d..23112e2 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -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 diff --git a/doc/pkt.texi b/doc/pkt.texi index c010399..3f8cd92 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -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 diff --git a/makedist.sh b/makedist.sh index 03870da..ac00072 100755 --- a/makedist.sh +++ b/makedist.sh @@ -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 diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index d86a5a5..2b3805f 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -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 index 0000000..95f85aa --- /dev/null +++ b/src/chacha20 @@ -0,0 +1 @@ +golang.org/x/crypto/chacha20poly1305/internal/chacha20 \ No newline at end of file diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index 0a1b1d8..d948a27 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -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 { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go index e79f9e1..bfa4816 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go @@ -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 } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-cfgenc/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-cfgenc/main.go index 9376d33..30f4137 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-cfgenc/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-cfgenc/main.go @@ -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") diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go index a7b346f..891298b 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go @@ -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 { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go index ffd1c0c..28d2eb2 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go @@ -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 diff --git a/src/cypherpunks.ru/nncp/eblob.go b/src/cypherpunks.ru/nncp/eblob.go index cb241ab..5fb4022 100644 --- a/src/cypherpunks.ru/nncp/eblob.go +++ b/src/cypherpunks.ru/nncp/eblob.go @@ -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 } diff --git a/src/cypherpunks.ru/nncp/jobs.go b/src/cypherpunks.ru/nncp/jobs.go index 4a59db0..8630846 100644 --- a/src/cypherpunks.ru/nncp/jobs.go +++ b/src/cypherpunks.ru/nncp/jobs.go @@ -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 } diff --git a/src/cypherpunks.ru/nncp/pkt.go b/src/cypherpunks.ru/nncp/pkt.go index aa7368d..c24dad3 100644 --- a/src/cypherpunks.ru/nncp/pkt.go +++ b/src/cypherpunks.ru/nncp/pkt.go @@ -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 diff --git a/src/cypherpunks.ru/nncp/toss_test.go b/src/cypherpunks.ru/nncp/toss_test.go index 47a4ba4..a5687a7 100644 --- a/src/cypherpunks.ru/nncp/toss_test.go +++ b/src/cypherpunks.ru/nncp/toss_test.go @@ -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( diff --git a/src/cypherpunks.ru/nncp/tx.go b/src/cypherpunks.ru/nncp/tx.go index 7faef49..5dff27c 100644 --- a/src/cypherpunks.ru/nncp/tx.go +++ b/src/cypherpunks.ru/nncp/tx.go @@ -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 { -- 2.44.0