Title: Asynchronous secure file transfer with nncp | |
Author: Solène | |
Date: 04 October 2024 | |
Tags: privacy security network unix | |
Description: In this blog post, you will learn about the software nncp | |
and how to use it to exchange encrypted data with peers | |
# Introduction | |
nncp (node to node copy) is a software to securely exchange data | |
between peers. Is it command line only, it is written in Go and | |
compiles on Linux and BSD systems (although it is only packaged for | |
FreeBSD in BSDs). | |
The website will do a better job than me to talk about the numerous | |
features, but I will do my best to explain what you can do with it and | |
how to use it. | |
nncp official project website | |
# Explanations | |
nncp is a suite of tools to asynchronously exchange data between peers, | |
using zero knowledge encryption. Once peers have exchanged their | |
public keys, they are able to encrypt data to send to this peer, this | |
is nothing really new to be honest, but there is a twist. | |
* a peer can directly connect to another using TCP, you can even | |
configure different addresses like a tor onion or I2P host and use the | |
one you want | |
* a peer can connect to another using ssh | |
* a peer can generate plain files that will be carried over USB, | |
network storage, synchronization software, whatever, to be consumed by | |
a peer. Files can be split in chunks of arbitrary size in order to | |
prevent anyone snooping from figuring how many files are exchanged or | |
their name (hence zero knowledge). | |
* a peer can generate data to burn on a CD or tape (it is working as a | |
stream of data instead of plain files) | |
* a peer can be reachable through another relay peer | |
* when a peer receives files, nncp generates ACK files | |
(acknowledgement) that will tell you they correctly received it | |
* a peer can request files and/or trigger pre-configured commands you | |
expose to this peer | |
* a peer can send emails with nncp (requires a specific setup on the | |
email server) | |
* data transfer can be interrupted and resumed | |
What is cool with nncp is that files you receive are unpacked in a | |
given directory and their integrity is verified. This is sometimes | |
more practical than a network share in which you are never sure when | |
you can move / rename / modify / delete the file that was transferred | |
to you. | |
I identified a few "realistic" use cases with nncp: | |
* exchange files between air gap environments (I tried to exchange | |
files over sound or QR codes, I found no reliable open source solution) | |
* secure file exchange over physical medium with delivery notification | |
(the medium needs to do a round-trip for the notification) | |
* start a torrent download remotely, prepare the file to send back once | |
downloaded, retrieve the file at your own pace | |
* reliable data transfer over poor connections (although I am not sure | |
if it beats kermit at this task :D ) | |
* "simple" file exchange between computers / people over network | |
This let a lot of room for other imaginative use cases. | |
# Real world example: Syncthing gateway | |
My preferred workflow with nncp that I am currently using is a group of | |
three syncthing servers. | |
Each syncthing server is running on a different computer, the location | |
does not really matter. There is a single share between these | |
syncthing instances. | |
The servers where syncthing are running have incoming and outgoing | |
directories exposed over a NFS / SMB share, with a directory named | |
after each peer in both directories. Deposing a file in the "outgoing" | |
directory of a peer will make nncp to prepare the file for this peer, | |
put it into the syncthing share and let it share, the file is consumed | |
in the process. | |
In the same vein, in the incoming directory, new files are unpacked in | |
the incoming directory of emitting peer on the receiver server running | |
syncthing. | |
Why is it cool? You can just drop a file in the peer you want to send | |
to, it disappears locally and magically appears on the remote side. If | |
something wrong happens, due to ACK, you can verify if the file was | |
delivered and unpacked. With three shares, you can almost have two | |
connected at the same time. | |
It is a pretty good file deposit that requires no knowledge to use. | |
This could be implemented with pure syncthing, however you would have | |
to: | |
* for each peer, configure a one-way directory share in syncthing for | |
each other peer to upload data to | |
* for each peer, configure a one-way directory share in syncthing for | |
each other peer to receive data from | |
* for each peer, configure an encrypted share to relay all one way | |
share from other peers | |
This does not scale well. | |
Side note, I am using syncthing because it is fun and requires no | |
infrastructure. But actually, a webdav filesystem, a Nextcloud drive | |
or anything to share data over the network would work just fine. | |
# Setup | |
## Configuration file and private keys | |
On each peer, you have to generate a configuration file with its | |
private keys. The default path for the configuration file is | |
`/etc/nncp.hjson` but nothing prevents you from storing this file | |
anywhere, you will have to use the parameter `-cfg /path/to/config` | |
file in that case. | |
Generate the file like this: | |
``` | |
nncp-cfgnew > /etc/nncp.hjson | |
``` | |
The file contains comments, this is helpful if you want to see how the | |
file is structured and existing options. Never share the private keys | |
of this file! | |
I recommend checking the spool and log paths, and decide which user | |
should use nncp. For instance, you can use `/var/spool/nncp` to store | |
nncp data (waiting to be delivered or unpacked) and the log file, and | |
make your user the owner of this directory. | |
## Public keys | |
Now, generate the public keys (they are just derived from the private | |
keys generated earlier) to share with your peers, there is a command | |
for this that will read the private keys and output the public keys in | |
a format ready to put in the nncp.hjson file of recipients. | |
``` | |
nncp-cfgmin > my-peer-name.pub | |
``` | |
You can share the generated file with anyone, this will allow them to | |
send you files. The peer name of your system is "self", you can rename | |
it, it is just an identifier. | |
## Import public keys | |
When import public keys, you just need to add the content generated by | |
the command `nncp-cfgmin` of a peer in your nncp configuration file. | |
Just copy / paste the content in the `neigh` structure within the | |
configuration file, just make sure to rename "self" by the identifier | |
you want to give to this peer. | |
If you want to receive data from this peer, make sure to add an | |
attribute line `incoming: "/path/to/incoming/data"` for that peer, | |
otherwise you will not be able to unpack received file. | |
# Usage | |
Now you have peers who exchanged keys, they are able to send data to | |
each other. nncp is a collection of tools, let's see the most common | |
and what they do: | |
* nncp-file: add a file in the spool to deliver to a peer | |
* nncp-toss: unpack incoming data (files, commands, file request, | |
emails) and generate ack | |
* nncp-reass: reassemble files that were split in smaller parts | |
* nncp-exec: trigger a pre-configured command on the remote peer, stdin | |
data will be passed as the command parameters. Let's say a peer offers | |
a "wget" service, you can use `echo "https://some-domain/uri/" | | |
nncp-exec peername wget` to trigger a remote wget. | |
If you use the client / server model over TCP, you will also use: | |
* nncp-daemon: the daemon waiting for connections | |
* nncp-caller: a daemon occasionally triggering client connections (it | |
works like a crontab) | |
* nncp-call: trigger a client connection to a peer | |
If you use asynchronous file transfers, you will use: | |
* nncp-xfer: generates to / consumes files from a directory for async | |
transfer | |
# Workflow (how to use) | |
## Sending files | |
For sending files, just use `nncp-file file-path peername:`, the file | |
name will be used when unpacked, but you can also give the filename you | |
want to give once unpacked. | |
A directory could be used as a parameter instead of a file, it will be | |
stored automatically in a .tar file for delivery. | |
Finally, you can send a stream of data using nncp-file stdin, but you | |
have to give a name to the resulting file. | |
## Sync and file unpacking | |
This was not really clear from the documentation, so here it is how to | |
best use nncp when exchanging files using plain files, the destination | |
is `/mnt/nncp` in my examples (it can be an external drive, a syncthing | |
share, a NFS mount...): | |
When you want to sync, always use this scheme: | |
1. `nncp-xfer -rx /mnt/nncp` | |
2. `nncp-toss -gen-ack` | |
3. `nncp-xfer -keep -tx -mkdir /mnt/nncp` | |
4. `nncp-rm -all -ack` | |
This receives files using `nncp-xfer -rx`, the files are stored in nncp | |
spool directory. Then, with `nncp-toss -gen-ack`, the files are | |
unpacked to the "incoming" directory of each peer who sent files, and | |
ACK are generated (older versions of `nncp-toss` does not handle ack, | |
you need to generate ack befores and remove them after tx, with | |
`nncp-ack -all 4>acks` and `nncp-rm -all -pkt < acks`). | |
`nncp-xfer -tx` will put in the directory the data you want to send to | |
peers, and also the ack files generated by the rx which happened | |
before. The `-keep` flag is crucial here if you want to make use of | |
ACK, with `-keep`, the sent data are kept in the pool until you receive | |
the ACK for them, otherwise the data are removed from the spool and | |
will not be retransmited if the files were not received. Finally, | |
`nncp-rm` will delete all ACK files so you will not transmit them | |
again. | |
# Explanations about ACK | |
From my experience and documentation reading, there are three cases | |
with the spool and ACK: | |
* the shared drive is missing the files you sent (that are still in | |
pool), and you received no ACK, the next time you run `nncp-xfer`, the | |
files will be transmitted again | |
* when you receive ACK files for files in spool, they are deleted from | |
the spool | |
* when you do not use `-keep` when sending files with `nncp-xfer`, the | |
files will not be stored in the spool so you will not be able to know | |
what to retransmit if ACK are missing | |
ACKs do not clean up themselves, you need to use `nncp-rm`. It took me | |
a while to figure this, my nodes were sending ACKs to each other | |
repeatedly. | |
# Conclusion | |
I really like nncp as it allows me to securely transfer files between | |
my computers without having to care if they are online. Rsync is not | |
always possible because both the sender and receiver need to be up at | |
the same time (and reachable correctly). | |
The way files are delivered is also practical for me, as I already | |
shared above, files are unpacked in a defined directory by peer, | |
instead of remembering I moved something in a shared drive. This | |
removes the doubt about files being in a shared drive: why is it there? | |
Why did I put it there? What was its destination?? | |
I played with various S3 storage to exchange nncp data, but this is for | |
another blog post :-) | |
# Going further | |
There are more features in nncp, I did not play with all of them. | |
You can define "areas" in parallel of using peers, you can use emails | |
notifications when a remote receives data from you to have a | |
confirmation, requesting remote files etc... It is all in the | |
documentation. | |
I have the idea to use nncp on a SMTP server to store encrypted | |
incoming emails until I retrieve them (I am still working at improving | |
the security of email storage), stay tuned :) |