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.
click here to start

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

Sample of Alternatives to SMF