Signed Message Forum
A Signed Message Forum (SMF) implements a decentralized web of
trust. Every message is signed with a private key. Everyone
generates and controls their own private key(s) and use those
keys to sign messages. The SMF only saves and processes signed
messages. To verify the signatures on the messages, the SMF
uses the public keys corresponding to the users' private keys.
An "approved" public key is any key that has been sent in an
signed approval message.
Identity in the SMF is
provided by an address: a base 58 representation of a hash of
the public key along with a text name. It can be a real name
or an alias as long as the public key has been vouched for
(approved). Robot users or "sock puppets" are fine as long as
some other user has vouched for them. Approved users can
create new identies (right now up to 10) simply by signing an
approval message with an already-approved identity.
There are no passwords. It's up to each user to protect their
private key from loss or theft since signing messages with
that private key is the only way to prove their identity.
There is no central authority to go ask to restore an account
(identity). It is simply not possible. There is no censorship
other than the automated filtering. There is no central
authority to approve or disapprove users of the SMF, only
other approved users vouching for a new user.
Start here to generate a private key. You will need to (1)
send the corresponding public key to the server and (2) sign
the public key with the private key and send it to an existing
user user for vouching. That user will sign an approval message.
Message Signing and Verification
The private key operation (signing) is performed in javascript
running in every user's browser. The publc key operation
(verification) is performed in python in the server. All
signing / verification keypairs are ECDSA P-256 using SHA 256.
Other algorithms could be supported and should be supported
particularly to allow the operation system or authentication
hardware to choose. But the algorithm is hard coded on noth
ends for now.
On both ends the signature and
verification is performed on the payload which is always
address (hash of public key) + nonce + message body (with
spaces and quotes removed). On the client the signing uses
the crypto.subtle.sign function in javascript. The server
performs verification to only accept a message if signed by an
approved public key before inserting that message in the
database. Verification no the server uses the ecdsa library
but use the jwcrypto library to handle the public keys which
are passed in JWK format.
Hardware Signing and Server Verification
Hardware is currently only supported for signing because FIDO
hardware only does signing, not verification or encryption or
decryption. FIDO is designed to be used as a second factor to
sign a random challenge sent by a server. However the Signed
Message Forum has no central authority, although it is
designed with a centralized server since that is simpler than
a distribution (e.g. blockchain) implementation. So instead
we validate by both the cryptographic signature and a
challenge hash which is created in part from registration
(user approval) data.
Instead of the random challenge
from a server, the browser creates the same size hash from the
payload (described above). The nonce in the payload ensures
the hash is random to prevent replay. However the server
needs to ensure that this "challenge" came from a particular
user. So the server recreates the challenge using the same
algorithm as the javascript in the browser using the address
provided by the user in the registration step, which is
actually provided during approval by an approved user who
vouched for that user (and address). The FIDO hardware always
sends back the challenge in the clientdata structure. On the
server we compare that to the challenge created from the
hashed payload whicb includes the address from registration.
End-to-End Encryption
The public key operation (encryption) is performed in
javascript in the sender's browser. The private key operation
(decrpytion) is performed in the recipient's browser. The
server provides the public keys based on name and address
(hashed public key) of the recipient. Recipients send their
public key with a signed message essentially vouching for
their public key. End-to-end encryption uses only RSA-OAEP
with 2048 bit keys. This is mostly a crypto.subtle javascript
library limition which seems reasonable. The RSA keys are
used to wrap and unwrap an AES-CBC 256 bit symmetric key used
to encrypt messages. The ciphertext and IV is posted for each
encrpyted message along with the AES key wrapped using the RSA
public key of each intended recipient, keyed by their signing
key address.
Security
User security is up to each user
Server security relies on error-free implementation and
database access control. All server (and obviously client)
code is accessible.
Server code is here
All database access code at that link uses static SQL strings.
All messages are run through an HTML sanitizer (inside of
message.py). All quotes are escaped using a unique escape
sequence to avoid potential security problems in the JSON
message parser. Although the world doesn't really need
another unique escape sequence, we must remove quotes and we
do not want to unescape anyone else's escaped quotes.
There are only two simple APIs in the server code linked
above. The pksapp accepts unsigned messages containing
addresses and public keys and stores them in the data. The
only current limitation is 10 keys per IPV4 address. The
message app only accepts signed messages. There is no general
API to protect (e.g. for queries like searches). Instead all
HTML is static and sent out via the Apache web server. To
create the potential dynamic content the gen_msg_lists python
app generates new HTML content every 10 seconds (doesn't
regenerate if nothing changed). But all dynamics will
necessarily be in more elaborate javascript on browsers and in
phone apps which need to be developed. The content is entirely
from the database accessed in gen_msg_lists and all database
content is eventually stored in the webserver directory for
user access. Obviously no signing is needed to access
content, everyone everywhere has read access to
everything.
In short with user security controlled by
each user, there is nothing of value to users to steal from
the server. A minor exception is the user IP which we have to
hold in some form to check the keys-per-IP limit. The
database must be protected from write access and the
verification public keys are not revealed, similar to the
blockchain implementations. Obviously the public keys used
for encryption are revealed otherwise end-to-end encrption
would not be possible (without a complicated protocol and
undesirable centralized authentication).
E2EE public keys
Personalization
Images can be signed and sent to the server like any other
signed message. The difference is after the signature is
verified images are stored in files on the server to be served
up by apache instead of the database. Every user has a
personal directory under /messages where images are stored.
All images are sent for vetting to sightengine.com The code is
relatively simple and in the server direcory
(image_moderation.py). Images that do not pass
moderation are removed and replaced with a link to a
"Safe4Work" score image. Images with scores under 0.50 are
replaced. The file extension (e.g. ".png" is flexible and
sent with the message.
After uploading images they
can be included in a home page, with one home page per user.
The home page(s) screen allows a user to view the existing
home page (if any) and to lay out a home page with HTML. Like
all messages the home page is run through the HTML sanitizer.
All IMG tag URLs within the HTML are run through the image
moderation. All IMG URLs with a score less than 0.5 are
replaced with a URL to the safe4work score. A home page is
stored in a file in the users directory under /messages in a
file called, appropriately, index.html. The presence of that
file prevents Apache from indexing the user's directory which
is the default. With that default, all signed messages and
signed images posted by a user are shown in the usual
directory listing.
Although the javascript currently
tells the server to store file called index.html a bot or any
custom client code can send any filename they want to the
server and the message contents will be stored in a file with
that name. Obviously filenames with ".." in them are
rejected. This way a user with custom code (e.g. the robot
code linked below) can populate their user directory with
whatever HTML files they want along with the image files. The
HTML files can have whatever name is desired. However image
file names are limited to a hash of the image content.
Censorship
As described above, there is automated censorship. Large
public forums also use automated censorship along with manual
censorship and that censorship can be arbitrary and
capricious. Censorship in SMF is transparent. It starts with
a bleeper to bleep profane, racist or sexist words. People
can get around the bleeper of course but that requires
creativity. Bleeper code is under /server. All text, HTML or
not, is sent through:
python HTML
sanitizer That sanitizer is slightly broadened to allow
img tags, but all img tags are checked using sightengine
sightengine The key for
the sightengine API is stored in /keys which is the only
forbidden (not publicly accessible) directory on the
server. Images receiving a score less than 0.5 in the "none"
key under nudity are deleted from the server and replaced with
a URL to a canned image containing the score which imforms the
user of the censorship
All other censorship is
currently by a to-be-designed revocation system. Forum users
can create sock puppet users to vouch for their other users so
revocation will need to account for that. But in general the
point of a signed message forum is for users to vet other
users and obviate the need for censorship. Further automated
censorship will be needed to detect sensitive content that is
illegal to post and automatically delete it. A subscription
API could be designed with safeguards to prevent malicious
users from guessing what makes content sensitive.
Robot Users
Sample robot code Robot users are
encouraged because they add value by allowing users to write a
little code to automatically generate and post messages. A
robot user needs to sign messages just like any other user.
Therefore in the same code there's "reg" command line argument
to perform a registration step first. The result of that step
is a local file containing the private key and other user
info. After "reg" the code accepts a "post" command line
argument which scrapes a website, produces a summary and posts
the summary to the forum.
Architecture
Server code is here
Server Status Content is created
by the gen_*.py code at the first link. The status of those
python scripts is shown in the second link. The content is
then served statically using Apache. Dynamics are still
to-be-designed. There are a lot of "refresh" buttons in the
admin screens that need to be replaced with more sophisticated
javascript that will keep the generated html up-to-date.
Further work is also needed on javascript to run in the
browser and create somewhat dynamic displays from the "static"
content served up by Apache. Although the content being
served is static, it is updated every 10 seconds nominally for
now. The eventual update rate will depend on code performance
which will need improvements such as incremental updates.
TODO
- Apps for all major phone OSs (in progress using Cordova)
- Forum features like preview
- Add privacy protection (detection of tracking images) to image_moderation
- Add copyright detection to image_moderation
- Revocation via signed messages
Sample of Alternatives to SMF
- GPG and other chained-trust messaging systems (not a public forum)
- Legacy forums where users know each other personally (centralized)
- Forums with membership using affinity groups (centralized, no secrecy)
- Membership systems e.g. memberpress (centralized, no vouching, no secrecy)
- Group-capable open source: Telegram (delegated but centralized admin, no vouching)
- Decentralzed Forums e.g. Mastodon (semi-centralized admin, no vouching, no secrecy)
- End-to-end encrypted chat e.g. Signal protocol (centralized)
- Chat user verification: you share a 60 digit code on another channel you provide
- Blue check mark (no vouching, centralized, human-censored, no anonymity, no secrecy)
- Digital identities using private keys with membership forums (centralized)
- Decentralized messaging with crypto private/public keys
e.g. new blockchain apps, (mining is expensive)