From: Sergey Matveev Date: Sat, 27 Apr 2019 11:09:40 +0000 (+0300) Subject: AEAD encryption mode and new encrypted packet format X-Git-Tag: 4.0^2~10 X-Git-Url: http://www.git.cypherpunks.ru/?p=nncp.git;a=commitdiff_plain;h=05d433befb610aac3922c7a8f2afad2af8e23b4d AEAD encryption mode and new encrypted packet format --- diff --git a/VERSION b/VERSION index 2f4b607..5186d07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4 +4.0 diff --git a/common.mk b/common.mk index 322d8d2..145e843 100644 --- a/common.mk +++ b/common.mk @@ -38,69 +38,65 @@ ALL = \ all: $(ALL) -src/cypherpunks.ru/nncp/internal/chacha20: src/golang.org/x/crypto/internal/chacha20 src/golang.org/x/crypto/internal/subtle - $(MAKE) -C src/cypherpunks.ru/nncp/internal - -nncp-bundle: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-bundle: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-bundle -nncp-call: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-call: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-call -nncp-caller: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-caller: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-caller -nncp-cfgenc: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-cfgenc: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-cfgenc -nncp-cfgmin: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-cfgmin: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-cfgmin -nncp-cfgnew: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-cfgnew: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-cfgnew -nncp-check: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-check: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-check -nncp-daemon: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-daemon: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-daemon -nncp-exec: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-exec: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-exec -nncp-file: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-file: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-file -nncp-freq: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-freq: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-freq -nncp-log: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-log: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-log -nncp-pkt: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-pkt: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-pkt -nncp-reass: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-reass: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-reass -nncp-rm: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-rm: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-rm -nncp-stat: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-stat: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-stat -nncp-toss: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-toss: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-toss -nncp-xfer: src/cypherpunks.ru/nncp/internal/chacha20 +nncp-xfer: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-xfer -test: src/cypherpunks.ru/nncp/internal/chacha20 +test: GOPATH=$(GOPATH) go test -failfast cypherpunks.ru/nncp/... clean: rm -f $(ALL) - rm -fr src/cypherpunks.ru/nncp/internal/chacha20 .PHONY: doc diff --git a/doc/cmds.texi b/doc/cmds.texi index 5c95467..9628fa0 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -299,12 +299,13 @@ This command queues file in @ref{Spool, spool} directory immediately If @file{SRC} equals to @file{-}, then create an encrypted temporary file and copy everything taken from stdin to it and use for outbound 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://cr.yp.to/chacha.html, -ChaCha20} algorithm. Data is splitted on 128 KiB blocks. Each block is -encrypted with increasing nonce counter. +taken from stdin, then you have to have more than 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 in AEAD mode with +@url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305} +algorithms. 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 @@ -348,6 +349,7 @@ Parse @ref{Log, log} file and print out its records in human-readable form. @verbatim % nncp-pkt [options] < pkt % nncp-pkt [options] [-decompress] -dump < pkt > payload +% nncp-pkt -overheads @end verbatim Low level packet parser. Normally it should not be used, but can help in @@ -380,6 +382,8 @@ And with the @option{-dump} option it will give you the actual payload tries to zlib-decompress the data from plain packet (useful for mail packets). +@option{-overheads} options print encrypted, plain and size header overheads. + @node nncp-reass @section nncp-reass diff --git a/doc/eblob.texi b/doc/eblob.texi index 74e6661..45de886 100644 --- a/doc/eblob.texi +++ b/doc/eblob.texi @@ -32,16 +32,16 @@ winner). Eblob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure: @verbatim -+-------+------------------+------------+ -| MAGIC | S | T | P | SALT | BLOB | MAC | -+-------+------------------+------------+ ++-------+------------------+------+ +| MAGIC | S | T | P | SALT | BLOB | ++-------+------------------+------+ @end verbatim @multitable @columnfractions 0.2 0.3 0.5 @headitem @tab XDR type @tab Value @item Magic number @tab 8-byte, fixed length opaque data @tab - @verb{|N N C P B 0x00 0x00 0x02|} + @verb{|N N C P B 0x00 0x00 0x03|} @item S, T, P @tab unsigned integer @tab Space cost, time cost and parallel jobs number @@ -50,22 +50,19 @@ Eblob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure: Randomly generated salt @item Blob @tab variable length opaque data @tab - Encrypted data itself -@item MAC @tab - 32 bytes, fixed length opaque data @tab - BLAKE2b-256 MAC of encrypted blob + Authenticated and Encrypted data itself @end multitable @enumerate @item generate the main key using @code{balloon(BLAKE2b-256, S, T, P, salt, password)} @item initialize @url{https://blake2.net/, BLAKE2Xb} XOF with generated -main key and 96-byte output length -@item feed @verb{|N N C P B 0x00 0x00 0x02|} magic number to XOF -@item read 32-bytes of blob encryption key -@item read 64-bytes of blob authentication key -@item encrypt the blob using @url{https://cr.yp.to/chacha.html, -ChaCha20}. Blob is splitted on 128 KiB blocks. Each block is encrypted -with increasing nonce counter -@item authenticate ciphertext with MAC +main key and 32-byte output length +@item feed @verb{|N N C P B 0x00 0x00 0x03|} magic number to XOF +@item read 32-bytes of blob AEAD encryption key +@item encrypt and authenticate blob using + @url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305}. + Blob is splitted on 128 KiB blocks. Each block is encrypted with + increasing nonce counter. Eblob packet itself, with empty blob + field, is fed as an additional authenticated data @end enumerate diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 46e6357..53b0c2a 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -1,6 +1,21 @@ @node Новости @section Новости +@node Релиз 4.0 +@subsection Релиз 4.0 +@itemize +@item +@strong{Несовместимое} изменение формата зашифрованных и eblob пакетов: +используется AEAD режим шифрования с 128 КиБ блоками, так как раньше +@command{nncp-toss} не проверял MAC зашифрованного пакета прежде чем +отсылать дешифрованные данные внешней команде. Старые версии не +поддерживаются. +@item +Зависимые библиотеки обновлены. +@item +Небольшие исправления ошибок. +@end itemize + @node Релиз 3.4 @subsection Релиз 3.4 @itemize diff --git a/doc/news.texi b/doc/news.texi index 16f1d6f..1cec8dd 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,20 @@ See also this page @ref{Новости, on russian}. +@node Release 4.0 +@section Release 4.0 +@itemize +@item +@strong{Incompatible} encrypted and eblob packet format change: AEAD +encryption mode with 128 KiB blocks is used now, because previously +@command{nncp-toss} did not verify encrypted packet's MAC before feeding +decrypted data to external command. Older versions are not supported. +@item +Dependant libraries are updated. +@item +Minor bugfixes. +@end itemize + @node Release 3.4 @section Release 3.4 @itemize diff --git a/doc/pkt.texi b/doc/pkt.texi index b1c2c38..d216f93 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -28,7 +28,7 @@ drive. @headitem @tab XDR type @tab Value @item Magic number @tab 8-byte, fixed length opaque data @tab - @verb{|N N C P P 0x00 0x00 0x01|} + @verb{|N N C P P 0x00 0x00 0x02|} @item Payload type @tab unsigned integer @tab 0 (file), 1 (freq), 2 (exec), 3 (transition) @@ -78,11 +78,11 @@ storages and that are synchronized between TCP daemons. Each encrypted packet has the following header: @verbatim - +------------ HEADER --------------------+ +-------- ENCRYPTED --------+ - / \ / \ -+--------------------------------------------+------------+----...-----------+------+ -| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | SIZE | MAC | CIPHERTEXT | MAC | JUNK | -+-------------------------------------/------\------------+----...-----------+------+ + +------------ HEADER --------------------+ +------------- ENCRYPTED -------------+ + / \ / \ ++--------------------------------------------+------+---------+----------...---+------+ +| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | SIZE | BLOCK 0 | BLOCK 1 ... | JUNK | ++-------------------------------------/------\------+---------+----------...---+------+ / \ +-------------------------------------+ | MAGIC | NICE | SENDER | RCPT | EPUB | @@ -93,7 +93,7 @@ Each encrypted packet has the following header: @headitem @tab XDR type @tab Value @item Magic number @tab 8-byte, fixed length opaque data @tab - @verb{|N N C P E 0x00 0x00 0x03|} + @verb{|N N C P E 0x00 0x00 0x04|} @item Niceness @tab unsigned integer @tab 1-255, packet @ref{Niceness, niceness} level @@ -113,12 +113,12 @@ Each encrypted packet has the following header: Signature is calculated over all previous fields. -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. +All following encryption is done in AEAD mode using +@url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305} +algorithms. Data is splitted on 128 KiB blocks. Each block is encrypted with +increasing nonce counter. -After the headers comes an encrypted payload size and MAC of that size. +Authenticated and encrypted size come after the header: @multitable @columnfractions 0.2 0.3 0.5 @headitem @tab XDR type @tab Value @@ -127,7 +127,7 @@ After the headers comes an encrypted payload size and MAC of that size. Payload size. @end multitable -Next comes the actual encrypted payload with corresponding MAC. +Then comes the actual payload. Each node has static @strong{exchange} and @strong{signature} keypairs. When node A want to send encrypted packet to node B, it: @@ -143,18 +143,15 @@ When node A want to send encrypted packet to node B, it: @item derive the keys: @enumerate @item initialize @url{https://blake2.net/, BLAKE2Xb} XOF with - derived ephemeral key and 224-byte output length - @item feed @verb{|N N C P E 0x00 0x00 0x03|} magic number to XOF - @item read 32-bytes of "size" encryption key (for ChaCha20) - @item read 64-bytes of "size" authentication key (for BLAKE2b-MAC) - @item read 32-bytes of payload encryption key - @item read 64-bytes of payload authentication key - @item optionally read 32-bytes pad generation key (for ChaCha20) + derived ephemeral key and 96-byte output length + @item feed @verb{|N N C P E 0x00 0x00 0x04|} magic number to XOF + @item read 32-bytes of "size" AEAD encryption key + @item read 32-bytes of payload AEAD encryption key + @item optionally read 32-bytes pad generation key @end enumerate -@item encrypts size, appends its ciphertext to the header -@item appends MAC tag over that ciphertext -@item encrypts and appends payload ciphertext -@item appends MAC tag over that payload ciphertext +@item encrypts size, appends its authenticated ciphertext to the header +@item encrypts payload, appends its authenticated ciphertext @item possibly appends any kind of "junk" noise data to hide real - payload's size from the adversary + payload's size from the adversary (generated using XOF with + unlimited output length) @end enumerate diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index 79ad5ca..9519f96 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -371,7 +371,7 @@ func (nodeOur *NodeOur) ToYAML() string { func CfgParse(data []byte) (*Ctx, error) { var err error - if bytes.Compare(data[:8], MagicNNCPBv2[:]) == 0 { + if bytes.Compare(data[:8], MagicNNCPBv3[:]) == 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 eaa61a4..553a6d1 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go @@ -236,7 +236,7 @@ func main() { ctx.LogD("nncp-bundle", sds, "Bad packet structure") continue } - if pktEnc.Magic != nncp.MagicNNCPEv3 { + if pktEnc.Magic != nncp.MagicNNCPEv4 { 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 5947f67..be009f4 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.MagicNNCPBv2 { + if eblob.Magic != nncp.MagicNNCPBv3 { 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 f632741..1ccea9e 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go @@ -31,7 +31,6 @@ import ( "cypherpunks.ru/nncp" "github.com/davecgh/go-xdr/xdr2" - "golang.org/x/crypto/blake2b" ) func usage() { @@ -44,6 +43,7 @@ func usage() { func main() { var ( + overheads = flag.Bool("overheads", false, "Print packet overheads") dump = flag.Bool("dump", false, "Write decrypted/parsed payload to stdout") decompress = flag.Bool("decompress", false, "Try to zlib decompress dumped data") cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") @@ -61,8 +61,18 @@ func main() { return } + if *overheads { + fmt.Printf( + "Plain: %d\nEncrypted: %d\nSize: %d\n", + nncp.PktOverhead, + nncp.PktEncOverhead, + nncp.PktSizeOverhead, + ) + return + } + var err error - beginning := make([]byte, nncp.PktOverhead-8-2*blake2b.Size256) + beginning := make([]byte, nncp.PktOverhead) if _, err = io.ReadFull(os.Stdin, beginning); err != nil { log.Fatalln("Not enough data to read") } @@ -121,7 +131,7 @@ func main() { } var pktEnc nncp.PktEnc _, err = xdr.Unmarshal(bytes.NewReader(beginning), &pktEnc) - if err == nil && pktEnc.Magic == nncp.MagicNNCPEv3 { + if err == nil && pktEnc.Magic == nncp.MagicNNCPEv4 { 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 b8c6cc6..102292f 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.MagicNNCPEv3 { + if err != nil || pktEnc.Magic != nncp.MagicNNCPEv4 { 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 51f9709..816388d 100644 --- a/src/cypherpunks.ru/nncp/eblob.go +++ b/src/cypherpunks.ru/nncp/eblob.go @@ -21,15 +21,12 @@ package nncp import ( "bytes" "crypto/rand" - "crypto/subtle" - "errors" "hash" - "io" "cypherpunks.ru/balloon" - "cypherpunks.ru/nncp/internal/chacha20" "github.com/davecgh/go-xdr/xdr2" "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/chacha20poly1305" ) const ( @@ -39,7 +36,7 @@ const ( ) var ( - MagicNNCPBv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 2} + MagicNNCPBv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 3} ) type EBlob struct { @@ -49,7 +46,6 @@ type EBlob struct { PCost uint32 Salt *[32]byte Blob []byte - MAC *[blake2b.Size256]byte } func blake256() hash.Hash { @@ -69,46 +65,31 @@ func NewEBlob(sCost, tCost, pCost int, password, data []byte) ([]byte, error) { if _, err = rand.Read(salt[:]); err != nil { return nil, err } - key := balloon.H(blake256, password, salt[:], sCost, tCost, pCost) - kdf, err := blake2b.NewXOF(32+64, key) - if err != nil { - return nil, err - } - if _, err = kdf.Write(MagicNNCPBv2[:]); err != nil { - return nil, err - } - 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 - } - mac, err := blake2b.New256(keyAuth) - if err != nil { - return nil, err - } - 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: MagicNNCPBv2, + Magic: MagicNNCPBv3, SCost: uint32(sCost), TCost: uint32(tCost), PCost: uint32(pCost), Salt: salt, - Blob: data, - MAC: macTag, + Blob: nil, } - var eblobRaw bytes.Buffer - if _, err = xdr.Marshal(&eblobRaw, &eblob); err != nil { + var eblobBuf bytes.Buffer + if _, err = xdr.Marshal(&eblobBuf, &eblob); err != nil { return nil, err } - return eblobRaw.Bytes(), nil + key := balloon.H(blake256, password, salt[:], sCost, tCost, pCost) + aead, err := chacha20poly1305.New(key) + if err != nil { + return nil, err + } + buf := make([]byte, 0, len(data)+aead.Overhead()) + buf = aead.Seal(buf, make([]byte, aead.NonceSize()), data, eblobBuf.Bytes()) + eblob.Blob = buf + eblobBuf.Reset() + if _, err = xdr.Marshal(&eblobBuf, &eblob); err != nil { + return nil, err + } + return eblobBuf.Bytes(), nil } func DeEBlob(eblobRaw, password []byte) ([]byte, error) { @@ -117,7 +98,7 @@ func DeEBlob(eblobRaw, password []byte) ([]byte, error) { if _, err = xdr.Unmarshal(bytes.NewReader(eblobRaw), &eblob); err != nil { return nil, err } - if eblob.Magic != MagicNNCPBv2 { + if eblob.Magic != MagicNNCPBv3 { return nil, BadMagic } key := balloon.H( @@ -128,31 +109,25 @@ func DeEBlob(eblobRaw, password []byte) ([]byte, error) { int(eblob.TCost), int(eblob.PCost), ) - kdf, err := blake2b.NewXOF(32+64, key) + aead, err := chacha20poly1305.New(key) if err != nil { return nil, err } - if _, err = kdf.Write(MagicNNCPBv2[:]); err != nil { - return nil, err - } - 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 { + + ciphertext := eblob.Blob + eblob.Blob = nil + var eblobBuf bytes.Buffer + if _, err = xdr.Marshal(&eblobBuf, &eblob); err != nil { return nil, err } - mac, err := blake2b.New256(keyAuth) + data, err := aead.Open( + ciphertext[:0], + make([]byte, aead.NonceSize()), + ciphertext, + eblobBuf.Bytes(), + ) if err != nil { return nil, err } - 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") - } - chacha20.XORKeyStream(eblob.Blob, eblob.Blob, new([16]byte), keyEnc) - return eblob.Blob, nil + return data, nil } diff --git a/src/cypherpunks.ru/nncp/internal/.gitignore b/src/cypherpunks.ru/nncp/internal/.gitignore deleted file mode 100644 index c1dfcb2..0000000 --- a/src/cypherpunks.ru/nncp/internal/.gitignore +++ /dev/null @@ -1 +0,0 @@ -chacha20 diff --git a/src/cypherpunks.ru/nncp/internal/Makefile b/src/cypherpunks.ru/nncp/internal/Makefile deleted file mode 100644 index 2bc00c7..0000000 --- a/src/cypherpunks.ru/nncp/internal/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -chacha20: - mkdir chacha20 - cp ../../../golang.org/x/crypto/internal/chacha20/* chacha20 - cp ../../../golang.org/x/crypto/internal/subtle/aliasing.go chacha20 - sed -i.bak 's/package subtle.*$$/package chacha20/' chacha20/aliasing.go - sed -i.bak '/internal.subtle/d ; s/subtle\.//g' chacha20/chacha_generic.go diff --git a/src/cypherpunks.ru/nncp/jobs.go b/src/cypherpunks.ru/nncp/jobs.go index f3aa9e9..ee7052d 100644 --- a/src/cypherpunks.ru/nncp/jobs.go +++ b/src/cypherpunks.ru/nncp/jobs.go @@ -65,7 +65,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 != MagicNNCPEv3 { + if _, err = xdr.Unmarshal(fd, &pktEnc); err != nil || pktEnc.Magic != MagicNNCPEv4 { fd.Close() continue } diff --git a/src/cypherpunks.ru/nncp/pkt.go b/src/cypherpunks.ru/nncp/pkt.go index 1c6607f..e157bef 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" "io" - "cypherpunks.ru/nncp/internal/chacha20" "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/nacl/box" + "golang.org/x/crypto/poly1305" ) type PktType uint8 const ( EncBlkSize = 128 * (1 << 10) - KDFXOFSize = 2*(32+64) + 32 + KDFXOFSize = chacha20poly1305.KeySize * 2 PktTypeFile PktType = iota PktTypeFreq PktType = iota @@ -52,12 +53,13 @@ const ( var ( MagicNNCPPv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 2} - MagicNNCPEv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 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") - PktOverhead int64 - PktEncOverhead int64 + PktOverhead int64 + PktEncOverhead int64 + PktSizeOverhead int64 = 8 + poly1305.TagSize ) type Pkt struct { @@ -95,7 +97,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") @@ -103,7 +105,7 @@ func init() { panic(err) } pktEnc := PktEnc{ - Magic: MagicNNCPEv3, + Magic: MagicNNCPEv4, Nice: 123, Sender: dummyId, Recipient: dummyId, @@ -132,42 +134,59 @@ func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) { return &pkt, nil } -type DevZero struct{} - -func (d DevZero) Read(b []byte) (n int, err error) { - for n = 0; n < len(b); n++ { - b[n] = 0 - } - return -} - -func ae(keyEnc *[32]byte, r io.Reader, w io.Writer) (int, error) { +func aeadProcess( + aead cipher.AEAD, + nonce []byte, + doEncrypt bool, + r io.Reader, + w io.Writer) (int, error) { var blkCtr uint64 - ciphNonce := new([16]byte) - ciphCtr := ciphNonce[8:] - buf := make([]byte, EncBlkSize) + ciphCtr := nonce[len(nonce)-8:] + buf := make([]byte, EncBlkSize+aead.Overhead()) + var toRead []byte + var toWrite []byte var n int - var written int + var readBytes int var err error + if doEncrypt { + toRead = buf[:EncBlkSize] + } else { + toRead = buf + } for { - n, err = io.ReadFull(r, buf) + n, err = io.ReadFull(r, toRead) if err != nil { if err == io.EOF { break } if err != io.ErrUnexpectedEOF { - return written + n, err + return readBytes + n, err } } - written += n + readBytes += 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 + 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 written, nil + return readBytes, nil +} + +func sizeWithTags(size int64) (fullSize int64) { + fullSize = size + (size/EncBlkSize)*poly1305.TagSize + if size%EncBlkSize != 0 { + fullSize += poly1305.TagSize + } + return } func PktEncWrite( @@ -187,7 +206,7 @@ func PktEncWrite( return err } tbs := PktTbs{ - Magic: MagicNNCPEv3, + Magic: MagicNNCPEv4, Nice: nice, Sender: our.Id, Recipient: their.Id, @@ -200,7 +219,7 @@ func PktEncWrite( signature := new([ed25519.SignatureSize]byte) copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes())) pktEnc := PktEnc{ - Magic: MagicNNCPEv3, + Magic: MagicNNCPEv4, Nice: nice, Sender: our.Id, Recipient: their.Id, @@ -216,71 +235,46 @@ func PktEncWrite( if err != nil { return err } - if _, err = kdf.Write(MagicNNCPEv3[:]); err != nil { + if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil { return err } - keyEnc := new([32]byte) - if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil { + key := make([]byte, chacha20poly1305.KeySize) + if _, err = io.ReadFull(kdf, key); err != nil { return err } - keyAuth := make([]byte, 64) - if _, err = io.ReadFull(kdf, keyAuth); err != nil { - return err - } - mac, err := blake2b.New256(keyAuth) + aead, err := chacha20poly1305.New(key) if err != nil { return err } + nonce := make([]byte, aead.NonceSize()) - 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 { + 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 } - if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil { - return err - } - if _, err = io.ReadFull(kdf, keyAuth); err != nil { - return err - } - mac, err = blake2b.New256(keyAuth) - if err != nil { - return err - } lr := io.LimitedReader{R: data, N: size} mr := io.MultiReader(&pktBuf, &lr) - mw := io.MultiWriter(out, mac) - fullSize := pktBuf.Len() + int(size) - written, err := ae(keyEnc, mr, mw) + written, err := aeadProcess(aead, nonce, true, mr, out) if err != nil { return err } if written != fullSize { return io.ErrUnexpectedEOF } - if _, err = out.Write(mac.Sum(nil)); err != nil { - return err - } if padSize > 0 { - if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil { + if _, err = io.ReadFull(kdf, key); err != nil { return err } - lr = io.LimitedReader{R: DevZero{}, N: padSize} - written, err = ae(keyEnc, &lr, out) + kdf, err = blake2b.NewXOF(blake2b.OutputLengthUnknown, key) if err != nil { return err } - if written != int(padSize) { - return io.ErrUnexpectedEOF + if _, err = io.CopyN(out, kdf, padSize); err != nil { + return err } } return nil @@ -288,7 +282,7 @@ func PktEncWrite( func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) { tbs := PktTbs{ - Magic: MagicNNCPEv3, + Magic: MagicNNCPEv4, Nice: pktEnc.Nice, Sender: their.Id, Recipient: our.Id, @@ -311,7 +305,7 @@ func PktEncRead( if err != nil { return nil, 0, err } - if pktEnc.Magic != MagicNNCPEv3 { + if pktEnc.Magic != MagicNNCPEv4 { return nil, 0, BadMagic } their, known := nodes[*pktEnc.Sender] @@ -334,66 +328,37 @@ func PktEncRead( if err != nil { return their, 0, err } - if _, err = kdf.Write(MagicNNCPEv3[:]); err != nil { + if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil { return their, 0, err } - 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 { + key := make([]byte, chacha20poly1305.KeySize) + if _, err = io.ReadFull(kdf, key); err != nil { return their, 0, err } - mac, err := blake2b.New256(keyAuth) + aead, err := chacha20poly1305.New(key) if err != nil { return their, 0, err } + nonce := make([]byte, aead.NonceSize()) - sizeBuf := make([]byte, 8) + sizeBuf := make([]byte, 8+aead.Overhead()) 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) - if _, err = io.ReadFull(data, tag); err != nil { - return their, 0, err - } - if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 { - return their, 0, errors.New("Unauthenticated size") - } - chacha20.XORKeyStream(sizeBuf, sizeBuf, new([16]byte), keyEnc) - size := int64(binary.BigEndian.Uint64(sizeBuf)) - - if _, err = io.ReadFull(kdf, keyEnc[:]); err != nil { - return their, size, err - } - if _, err = io.ReadFull(kdf, keyAuth); err != nil { - return their, size, err - } - mac, err = blake2b.New256(keyAuth) + sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, nil) if err != nil { return their, 0, err } + size := int64(binary.BigEndian.Uint64(sizeBuf)) - fullSize := PktOverhead + size - 8 - 2*blake2b.Size256 - lr := io.LimitedReader{R: data, N: fullSize} - tr := io.TeeReader(&lr, mac) - written, err := ae(keyEnc, tr, out) + lr := io.LimitedReader{R: data, N: size} + written, err := aeadProcess(aead, nonce, false, &lr, out) if err != nil { return their, int64(written), err } - if written != int(fullSize) { + if written != int(size) { return their, int64(written), io.ErrUnexpectedEOF } - if _, err = io.ReadFull(data, tag); err != nil { - return their, size, err - } - if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 { - return their, size, errors.New("Unauthenticated payload") - } return their, size, nil } diff --git a/src/cypherpunks.ru/nncp/pkt_test.go b/src/cypherpunks.ru/nncp/pkt_test.go index 3e3523c..43602d7 100644 --- a/src/cypherpunks.ru/nncp/pkt_test.go +++ b/src/cypherpunks.ru/nncp/pkt_test.go @@ -120,7 +120,7 @@ func TestPktEncRead(t *testing.T) { if *node.Id != *node1.Id { return false } - if sizeGot != int64(size) { + if sizeGot != sizeWithTags(PktOverhead+int64(size)) { return false } var pktBuf bytes.Buffer diff --git a/src/cypherpunks.ru/nncp/toss.go b/src/cypherpunks.ru/nncp/toss.go index bcfeb15..c7522e8 100644 --- a/src/cypherpunks.ru/nncp/toss.go +++ b/src/cypherpunks.ru/nncp/toss.go @@ -37,6 +37,7 @@ import ( "github.com/davecgh/go-xdr/xdr2" "github.com/dustin/go-humanize" "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/poly1305" ) const ( @@ -87,12 +88,18 @@ func (ctx *Ctx) Toss( var pkt Pkt var err error var pktSize int64 + var pktSizeBlocks int64 if _, err = xdr.Unmarshal(pipeR, &pkt); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "unmarshal") isBad = true goto Closing } - pktSize = job.Size - PktEncOverhead - PktOverhead + pktSize = job.Size - PktEncOverhead - PktOverhead - PktSizeOverhead + pktSizeBlocks = pktSize / (EncBlkSize + poly1305.TagSize) + if pktSize%(EncBlkSize+poly1305.TagSize) != 0 { + pktSize -= poly1305.TagSize + } + pktSize -= pktSizeBlocks * poly1305.TagSize sds["size"] = strconv.FormatInt(pktSize, 10) ctx.LogD("rx", sds, "taken") switch pkt.Type { diff --git a/src/cypherpunks.ru/nncp/tx.go b/src/cypherpunks.ru/nncp/tx.go index b3f8da5..db260ce 100644 --- a/src/cypherpunks.ru/nncp/tx.go +++ b/src/cypherpunks.ru/nncp/tx.go @@ -34,6 +34,7 @@ import ( "github.com/davecgh/go-xdr/xdr2" "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/chacha20poly1305" ) func (ctx *Ctx) Tx( @@ -53,7 +54,11 @@ func (ctx *Ctx) Tx( lastNode = ctx.Neigh[*node.Via[i-1]] hops = append(hops, lastNode) } - padSize := minSize - size - int64(len(hops))*(PktOverhead+PktEncOverhead) + expectedSize := size + for i := 0; i < len(hops); i++ { + expectedSize = PktEncOverhead + PktSizeOverhead + sizeWithTags(PktOverhead+expectedSize) + } + padSize := minSize - expectedSize if padSize < 0 { padSize = 0 } @@ -69,12 +74,11 @@ func (ctx *Ctx) Tx( errs <- PktEncWrite(ctx.Self, hops[0], pkt, nice, size, padSize, src, dst) dst.Close() }(curSize, src, pipeW) - curSize += padSize + curSize = PktEncOverhead + PktSizeOverhead + sizeWithTags(PktOverhead+curSize) + padSize var pipeRPrev io.Reader for i := 1; i < len(hops); i++ { pktTrns, _ := NewPkt(PktTypeTrns, 0, hops[i-1].Id[:]) - curSize += PktOverhead + PktEncOverhead pipeRPrev = pipeR pipeR, pipeW = io.Pipe() go func(node *Node, pkt *Pkt, size int64, src io.Reader, dst io.WriteCloser) { @@ -86,6 +90,7 @@ func (ctx *Ctx) Tx( errs <- PktEncWrite(ctx.Self, node, pkt, nice, size, 0, src, dst) dst.Close() }(hops[i], pktTrns, curSize, pipeRPrev, pipeW) + curSize = PktEncOverhead + PktSizeOverhead + sizeWithTags(PktOverhead+curSize) } go func() { _, err := io.Copy(tmp.W, pipeR) @@ -116,11 +121,16 @@ func prepareTxFile(srcPath string) (io.Reader, *os.File, int64, error) { } os.Remove(src.Name()) tmpW := bufio.NewWriter(src) - tmpKey := new([32]byte) + tmpKey := make([]byte, chacha20poly1305.KeySize) if _, err = rand.Read(tmpKey[:]); err != nil { return nil, nil, 0, err } - written, err := ae(tmpKey, bufio.NewReader(os.Stdin), tmpW) + aead, err := chacha20poly1305.New(tmpKey) + if err != nil { + return nil, nil, 0, err + } + nonce := make([]byte, aead.NonceSize()) + written, err := aeadProcess(aead, nonce, true, bufio.NewReader(os.Stdin), tmpW) if err != nil { return nil, nil, 0, err } @@ -130,7 +140,11 @@ func prepareTxFile(srcPath string) (io.Reader, *os.File, int64, error) { } src.Seek(0, io.SeekStart) r, w := io.Pipe() - go ae(tmpKey, bufio.NewReader(src), w) + go func() { + if _, err := aeadProcess(aead, nonce, false, bufio.NewReader(src), w); err != nil { + panic(err) + } + }() reader = r } else { src, err = os.Open(srcPath)