environment variable.
@item -debug
Print debug messages. Normally this option should not be used.
+@item -minsize
+ Minimal required resulting packet size. For example if you send 2
+ KiB file and set @option{-minsize 4096}, then resulting packet will
+ be 4 KiB (containing file itself and some junk).
@item -nice
Set desired outgoing packet niceness level. 1-255 values are
allowed. Higher value means lower priority. In some commands that
var (
cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
niceRaw = flag.Int("nice", nncp.DefaultNiceMail, "Outbound packet niceness")
+ minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size")
quiet = flag.Bool("quiet", false, "Print only errors")
debug = flag.Bool("debug", false, "Print debug messages")
version = flag.Bool("version", false, "Print version information")
log.Fatalln("Invalid NODE specified:", err)
}
- if err = ctx.TxFile(node, nice, flag.Arg(0), splitted[1]); err != nil {
+ if err = ctx.TxFile(node, nice, flag.Arg(0), splitted[1], int64(*minSize)); err != nil {
log.Fatalln(err)
}
}
var (
cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
niceRaw = flag.Int("nice", nncp.DefaultNiceMail, "Outbound packet niceness")
+ minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size")
quiet = flag.Bool("quiet", false, "Print only errors")
debug = flag.Bool("debug", false, "Print debug messages")
version = flag.Bool("version", false, "Print version information")
log.Fatalln("Invalid NODE specified:", err)
}
- if err = ctx.TxFreq(node, nice, splitted[1], flag.Arg(1)); err != nil {
+ if err = ctx.TxFreq(node, nice, splitted[1], flag.Arg(1), int64(*minSize)); err != nil {
log.Fatalln(err)
}
}
fmt.Fprintln(os.Stderr, "nncp-mail -- send email\n")
fmt.Fprintf(os.Stderr, "Usage: %s [options] NODE USER ...\nOptions:\n", os.Args[0])
flag.PrintDefaults()
- fmt.Fprintln(os.Stderr, "Email body is read from stdin.")
}
func main() {
var (
cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
niceRaw = flag.Int("nice", nncp.DefaultNiceMail, "Outbound packet niceness")
+ minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size")
quiet = flag.Bool("quiet", false, "Print only errors")
debug = flag.Bool("debug", false, "Print debug messages")
version = flag.Bool("version", false, "Print version information")
log.Fatalln("Can not read mail body from stdin:", err)
}
- if err = ctx.TxMail(node, nice, strings.Join(flag.Args()[1:], " "), body); err != nil {
+ if err = ctx.TxMail(node, nice, strings.Join(flag.Args()[1:], " "), body, int64(*minSize)); err != nil {
log.Fatalln(err)
}
}
"cypherpunks.ru/nncp"
"github.com/davecgh/go-xdr/xdr2"
- "github.com/dustin/go-humanize"
"golang.org/x/crypto/blake2b"
)
}
var err error
- beginning := make([]byte, nncp.PktOverhead - 8 - 2*blake2b.Size256)
+ beginning := make([]byte, nncp.PktOverhead-8-2*blake2b.Size256)
if _, err = io.ReadFull(os.Stdin, beginning); err != nil {
log.Fatalln("Not enough data to read")
}
log.Fatalln("Can not parse config:", err)
}
bufW := bufio.NewWriter(os.Stdout)
- if _, err = nncp.PktEncRead(
+ if _, _, err = nncp.PktEncRead(
ctx.Self,
ctx.Neigh,
io.MultiReader(
return h
}
-func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size int64, data io.Reader, out io.Writer) error {
+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 PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize int64, data io.Reader, out io.Writer) error {
pubEph, prvEph, err := box.GenerateKey(rand.Reader)
if err != nil {
return err
curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)
kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:])
- // Derive keys
- keyEnc4Size := make([]byte, 32)
- if _, err = io.ReadFull(kdf, keyEnc4Size); err != nil {
- return err
- }
- keyAuth4Size := make([]byte, 64)
- if _, err = io.ReadFull(kdf, keyAuth4Size); err != nil {
- return err
- }
keyEnc := make([]byte, 32)
if _, err = io.ReadFull(kdf, keyEnc); err != nil {
return err
return err
}
- // Initialize ciphers and MACs
- ciph4Size, err := twofish.NewCipher(keyEnc4Size)
- if err != nil {
- return err
- }
- ctr4Size := cipher.NewCTR(ciph4Size, make([]byte, twofish.BlockSize))
- mac4Size, err := blake2b.New256(keyAuth4Size)
- if err != nil {
- return err
- }
ciph, err := twofish.NewCipher(keyEnc)
if err != nil {
return err
return err
}
- mw := io.MultiWriter(out, mac4Size)
- ae := &cipher.StreamWriter{S: ctr4Size, W: mw}
+ mw := io.MultiWriter(out, mac)
+ ae := &cipher.StreamWriter{S: ctr, W: mw}
usize := uint64(size)
if _, err = xdr.Marshal(ae, &usize); err != nil {
return err
}
- out.Write(mac4Size.Sum(nil))
+ ae.Close()
+ out.Write(mac.Sum(nil))
+
+ if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+ return err
+ }
+ if _, err = io.ReadFull(kdf, keyAuth); err != nil {
+ return err
+ }
+
+ ciph, err = twofish.NewCipher(keyEnc)
+ if err != nil {
+ return err
+ }
+ ctr = cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
+ mac, err = blake2b.New256(keyAuth)
+ 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, int64(size)); err != nil {
+ if _, err = io.CopyN(ae, data, size); err != nil {
return err
}
ae.Close()
out.Write(mac.Sum(nil))
+
+ if padSize > 0 {
+ if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+ return err
+ }
+ ciph, err = twofish.NewCipher(keyEnc)
+ 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
+ }
+ ae.Close()
+ }
return nil
}
return ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil
}
-func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Writer) (*Node, error) {
+func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Writer) (*Node, int64, error) {
var pktEnc PktEnc
_, err := xdr.Unmarshal(data, &pktEnc)
if err != nil {
- return nil, err
+ return nil, 0, err
}
if pktEnc.Magic != MagicNNCPEv1 {
- return nil, BadMagic
+ return nil, 0, BadMagic
}
their, known := nodes[*pktEnc.Sender]
if !known {
- return nil, errors.New("Unknown sender")
+ return nil, 0, errors.New("Unknown sender")
}
verified, err := TbsVerify(our, their, &pktEnc)
if err != nil {
- return nil, err
+ return nil, 0, err
}
if !verified {
- return their, errors.New("Invalid signature")
+ return their, 0, errors.New("Invalid signature")
}
sharedKey := new([32]byte)
curve25519.ScalarMult(sharedKey, our.ExchPrv, pktEnc.ExchPub)
kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:])
- // Derive keys
- keyEnc4Size := make([]byte, 32)
- if _, err = io.ReadFull(kdf, keyEnc4Size); err != nil {
- return their, err
- }
- keyAuth4Size := make([]byte, 64)
- if _, err = io.ReadFull(kdf, keyAuth4Size); err != nil {
- return their, err
- }
keyEnc := make([]byte, 32)
if _, err = io.ReadFull(kdf, keyEnc); err != nil {
- return their, err
+ return their, 0, err
}
keyAuth := make([]byte, 64)
if _, err = io.ReadFull(kdf, keyAuth); err != nil {
- return their, err
+ return their, 0, err
}
- // Initialize ciphers and MACs
- ciph4Size, err := twofish.NewCipher(keyEnc4Size)
- if err != nil {
- return their, err
- }
- ctr4Size := cipher.NewCTR(ciph4Size, make([]byte, twofish.BlockSize))
- mac4Size, err := blake2b.New256(keyAuth4Size)
- if err != nil {
- return their, err
- }
ciph, err := twofish.NewCipher(keyEnc)
if err != nil {
- return their, err
+ return their, 0, err
}
ctr := cipher.NewCTR(ciph, make([]byte, twofish.BlockSize))
mac, err := blake2b.New256(keyAuth)
if err != nil {
- return their, err
+ return their, 0, err
}
- tr := io.TeeReader(data, mac4Size)
- ae := &cipher.StreamReader{S: ctr4Size, R: tr}
+ tr := io.TeeReader(data, mac)
+ ae := &cipher.StreamReader{S: ctr, R: tr}
var usize uint64
if _, err = xdr.Unmarshal(ae, &usize); err != nil {
- return their, err
+ return their, 0, err
}
tag := make([]byte, blake2b.Size256)
if _, err = io.ReadFull(data, tag); err != nil {
- return their, err
+ return their, 0, err
+ }
+ if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 {
+ return their, 0, errors.New("Unauthenticated size")
+ }
+ size := int64(usize)
+
+ if _, err = io.ReadFull(kdf, keyEnc); err != nil {
+ return their, size, err
}
- if subtle.ConstantTimeCompare(mac4Size.Sum(nil), tag) != 1 {
- return their, errors.New("Unauthenticated payload")
+ 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
}
tr = io.TeeReader(data, mac)
ae = &cipher.StreamReader{S: ctr, R: tr}
- if _, err = io.CopyN(out, ae, PktOverhead+int64(usize)-8-blake2b.Size256-blake2b.Size256); err != nil {
- return their, err
+ if _, err = io.CopyN(out, ae, PktOverhead+size-8-blake2b.Size256-blake2b.Size256); err != nil {
+ return their, size, err
}
if _, err = io.ReadFull(data, tag); err != nil {
- return their, err
+ return their, size, err
}
if subtle.ConstantTimeCompare(mac.Sum(nil), tag) != 1 {
- return their, errors.New("Unauthenticated payload")
+ return their, size, errors.New("Unauthenticated payload")
}
- return their, nil
+ return their, size, nil
}
if err != nil {
panic(err)
}
- f := func(path string, pathSize uint8, data [1 << 16]byte, size uint16) bool {
+ f := func(path string, pathSize uint8, data [1 << 16]byte, size, padSize uint16) bool {
dataR := bytes.NewReader(data[:])
var ct bytes.Buffer
if len(path) > int(pathSize) {
if err != nil {
panic(err)
}
- err = PktEncWrite(nodeOur, nodeTheir.Their(), pkt, 123, int64(size), dataR, &ct)
+ err = PktEncWrite(
+ nodeOur,
+ nodeTheir.Their(),
+ pkt,
+ 123,
+ int64(size),
+ int64(padSize),
+ dataR,
+ &ct,
+ )
if err != nil {
return false
}
if err != nil {
panic(err)
}
- f := func(path string, pathSize uint8, data [1 << 16]byte, size uint16, junk []byte) bool {
+ f := func(path string, pathSize uint8, data [1 << 16]byte, size, padSize uint16, junk []byte) bool {
dataR := bytes.NewReader(data[:])
var ct bytes.Buffer
if len(path) > int(pathSize) {
if err != nil {
panic(err)
}
- err = PktEncWrite(node1, node2.Their(), pkt, 123, int64(size), dataR, &ct)
+ err = PktEncWrite(
+ node1,
+ node2.Their(),
+ pkt,
+ 123,
+ int64(size),
+ int64(padSize),
+ dataR,
+ &ct,
+ )
if err != nil {
return false
}
var pt bytes.Buffer
nodes := make(map[NodeId]*Node)
nodes[*node1.Id] = node1.Their()
- node, err := PktEncRead(node2, nodes, &ct, &pt)
+ node, sizeGot, err := PktEncRead(node2, nodes, &ct, &pt)
if err != nil {
return false
}
if *node.Id != *node1.Id {
return false
}
+ if sizeGot != int64(size) {
+ return false
+ }
var pktBuf bytes.Buffer
xdr.Marshal(&pktBuf, &pkt)
return bytes.Compare(pt.Bytes(), append(pktBuf.Bytes(), data[:int(size)]...)) == 0
errs := make(chan error, 1)
go func(job Job) {
pipeWB := bufio.NewWriter(pipeW)
- _, err := PktEncRead(
+ _, _, err := PktEncRead(
ctx.Self,
ctx.Neigh,
bufio.NewReader(job.Fd),
goto Closing
}
if !dryRun {
- if err = ctx.TxFile(sender, job.PktEnc.Nice, filepath.Join(*freq, src), dst); err != nil {
+ if err = ctx.TxFile(sender, job.PktEnc.Nice, filepath.Join(*freq, src), dst, 0); err != nil {
ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "tx file")
isBad = true
goto Closing
DefaultNiceMail,
"recipient",
[]byte{123},
+ 1<<15,
); err != nil {
panic(err)
}
DefaultNiceFile,
src,
fileName,
+ 1<<15,
); err != nil {
panic(err)
}
DefaultNiceFile,
srcPath,
"samefile",
+ 1<<15,
); err != nil {
panic(err)
}
DefaultNiceFreq,
fileName,
fileName,
+ 1<<15,
); err != nil {
panic(err)
}
}
for job := range ctx.Jobs(ctx.Self.Id, TTx) {
var buf bytes.Buffer
- _, err := PktEncRead(ctx.Self, ctx.Neigh, job.Fd, &buf)
+ _, _, err := PktEncRead(ctx.Self, ctx.Neigh, job.Fd, &buf)
if err != nil {
panic(err)
}
&pktTrans,
123,
int64(len(data)),
+ 0,
bytes.NewReader(data),
&dst,
); err != nil {
"golang.org/x/crypto/blake2b"
)
-func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size int64, src io.Reader) (*Node, error) {
+func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size, minSize int64, src io.Reader) (*Node, error) {
tmp, err := ctx.NewTmpFileWHash()
if err != nil {
return nil, err
lastNode = ctx.Neigh[*node.Via[i-1]]
hops = append(hops, lastNode)
}
+ padSize := minSize - size - int64(len(hops)) * (PktOverhead + PktEncOverhead)
+ if padSize < 0 {
+ padSize = 0
+ }
errs := make(chan error)
curSize := size
pipeR, pipeW := io.Pipe()
"nice": strconv.Itoa(int(nice)),
"size": strconv.FormatInt(size, 10),
}, "wrote")
- errs <- PktEncWrite(ctx.Self, hops[0], pkt, nice, size, src, dst)
+ errs <- PktEncWrite(ctx.Self, hops[0], pkt, nice, size, padSize, src, dst)
dst.Close()
}(curSize, src, pipeW)
+ curSize += padSize
var pipeRPrev io.Reader
for i := 1; i < len(hops); i++ {
"nice": strconv.Itoa(int(nice)),
"size": strconv.FormatInt(size, 10),
}, "trns wrote")
- errs <- PktEncWrite(ctx.Self, node, pkt, nice, size, src, dst)
+ errs <- PktEncWrite(ctx.Self, node, pkt, nice, size, 0, src, dst)
dst.Close()
}(hops[i], &pktTrans, curSize, pipeRPrev, pipeW)
}
return lastNode, err
}
-func (ctx *Ctx) TxFile(node *Node, nice uint8, srcPath, dstPath string) error {
+func (ctx *Ctx) TxFile(node *Node, nice uint8, srcPath, dstPath string, minSize int64) error {
if dstPath == "" {
dstPath = filepath.Base(srcPath)
}
if err != nil {
return err
}
- _, err = ctx.Tx(node, pkt, nice, srcStat.Size(), bufio.NewReader(src))
+ _, err = ctx.Tx(node, pkt, nice, srcStat.Size(), minSize, bufio.NewReader(src))
if err == nil {
ctx.LogI("tx", SDS{
"type": "file",
return err
}
-func (ctx *Ctx) TxFreq(node *Node, nice uint8, srcPath, dstPath string) error {
+func (ctx *Ctx) TxFreq(node *Node, nice uint8, srcPath, dstPath string, minSize int64) error {
dstPath = filepath.Clean(dstPath)
if filepath.IsAbs(dstPath) {
return errors.New("Relative destination path required")
}
src := strings.NewReader(dstPath)
size := int64(src.Len())
- _, err = ctx.Tx(node, pkt, nice, size, src)
+ _, err = ctx.Tx(node, pkt, nice, size, minSize, src)
if err == nil {
ctx.LogI("tx", SDS{
"type": "freq",
return err
}
-func (ctx *Ctx) TxMail(node *Node, nice uint8, recipient string, body []byte) error {
+func (ctx *Ctx) TxMail(node *Node, nice uint8, recipient string, body []byte, minSize int64) error {
pkt, err := NewPkt(PktTypeMail, recipient)
if err != nil {
return err
}
compressor.Close()
size := int64(compressed.Len())
- _, err = ctx.Tx(node, pkt, nice, size, &compressed)
+ _, err = ctx.Tx(node, pkt, nice, size, minSize, &compressed)
if err == nil {
ctx.LogI("tx", SDS{
"type": "mail",
)
func TestTx(t *testing.T) {
- f := func(hops uint8, pathSrc, data string, nice uint8) bool {
+ f := func(hops uint8, pathSrc, data string, nice uint8, padSize int16) bool {
if len(pathSrc) > int(MaxPathSize) {
pathSrc = pathSrc[:MaxPathSize]
}
}
pkt, err := NewPkt(PktTypeMail, pathSrc)
src := strings.NewReader(data)
- dstNode, err := ctx.Tx(nodeTgt, pkt, 123, int64(src.Len()), src)
+ dstNode, err := ctx.Tx(
+ nodeTgt,
+ pkt,
+ 123,
+ int64(src.Len()),
+ int64(padSize),
+ src,
+ )
if err != nil {
return false
}
vias := append(nodeTgt.Via, nodeTgt.Id)
for i, hopId := range vias {
hopOur := privates[*hopId]
- foundNode, err := PktEncRead(hopOur, ctx.Neigh, &bufR, &bufW)
+ foundNode, _, err := PktEncRead(hopOur, ctx.Neigh, &bufR, &bufW)
if err != nil {
return false
}