|
@@ -1,220 +0,0 @@
|
|
|
-CMS messages exchange
|
|
|
-=====================
|
|
|
-
|
|
|
-Authentication and encryption
|
|
|
------------------------------
|
|
|
-
|
|
|
-<send>
|
|
|
- + [message] <- <input>
|
|
|
- + [message] -> signed with sender's [private sign.pem] -> [message.sig]
|
|
|
- + [message.sig] -> encrypted with recipient's [X.509 encrypt.pem] -> [message.out]
|
|
|
- + [message.out] -> <send>
|
|
|
-
|
|
|
-<recv>
|
|
|
- + [message.out] <- <receive>
|
|
|
- + [message.out] -> decrypted with recipient's [private decrypt.pem] -> [message.sig]
|
|
|
- + [message.sig] -> verified wrt sender's [X.509 verify.pem] -> [message]
|
|
|
- + -> r-signed with recipient's [private sign.pem] -> [receipt.sig]
|
|
|
- + [receipt.sig] -> encrypted with sender's [X.509 encrypt.pem] -> [receipt.out]
|
|
|
- + [receipt.out] -> <send>
|
|
|
-
|
|
|
-<ack>
|
|
|
- + [receipt.out] <- <receive>
|
|
|
- + [receipt.out] -> decrypted with sender's [private decrypt.pem] -> [receipt.sig]
|
|
|
- + [receipt.sig] -> verified wrt recipient's [X.509 verify.pem] + [message.sig]
|
|
|
- + [hash(receipt.sig)] -> <send>
|
|
|
-
|
|
|
-
|
|
|
-Protocol
|
|
|
---------
|
|
|
- + username is explicitly verified against root CA fingerprint
|
|
|
- + hostname is implicitly verified when fetching files
|
|
|
- + resistant against MITM injections (except first MSG substitution with same <msgid>)
|
|
|
- + resistant against temporary MITM resources substitution
|
|
|
- + resistant to request replay attacks intended to cause large number of disk writes
|
|
|
-
|
|
|
- + resistant against fingerprinting if username is unknown
|
|
|
- + vulnerable to DoS (e.g., many MSG requests) if username is known
|
|
|
-
|
|
|
- + each loop type is mutually exclusive for a given (r)queue/<msgid>
|
|
|
- + all code blocks are restartable (e.g., after crash)
|
|
|
- + messages and receipts are never lost if /cables filesystem is transactional
|
|
|
-
|
|
|
- + <hostname>/<username>/ transient public directory w/o list permission
|
|
|
- /certs/ public X.509 certificates
|
|
|
- /queue/<msgid> outgoing message <msgid>
|
|
|
- /rqueue/<msgid> outgoing receipt <msgid>
|
|
|
- /request/... service interface
|
|
|
-
|
|
|
- + /cables/ private directory
|
|
|
- /queue/<msgid>/ outgoing message <msgid> work dir
|
|
|
- /rqueue/<msgid>/ outgoing receipt <msgid> work dir
|
|
|
-
|
|
|
- + [send] (MUA-invoked script) writes to /cables/queue
|
|
|
- + [service] (fast and secure web service) writes to /cables/{queue,rqueue}
|
|
|
- + [crypto loop] writes to /cables/{queue,rqueue}, MUA inbox directory;
|
|
|
- reads from X.509 directory
|
|
|
- + [fetch loop] writes to /cables/{queue,rqueue}; reads from network
|
|
|
- + [comm loop] writes to /{<username>,cables}/{queue,rqueue}, network;
|
|
|
- reads username from X.509 directory
|
|
|
-
|
|
|
-
|
|
|
-<send>
|
|
|
- [send]
|
|
|
- + generate random 160-bit (40 hex digits) <msgid>
|
|
|
- + prepare /cables/queue/<msgid>/{message{,.hdr},username,{,s}hostname}
|
|
|
- + create /cables/queue/<msgid>/send.req
|
|
|
- * (atomic via rename from /cables/queue/tmp.<random>/<msgid>/)
|
|
|
-
|
|
|
- [fetch loop]
|
|
|
- + check /cables/queue/<msgid>/send.req
|
|
|
- + fetch <hostname>/<username>/certs/*.pem -> /cables/queue/<msgid>/*.pem
|
|
|
- + rename /cables/queue/<msgid>/send.req -> send.rdy
|
|
|
-
|
|
|
- [crypto loop]
|
|
|
- + check /cables/queue/<msgid>/send.rdy
|
|
|
- + prepare /cables/queue/<msgid>/message.out
|
|
|
- + rename /cables/queue/<msgid>/send.rdy -> send.ok (success)
|
|
|
- + -> send.req (crypto fail)
|
|
|
- + remove /cables/queue/<msgid>/message (if success)
|
|
|
-
|
|
|
- [comm loop]
|
|
|
- + check /cables/queue/<msgid>/send.ok
|
|
|
- + checkno /cables/queue/<msgid>/ack.ok
|
|
|
- + copy /cables/queue/<msgid>/message.out -> //<susername>/queue/<msgid> (atomic, if not exists)
|
|
|
- + request <hostname>/<username>/request/msg/<msgid>/<shostname>/<susername> -> send.ans
|
|
|
-
|
|
|
-
|
|
|
-<recv>
|
|
|
- [service]
|
|
|
- + upon msg/<msgid>/<hostname>/<username>
|
|
|
- + checkno /cables/rqueue/<msgid>
|
|
|
- + create /cables/rqueue/<msgid>.new/ (ok if exists)
|
|
|
- + write /cables/rqueue/<msgid>.new/{username,hostname}
|
|
|
- + create /cables/rqueue/<msgid>.new/recv.req (ok if exists)
|
|
|
- + rename /cables/rqueue/<msgid>.new -> <msgid>
|
|
|
-
|
|
|
- [fetch loop]
|
|
|
- + check /cables/rqueue/<msgid>/recv.req
|
|
|
- + fetch <hostname>/<username>/queue/<msgid> -> /cables/rqueue/<msgid>/message.out
|
|
|
- + fetch <hostname>/<username>/certs/*.pem -> /cables/rqueue/<msgid>/*.pem
|
|
|
- + rename /cables/rqueue/<msgid>/recv.req -> recv.rdy
|
|
|
-
|
|
|
- [crypto loop]
|
|
|
- + check /cables/rqueue/<msgid>/recv.rdy
|
|
|
- + prepare /cables/rqueue/<msgid>/{message{,.hdr},receipt.{ack,out}}
|
|
|
- + create <mua message> <- /cables/rqueue/<msgid>/message
|
|
|
- + rename /cables/rqueue/<msgid>/recv.rdy -> recv.ok (success)
|
|
|
- + -> recv.req (crypto fail)
|
|
|
- + remove /cables/rqueue/<msgid>/message{,.out} (if success)
|
|
|
-
|
|
|
- [comm loop]
|
|
|
- + check /cables/rqueue/<msgid>/recv.ok
|
|
|
- + copy /cables/rqueue/<msgid>/receipt.out -> //<rusername>/rqueue/<msgid> (atomic, if not exists)
|
|
|
- + request <hostname>/<username>/request/rcp/<msgid> -> recv.ans
|
|
|
-
|
|
|
-
|
|
|
-<ack>
|
|
|
- [service]
|
|
|
- + upon rcp/<msgid>
|
|
|
- + check /cables/queue/<msgid>/send.ok
|
|
|
- + create /cables/queue/<msgid>/ack.req (atomic, ok if exists)
|
|
|
- + touch /cables/queue/<msgid>/ (if ack.req did not exist)
|
|
|
-
|
|
|
- [fetch loop]
|
|
|
- + check /cables/queue/<msgid>/ack.req
|
|
|
- + checkno /cables/queue/<msgid>/ack.{rdy,ok}
|
|
|
- + fetch <hostname>/<username>/rqueue/<msgid> -> /cables/queue/<msgid>/receipt.out
|
|
|
- + rename /cables/queue/<msgid>/ack.req -> ack.rdy
|
|
|
-
|
|
|
- [crypto loop]
|
|
|
- + check /cables/queue/<msgid>/ack.rdy
|
|
|
- + prepare /cables/queue/<msgid>/receipt.ack
|
|
|
- + create <mua acknowledge>
|
|
|
- + rename /cables/queue/<msgid>/ack.rdy -> ack.ok (success)
|
|
|
- + -> ack.req (crypto fail)
|
|
|
- + remove /cables/queue/<msgid>/{message.{out,sig},receipt.out} (if success)
|
|
|
-
|
|
|
- [comm loop]
|
|
|
- + check /cables/queue/<msgid>/ack.ok
|
|
|
- + remove //<susername>/queue/<msgid> (if exists)
|
|
|
- + read /cables/queue/<msgid>/receipt.ack (128 hex digits)
|
|
|
- + request <hostname>/<username>/request/ack/<msgid>/<ackhash> (wait) -> ack.ans
|
|
|
- + rename /cables/queue/<msgid> -> <msgid>.del
|
|
|
- (if ack is lost due to MITM attack, receiver will keep requesting rcp/<msgid>)
|
|
|
-
|
|
|
- -and/or-
|
|
|
-
|
|
|
- + check /cables/queue/<msgid>.del/
|
|
|
- + remove //<susername>/queue/<msgid> (if exists)
|
|
|
- + remove /cables/queue/<msgid>.del/
|
|
|
-
|
|
|
-
|
|
|
-<fin>
|
|
|
- [service]
|
|
|
- + upon ack/<msgid>/<ackhash>
|
|
|
- + check /cables/rqueue/<msgid>/recv.ok
|
|
|
- + compare /cables/rqueue/<msgid>/receipt.ack <-> <ackhash>
|
|
|
- + rename /cables/rqueue/<msgid> -> <msgid>.del
|
|
|
-
|
|
|
- [comm loop]
|
|
|
- + check /cables/rqueue/<msgid>.del/
|
|
|
- + remove //<rusername>/rqueue/<msgid> (if exists)
|
|
|
- + remove /cables/rqueue/<msgid>.del/
|
|
|
-
|
|
|
-
|
|
|
-Loop scheduler
|
|
|
---------------
|
|
|
-
|
|
|
-Initialization (for <msgid>s of 40 hex digits):
|
|
|
- + remove /cables/rqueue/<msgid>.new/ (before [service] startup)
|
|
|
-
|
|
|
-Watch list (for <msgid>s of 40 hex digits):
|
|
|
- + /cables/queue/ <msgid>, <msgid>.del (inotify: moved_to, attrib)
|
|
|
- + /cables/rqueue/ <msgid>, <msgid>.del (inotify: moved_to)
|
|
|
-
|
|
|
- + <msgid>: non-blocking lock attempt
|
|
|
- + <msgid>.del: blocking lock (with timeout)
|
|
|
-
|
|
|
-Retry policies:
|
|
|
- + retry every X min. (+ random component)
|
|
|
-
|
|
|
-Validation: upon reaching max age (from <msgid>/username timestamp):
|
|
|
- (mutually exclusive with all loop types)
|
|
|
-
|
|
|
- + (queue) create <mua message>
|
|
|
- send.req/rdy: failed to fetch certificates and encrypt message
|
|
|
- send.ok + no ack.ok: failed to send message and receive receipt
|
|
|
- ack.ok: failed to acknowledge receipt
|
|
|
- + (queue) rename /cables/queue/<msgid> -> <msgid>.del
|
|
|
-
|
|
|
- + (rqueue) create <mua message>
|
|
|
- recv.req/rdy: failed to fetch and decrypt message
|
|
|
- recv.ok: failed to send receipt and receive acknowledgment
|
|
|
- + (rqueue) rename /cables/rqueue/<msgid> -> <msgid>.del
|
|
|
-
|
|
|
-
|
|
|
-Message format
|
|
|
---------------
|
|
|
-
|
|
|
-(send)
|
|
|
- + extract all unique To:, Cc:, Bcc: addresses
|
|
|
- + check that all addresses (+ From:) are recognized (e.g., *.onion)
|
|
|
- + remove Bcc: and all X-*: headers
|
|
|
- + reformat Date: as UTC
|
|
|
- + compress with gzip
|
|
|
-
|
|
|
-
|
|
|
-(recv)
|
|
|
- + uncompress with classic (single-threaded) gzip
|
|
|
- + replace From: header with the verified address (rename old header)
|
|
|
- + add X-Received-Date: header
|
|
|
-
|
|
|
-
|
|
|
-(ack)
|
|
|
- + extract original From:, To:, Cc:, Bcc:, Subject:, Date:, Message-ID:,
|
|
|
- In-Reply-To:, References: fields
|
|
|
- + replace Date: header with current date (rename old header)
|
|
|
- + prepend [vfy] to Subject: field contents
|
|
|
- + append body with verification message, including current timestamp
|
|
|
- and verified delivery address
|