VERSION | 2 +- doc/cmds.texi | 8 +++----- doc/download.texi | 4 ++++ doc/eblob.texi | 24 ++++++++++++++---------- doc/news.ru.texi | 15 +++++++++++++++ doc/news.texi | 15 +++++++++++++++ doc/pkt.texi | 32 +++++++++++++++----------------- doc/workflow.texi | 12 ++++++++++++ makedist.sh | 8 +++----- ports/nncp/Makefile | 2 +- src/cypherpunks.ru/nncp/cfg.go | 2 +- src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go | 2 +- src/cypherpunks.ru/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 +++++++-------------- diff --git a/VERSION b/VERSION index e224e77bf7ecd5b287819d199ce8a75dba225909a19a0679dd966dba7d61fa70..2c858ab6c54dddd5d5d3f908545751c6fc591a0d5740edad717a56e61e03b2b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0 +2.0 diff --git a/doc/cmds.texi b/doc/cmds.texi index 31480106e85400bb6b085a1f8900f381f799a3320f94bd63963eee52a6744e4d..89a796d3d3c57ef21dd2f14cb6e70d9350538ced5f485aa00306e8eee9cb07ca 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/download.texi b/doc/download.texi index 665b2e3563eb9abd225b66f9848717d18474a9c133f3fb86e130a258ab1145a8..eefff755b9df5bd979e29f57164ae4ef0f1e6c052f713a3645629aa9e968c5f2 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -23,6 +23,10 @@ @multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} @headitem Version @tab Size @tab Tarball @tab SHA256 checksum +@item @ref{Release 1.0, 1.0} @tab 987 KiB +@tab @url{download/nncp-1.0.tar.xz, link} @url{download/nncp-1.0.tar.xz.sig, sign} +@tab @code{68BF7803 CD25F59A 56D9FD6C 695002B5 BFBAF591 8A6583F4 3139FC28 CA1AB4AF} + @item @ref{Release 0.12, 0.12} @tab 978 KiB @tab @url{download/nncp-0.12.tar.xz, link} @url{download/nncp-0.12.tar.xz.sig, sign} @tab @code{707B4005 97753B29 73A5F3E5 DAB51B92 21CC296D 690EF4BC ADE93E0D 2595A5F2} diff --git a/doc/eblob.texi b/doc/eblob.texi index 54bda4d0609e1e6a0377dc9df546f9dfe68a50311399e6957835aa77f678a5da..7c4ae400d39b4ea4f17981e6d3cb1a79ff542bc17c2c33af4dc199adbf16f1ea 100644 --- a/doc/eblob.texi +++ b/doc/eblob.texi @@ -41,7 +41,7 @@ @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 0x01|} + @verb{|N N C P B 0x00 0x00 0x02|} @item S, T, P @tab unsigned integer @tab Space cost, time cost and parallel jobs number @@ -56,12 +56,16 @@ 32 bytes, fixed length opaque data @tab 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. +@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 +@end enumerate diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 32b2d8d158a367f5e70c347cf3291411c36dcc1e03079ef42457507b22b484c7..5e4c9f94856602b89715cc7b44d9149ff77b64c5a48482de90247f25f134d5d3 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 2b20115fce2fe44eb9aa8766f442e6b7bf815a30f46b098506a8f831bea5ab8b..b17253862b959309278491942488721e1a7ddd9efe1ad9665120cfb6016a4797 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,21 @@ @unnumbered News 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 91c984e001434178a9ac8b9b7c56e08e3cf146676c187c068dba397380694069..c46331e31444c6cfde9d7d3e88b6588f147cacfe33f12828bc22855aedf5acc7 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -68,8 +68,8 @@ Each encrypted packet has the following header: @verbatim - +------------ HEADER -------------+ +-------- ENCRYPTED --------+ - / \ / \ + +------------ HEADER --------------------+ +-------- ENCRYPTED --------+ + / \ / \ +--------------------------------------------+------------+----...-----------+------+ | MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | SIZE | MAC | CIPHERTEXT | MAC | JUNK | +-------------------------------------/------\------------+----...-----------+------+ @@ -83,7 +83,7 @@ @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 E 0x00 0x00 0x01|} + @verb{|N N C P E 0x00 0x00 0x03|} @item Niceness @tab unsigned integer @tab 1-255, packet @ref{Niceness, niceness} level @@ -103,12 +103,9 @@ @end multitable 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,16 @@ @url{http://ed25519.cr.yp.to/, ed25519} signature key @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 derive the keys: @enumerate - @item "Size" encryption (for Twofish) key - @item "Size" authentication (for BLAKE2b-MAC) key - @item Payload encryption key - @item Payload authentication key + @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) @end enumerate @item encrypts size, appends its ciphertext to the header @item appends MAC tag over that ciphertext diff --git a/doc/workflow.texi b/doc/workflow.texi index 6e6bf59d1825c34d7212d9804c507e68fcebc9a73f62762859eb105bea26debf..e1a9998f27b5dd063e8171c4efad2d7bedde26da279670879866e619dee82507 100644 --- a/doc/workflow.texi +++ b/doc/workflow.texi @@ -24,9 +24,21 @@ @item run either @ref{nncp-call} or @ref{nncp-caller} to initiate connection to required nodes from time to time @item use @ref{nncp-xfer} with removable storage devices for copying packets to/from other nodes + @item use @ref{nncp-bundle} with either sequential storage devices + or broadcasting transmissions for copying packets @end itemize @item After successful packet exchanging (or just simply from time to time), run @ref{nncp-toss} for tossing (decrypting and processing) all inbound queues to receive mail messages, files, file requests and relay transition packets to other nodes. @end enumerate + +@itemize +@item If you wish to encrypt your configuration file containing your +private keys, then use @ref{nncp-cfgenc} utility. You can always use an +encrypted config without decrypting it in temporary memory file. +@item If you wish to strip off any private keys from your config, then +use @ref{nncp-cfgmin} utility. It will be useful for transferring +messages with offline methods, but tossing them later on the machine +with private keys. +@end itemize diff --git a/makedist.sh b/makedist.sh index 26b5ef07d4bf01237be008e45387c07c580098d375adbcb15e1b28b99b099d73..65b854d567b63c456ffe9cfb36911d1cf3595e4ea9957feb4fc68146bd211af5 100755 --- a/makedist.sh +++ b/makedist.sh @@ -28,29 +28,27 @@ golang.org/x/crypto/AUTHORS golang.org/x/crypto/CONTRIBUTORS golang.org/x/crypto/LICENSE golang.org/x/crypto/PATENTS -golang.org/x/crypto/README +golang.org/x/crypto/README.md golang.org/x/crypto/blake2b 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 golang.org/x/net/PATENTS -golang.org/x/net/README +golang.org/x/net/README.md golang.org/x/net/netutil golang.org/x/sys/AUTHORS golang.org/x/sys/CONTRIBUTORS golang.org/x/sys/LICENSE golang.org/x/sys/PATENTS -golang.org/x/sys/README +golang.org/x/sys/README.md golang.org/x/sys/unix EOF tar cfCI - src $tmp/includes | tar xfC - $tmp diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index a559eda21a12c5b43c8fe30148a6a1be0dcfdfd4186e6de6006aa82087d482fe..28533f7f6a0209d7028e55d3cc0ca4ea7383daf3f5b926c38054074961723b2d 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -1,7 +1,7 @@ # $FreeBSD$ PORTNAME= nncp -PORTVERSION= 0.12 +PORTVERSION= 2.0 CATEGORIES= net MASTER_SITES= http://www.nncpgo.org/download/ diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index d62761b160c12fa276b6f1c7c2a91ef4f024edd4481408f72f38c28c636faa12..1e1dd6e00c2d0ffc4f60205bb0212e7cf41ddf7ae23fc2b69626fb8a046a4f25 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -339,7 +339,7 @@ } 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 72919502e0d73e3c22ccf2e160e5a8c0c79aaf2e284dcd236684fb105e04ac98..46eb946c2d202a80d5b9c0d0650e6f391994fc08823093cd24085c799a3de196 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 @@ if _, err = xdr.Unmarshal(bytes.NewReader(pktEncBuf), &pktEnc); err != nil { 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 73e4a689cc0406f32a9a628aab95c98f6f875e0ec619b8534443674cbe0db218..06e08f12aea1b0aa9d966a55b6661605170f59528664885cbd47221b1cc73787 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 @@ var eblob nncp.EBlob 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 88aa06912f0c6ce9dc63eb24f3e2e4026da33eaedd525a6f3870233454a5aec6..1dd7bf1555f7ac9a39c8c5ce55cfa2e48104db3f5c1aae7ad4ee5c8dae1ce6da 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 @@ return } 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 13b6ec17e8027839d4cbc2d26629bcdaf61679dc7ecbdb227d3a910025391214..cc0690dec4215d7a0b83ec56bf7f3cf6eb69665a64af1be899e5c66f68b07a79 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 @@ continue } 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 f3d4c700e5aba1c67901277e8e14a162657a9f7adf8425f362382bb78d4f5db7..7e401f105746eee80aaf9b1c902c6b7beba9785e63868f95b9d29705fb2e1e23 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 @@ DefaultP = 2 ) 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 @@ Blob []byte 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 @@ if _, err = rand.Read(salt[:]); err != nil { 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 + } + 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 } - ciph, err := twofish.NewCipher(keyEnc) - if 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 @@ var err 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 @@ int(eblob.SCost), 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 6b6be55ca5b572ab9a67c3f71afafcf7040bcc75828cf06289499614468099ca..574ab15ecff256706ca2c9d344ee56041a2a3a3eb3bf115d00527aa1cf3af3fa 100644 --- a/src/cypherpunks.ru/nncp/jobs.go +++ b/src/cypherpunks.ru/nncp/jobs.go @@ -64,7 +64,7 @@ if err != nil { 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 d6a31adf80d9f54ee2813e41244fe307328b2cd8b57db17c5ed351ff213043ea..43195080b78fb588b81d4381469c4d96c6269177543761104d8ac4e4b8ae79e3 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 @@ ) 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 @@ if err != nil { panic(err) } pktEnc := PktEnc{ - Magic: MagicNNCPEv2, + Magic: MagicNNCPEv3, Nice: 123, Sender: dummyId, Recipient: dummyId, @@ -134,14 +135,6 @@ copy(pkt.Path[:], pb) 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 @@ } 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 @@ if _, err := xdr.Marshal(&pktBuf, pkt); err != nil { return err } tbs := PktTbs{ - Magic: MagicNNCPEv2, + Magic: MagicNNCPEv3, Nice: nice, Sender: our.Id, Recipient: their.Id, @@ -174,7 +196,7 @@ } 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 @@ return err } sharedKey := new([32]byte) curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub) - kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv2[:]) + kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:]) + if err != nil { + return err + } + if _, err = kdf.Write(MagicNNCPEv3[:]); err != nil { + return err + } - keyEnc := make([]byte, 32) - if _, err = io.ReadFull(kdf, keyEnc); 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 } + mac, err := blake2b.New256(keyAuth) + if err != nil { + return err + } - ciph, err := twofish.NewCipher(keyEnc) - if 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 } - ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize)) - mac, err := blake2b.New256(keyAuth) - if err != nil { + if _, err = mac.Write(sizeBuf); 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 { + 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 @@ _, err := xdr.Unmarshal(data, &pktEnc) 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 @@ return their, 0, errors.New("Invalid signature") } 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 } - ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize)) + keyAuth := make([]byte, 64) + if _, err = io.ReadFull(kdf, keyAuth); err != nil { + return their, 0, err + } 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 @@ } 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 7fe80a1b5e34e94e48b9cdb4f404b4fd73a0af13c55a86636fbab935df9365db..5dd901e5425df26bb48631a67a5484cb8bf914ceb795811786bba29924956a2c 100644 --- a/src/cypherpunks.ru/nncp/toss_test.go +++ b/src/cypherpunks.ru/nncp/toss_test.go @@ -63,7 +63,8 @@ } 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 @@ continue } 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 @@ "recipient", []byte{123}, 1<<15, ); err != nil { - panic(err) + t.Error(err) + return false } } for _, recipient := range recipients { @@ -158,7 +161,8 @@ } 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 @@ src, 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 @@ } 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 @@ srcPath, []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 @@ srcPath, "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 @@ } 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 @@ fileName, 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 @@ for job := range ctx.Jobs(ctx.Self.Id, TTx) { 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 @@ } 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 @@ 0, 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 e50a6d1d4318fb1b5c02900c68289b72a43f4477da44c3329242d9d3e7173e56..f63d061fb456035fa19308d02bd3a07f2af67bd8bc9b780ebd2b7200ff402207 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 @@ "strings" "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 @@ return nil, nil, 0, err } os.Remove(src.Name()) tmpW := bufio.NewWriter(src) - - tmpKey := make([]byte, 32) - if _, err = rand.Read(tmpKey); err != nil { + tmpKey := new([32]byte) + if _, err = rand.Read(tmpKey[:]); err != nil { return nil, nil, 0, err } - ciph, err := twofish.NewCipher(tmpKey) + written, err := ae(tmpKey, bufio.NewReader(os.Stdin), tmpW) if 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)) - 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 {