Openmsg Technical Details
Openmsg Protocol
Version: 1.0
Status: Draft
Author: S. Walker
Date: 18 June 2025
Version: 1.0
Status: Draft
Author: S. Walker
Date: 18 June 2025
1. Abstract
Openmsg is a decentralized, cross-platform messaging protocol designed to eliminate spam and enforce
explicit trust-based communication between users. It uses one-time pass codes for connection establishment
and mutual authentication, with end-to-end encryption and anti-spoofing verification built-in.
This document sets out the Openmsg protocol for connecting two users on different or on the same server (the initial handshake) and for sending a message between two connected users. It details the process and workflow, the format of data, the type of encryption and hashing used, what checks need to be performed and where certain key files must be stored on a domain's server.
This document sets out the Openmsg protocol for connecting two users on different or on the same server (the initial handshake) and for sending a message between two connected users. It details the process and workflow, the format of data, the type of encryption and hashing used, what checks need to be performed and where certain key files must be stored on a domain's server.
2. Introduction
The core of Openmsg is permission-based messaging. One user cannot connect with another without explicit
permission via a one-time pass code. After the connection (handshake) is made, the two users can message
each other.
For example:
If User A wants to message User B, User A needs not just User B’s address but also a one-time pass code that User B provides. Without a valid pass code, the connection attempt is silently rejected; no spam, not even spam requests.
A handshake securely exchanges auth codes and encryption keys. After that, messages are encrypted, timestamped, and hashed using the shared auth code. A callback is made to the domain in the senders address, ensuring the message was really sent from there.
The design prevents message spoofing, replay attacks, and the misuse of leaked accounts and auth codes.
If User A wants to message User B, User A needs not just User B’s address but also a one-time pass code that User B provides. Without a valid pass code, the connection attempt is silently rejected; no spam, not even spam requests.
A handshake securely exchanges auth codes and encryption keys. After that, messages are encrypted, timestamped, and hashed using the shared auth code. A callback is made to the domain in the senders address, ensuring the message was really sent from there.
The design prevents message spoofing, replay attacks, and the misuse of leaked accounts and auth codes.
3. Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "RECOMMENDED", "MAY", and "OPTIONAL" in
this document are to be interpreted as described in RFC 2119.
Node: A server implementing the Openmsg protocol. A Node is represented and located by a domain name.
Provider: Another term for a Node. The provider provides the service to its Users.
User: An individual who uses a Node to send or receive messages.
Address: A unique identifier in the format <UserID>*<Domain>.
UserID: A numeric identifier of a specific User on a Node.
Handshake: The initial mutual verification process that establishes trust and a connection between two users.
Connection: Two connected Users.
Pass Code: A one-time-use code, with an expiry, issued to initiate a connection.
Auth Code: A private token used to validate authenticated communication between two users.
Message Package: The encrypted message and nonce, base64-encoded.
Message Bundle: The Message Package, the Auth Code, a random salt, and the message timestamp.
Node: A server implementing the Openmsg protocol. A Node is represented and located by a domain name.
Provider: Another term for a Node. The provider provides the service to its Users.
User: An individual who uses a Node to send or receive messages.
Address: A unique identifier in the format <UserID>*<Domain>.
UserID: A numeric identifier of a specific User on a Node.
Handshake: The initial mutual verification process that establishes trust and a connection between two users.
Connection: Two connected Users.
Pass Code: A one-time-use code, with an expiry, issued to initiate a connection.
- Active Pass Code: A Pass Code that has not been used and has not expired.
- Expired Pass Code: A Pass Code that has either been used or had expired.
Auth Code: A private token used to validate authenticated communication between two users.
Message Package: The encrypted message and nonce, base64-encoded.
Message Bundle: The Message Package, the Auth Code, a random salt, and the message timestamp.
4. Data Element Formats
Pass Code
A Pass Code MUST be a string of 6 digits (consisting of any of the ten digits 0-9). It MUST be randomly
generated, using a truly random method, when
a User requests a Pass Code. A Pass Code MUST be generated every time and NEVER reused. It SHOULD NOT be
assumed that Pass Codes will be unique across all Openmsg Users. However a Pass Code MUST be unique to that
User; it MUST be different to any and all other Active Pass Codes belonging to that User. It SHOULD be
different to any other Expired Pass Codes belonging to that User of which expired within a certain
timeframe. A Pass Code MUST be set to expire after it's creation. The expiry time SHOULD be 1 hour after
it's creation.
Ident Code
The Ident Code MUST be a randomly ganerated string containing uniformly selected random bytes, 32 bytes in
length. It MUST then be converted from binary to hexadecimal. The output MUST be an ASCII string containing
the hexadecimal representation of the random bytes. The Ident Code MUST be unique per Node.
Auth Code
The Auth Code MUST be a randomly ganerated string containing uniformly selected random bytes, 32 bytes in
length. It MUST then be converted from binary to hexadecimal. The output MUST be an ASCII string containing
the hexadecimal representation of the random bytes. The Auth Code MUST be unique per Node.
Message Text
The maximum length of the plain text message is 5000 bytes and must use UTF-8.
Message Encryption
Message Encryption MUST use Libsodium (Sodium). For more details on the format of all the componants, see
the Main Section "Message Encryption".
Message Encryption Key
Message Encryption Key MUST use Libsodium (Sodium)'s Keygen. It MUST be 32 bytes in length. It MUST then be
converted from binary to hexadecimal using Sodium's binary to hex function. The output MUST be a string
containing the hexadecimal representation of the random bytes. The hexadecimal string will be persisted for
later use. It will be converted back to binary for encryption.
The Message Encryption Nonce MUST use Libsodium (Sodium)'s Nonce creation function from random bytes. It
MUST be 32 bytes in length. For storing in your database for later verification, it MUST then be converted
to base64 encoding using Sodium's binary to base64 function. The URL SAFE NO PADDING flag MUST be set. The
unencoded binary version is used to encrypt the message.
Message Encryption Nonce
The message encryption nonce MUST be created using Sodium's SECRETBOX RANDOMBYTES function. It will be used
in its binary format during message encryption.
Message Encryption
The message MUST be encrypted using Sodium's SECRETBOX function, using the binary Message Encryption Nonce,
and the binary Message Encryption Key
Message Package
The Message Package MUST be a string concatenation of MessageNonce + EncryptedMessage in that order, with no
extra characters or spaces added. This string MUST then be Base64 encoded using Sodium's binary to base64
function. The URL SAFE NO PADDING flag MUST be set. The expected limit of the Message Package is based on
the Message Text limit.
Message Hash Salt
The "Hash Salt" MUST be a randomly ganerated string containing uniformly selected random bytes, 16 bytes in
length. A new Salt MUST be generated for each Hash. It MUST then be converted from binary to hexadecimal.
The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. A Salt
MUST NOT be reused.
Message Timestamp
The timestamp of the creation of the Encrypted Message and Hash. This MUST be a string of the current
numberical Unix Timestamp; the number of seconds from the Unix Epoch on January 1st, 1970 at UTC. The
timestamp MUST be recalculated for each message.
Message Bundle
The Message Bundle MUST be a string concatenation of Message Package + Auth Code + Hash Salt + Message
Timestamp in that order, with no extra characters or spaces added.
Message Hash
The Message Hash MUST be a hash of the Message Bundle. It MUST be hashed using SHA-256.
omLogin Code (omLogin_code)
An omLogin Code MUST be a string of 6 uppercase letters (from the 26 letters in the latin alphabet A-Z).
It MUST be randomly generated, using a truly random method,
when a User requests an omLogin Code. An omLogin Code MUST be generated every time and NEVER reused. It
SHOULD NOT
be assumed that omLogin Codes will be unique across all Openmsg Users. However a omLogin Code MUST be unique
to
that User; it MUST be different to any and all other Active omLogin Codes belonging to that User. It SHOULD
be
different to any other Expired omLogin Codes belonging to that User of which expired within a certain
timeframe. An omLogin Code MUST be set to expire after it's creation. The expiry time SHOULD be 60 seconds
after
it's creation.
omLogin Code Hash Salt (omLogin_hash_salt)
The "Hash Salt" MUST be a randomly ganerated string containing uniformly selected random bytes, 16 bytes in
length. A new Salt MUST be generated for each Hash. It MUST then be converted from binary to hexadecimal.
The output MUST be an ASCII string containing the hexadecimal representation of the random bytes. A Salt
MUST NOT be reused.
omLogin Code Hash (omLogin_hash)
The omLogin Code Hash MUST be a hash of the omLogin_code +auth_code + omLogin_hash_salt. It MUST be hashed using SHA-256.
5. Address Format
Every Openmsg addesss MUST be formatted in the following way:
User ID MUST be a string, but MUST ONLY contain digits (0-9). As the User ID MUST be stored as a string, the User ID MAY start with leading zeros, for example 007*domain.com - therefore 007*domain.com and 7*domain.com are completely separate Addresses.
Format: <User ID> * <domain> ; a User ID and a domain separated by an asterix.The full address is a string with a maximum length of 255 characters. There is no set limit on the length of the User ID or domain, however the complete openmsg address, including the User ID, the asterix and the domain MUST NOT exceed the overall limit.
Example: 0123456*openmsg.me
User ID MUST be a string, but MUST ONLY contain digits (0-9). As the User ID MUST be stored as a string, the User ID MAY start with leading zeros, for example 007*domain.com - therefore 007*domain.com and 7*domain.com are completely separate Addresses.
- The domain identifies the user's node
- The user ID must be unique per node
- Maximum length of 255 characters
6. Protocol Overview
This is a very high level overview of the protocol. Not all steps are detailed here; some steps have been
simplified or left out. See "Protocol in Detail" later in the document.
The pass code is only needed once, during the initial handshake:
A handshake securely exchanges an identification code, auth codes and an encryption key.
After that, messages are encrypted, timestamped, and hashed using the shared auth code.
The recipient server:
Reconstructs the hash to confirm authenticity, freshness (within 60 seconds), and message integrity.
Verifies the sender’s domain by performing a callback to the domain in the senders address, ensuring the message was really sent from there.
- Users creates an account via a Provider and get a unique Address (e.g. 56789*domain.com)
- To initiate communication, the sender must possess:
- The recipient’s address
- A valid one-time Pass Code issued by the recipient
- The sender performs a handshake, the recipient confirms the Pass Code and generates and sends back:
- An Ident Code
- An Auth Code
- A shared Encryption Key
- Both Users persist these three values
- Messages can now be exchanged using the Ident Code, Auth Code and Encryption Key
The pass code is only needed once, during the initial handshake:
A handshake securely exchanges an identification code, auth codes and an encryption key.
After that, messages are encrypted, timestamped, and hashed using the shared auth code.
The recipient server:
Reconstructs the hash to confirm authenticity, freshness (within 60 seconds), and message integrity.
Verifies the sender’s domain by performing a callback to the domain in the senders address, ensuring the message was really sent from there.
The next parts of the document details:
- Creating Pass Codes
- The Handshake
- Sending and Receiving Messages
- "Signup/Login with Openmsg"
7. The Handshake
Node Roles
Sending Node: The Node initiating the Handshake using the provided Openmsg Address and a PassCode
Receiving Node: The Node receiving and confirming the Handshake request. This is the Node on which the User generated the PassCode and gave it to the Sending Node in order to allow the Sending Node to be able to initiate the Handshake.
Receiving Node: The Node receiving and confirming the Handshake request. This is the Node on which the User generated the PassCode and gave it to the Sending Node in order to allow the Sending Node to be able to initiate the Handshake.
7.1 Pass Codes
Overview
A Pass Code is generated by the Receiving Node, usually at the request of the User, and given* to the
Sending Node along with the Users Openmsg Address
to allow the Sending Node to be able to initiate the Handshake.
*As an example, these details could be given the a different User who will initiate the Handshake on their Node, or entered into an online form when connecting to a website or signing up to a newsletter.
*As an example, these details could be given the a different User who will initiate the Handshake on their Node, or entered into an online form when connecting to a website or signing up to a newsletter.
Creation
Each of the 6 digits in a Pass Code MUST be generated one digit at a time, each digit MUST be a true random
number, and each digit MUST be appended to the Pass Code string one at a time.
The Pass Code MUST be stored as a string so that each digit is preserved even if the leading digit is a
zero.
A Pass Code MUST be persisted for later verification along with the timestamp of when it was created.
It MUST be set to expire after one hour.
7.2 Data Transfer Format and Endpoints
All data transfers between Nodes, including during the Handshake and Message delivery, MUST be performed as
HTTPS POST requests using JSON-encoded payloads.
Each Node MUST expose specific endpoints at fixed paths on its domain to receive this data. All Openmsg-related scripts MUST reside under the root path:
Subdirectories under
Each Node MUST expose specific endpoints at fixed paths on its domain to receive this data. All Openmsg-related scripts MUST reside under the root path:
/openmsg/Subdirectories under
/openmsg/ are used to separate different types of
actions (e.g., /openmsg/auth/, /openmsg/message-receive/ etc).
Each Node is responsible for hosting the appropriate handler scripts at these standardized locations to
ensure interoperability.
Example: for the Openmsg address 012345*receivingdomain.com, the script responsible for receiving incoming messages MUST be located at:This is the endpoint that sending nodes will use to perform POST requests when delivering messages.
https://receivingdomain.com/openmsg/message-receive/ as this is where sending Node will be making POSTS to.
7.3 The Handshake Protocol
Overview
To establish a connection between two Openmsg accounts, a handshake must be performed.
The node initiating the handshake is referred to as the Sending Node, and the node associated with the user being contacted is the Receiving Node.
To begin the handshake, the Sending Node must possess:
Upon successful validation, the Receiving Node:
The node initiating the handshake is referred to as the Sending Node, and the node associated with the user being contacted is the Receiving Node.
To begin the handshake, the Sending Node must possess:
- A valid Pass Code issued by the user on the Receiving Node.
- The user's full Openmsg Address, from which the domain of the Receiving Node is derived.
❯❯ URLUpon receiving the request, the Receiving Node:
https://<receiving-domain>/openmsg/auth/
- Verifies the Pass Code for the user associated with the provided Openmsg address. This ensures that the recipient has explicitly granted permission for contact.
- Performs a callback by issuing an HTTPS POST request to the domain specified in the Sender's Openmsg
address at:
❯❯ URL
This step verifies that the handshake was genuinely initiated by the correct Sending Node and prevents domain spoofing or unauthorized connection attempts.
https://<receiving-domain>/openmsg/auth-confirm/
Upon successful validation, the Receiving Node:
- Generates a unique Ident Code, Auth Code, and Message Encryption Key.
- Returns these values to the Sending Node via a JSON response.
Result:
The handshake concludes with the creation and mutual exchange of:
- An Ident Code for identifying the connection.
- An Auth Code for authenticating future messages.
- A Message Encryption Key for encrypting message content between the two parties.
⚠ Note: A new handshake can be performed to set up a new connection between two previously un-connected addresses. However, a handshake can also be performed by two currently connected addresses to replace or refresh codes and encryption keys. It should not replace Ident Codes for a current connection.
7.3.1 Handshake Initiation (Step 1 – Sending Node → Receiving Node)
To establish a connection between two Openmsg users, the Sending Node MUST initiate a Handshake Request by
sending a HTTPS POST request to the following endpoint on the Receiving Node:
❯❯ URL
https://<receiving-domain>/openmsg/auth/
The receiving domain is extracted from the Receiver's Openmsg Address as shown below.
7.3.2 Extracting the Receiving User ID and Domain
The receiving_openmsg_address is in the format:
The receiver's domain is the portion after the * symbol.
<user_id>*<domain>
For example:
012345*receivingdomain.com
The receiving_openmsg_address_id is the portion before the * symbol, in this example: 012345. This is the
unique numeric user ID on the Receiving Node’s domain.The receiver's domain is the portion after the * symbol.
7.3.3 Sending Node Responsibilities
Before initiating the handshake, the Sending Node MUST create a passcode_hash and then persist the following
pieces of data locally for validation in the upcoming callback step:
Compute the passcode_hash
Generate the passcode_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
- receiving_openmsg_address
- passcode_hash (see below)
- Current timestamp
These are used later to validate the callback from the Receiving Node confirming the legitimacy of the
handshake. The timestamp will be used during this validation to make sure the handshake is fresh.
Compute the passcode_hash
Generate the passcode_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
❯❯ Pseudo Code
pass_code + sending_openmsg_address + receiving_openmsg_address
in that order, with no extra characters or spaces added.
7.3.4 Request Payload
The Sending Node sends the following JSON-encoded data in the POST request body:
❯❯ JSON
{
"receiving_openmsg_address_id": "012345",
"pass_code": "456123",
"sending_openmsg_address": "987654*sendingdomain.com",
"sending_openmsg_address_name": "Alice Smith",
"sending_allow_replies": true,
"openmsg_version": 1.0
}
Field Descriptions:
- receiving_openmsg_address_id: The numeric user ID of the recipient on the Receiving Node. (Only the User ID part is sent not the whole address).
- pass_code: A single-use, time-limited pass code previously issued by the recipient to allow this connection attempt.
- sending_openmsg_address: Full Openmsg address of the sender, used to validate and complete the return handshake.
- sending_openmsg_address_name: Optional display name associated with the sender's account, for UX purposes.
- sending_allow_replies: Boolean flag indicating whether the sender permits responses
(
"false"used for one-way notification-type accounts). - openmsg_version: Version of the Openmsg protocol the client supports (used for version negotiation if needed).
7.4.1 Pass Code Verification & Callback (Step 2 – Receiving Node → Sending Node)
Once the Receiving Node has accepted a handshake request, it must verify the handshake.
7.4.2 Initial Pass Code Validation
Upon receiving the JSON POST request at:
Return a JSON error response immediately:
❯❯ URLThe Receiving Node MUST:
https://<receiving-domain>/openmsg/auth/
- Extract the following fields from the request:
- receiving_openmsg_address_id
- pass_code
- sending_openmsg_address
- sending_openmsg_address_name
- sending_allow_replies
- openmsg_version
- Verify that the provided pass_code is:
- Correct for the receiving user (as identified by receiving_openmsg_address_id)
- Not expired (it MUST have been issued within the last 60 minutes)
Return a JSON error response immediately:
❯❯ JSON
{
"error": true,
"error_code": "pass_code_invalid",
"error_message": "pass code not valid"
}
OR
❯❯ JSON
{
"error": true,
"error_code": "pass_code_expired",
"error_message": "expired pass code, over 1 hour old"
}
If validation succeeds:- The pass_code MUST be deleted or marked as used, to prevent reuse.
- Proceed to the callback verification step.
7.4.3 Extracting Callback Details
To prevent a Node from completing a handshake using an Openmsg address from a different domain (domain
spoofing), (even if they have a valid pass_code), the Receiving Node performs a domain ownership
verification. The Receiving Node MUST split the sending_openmsg_address into:
- Sending User ID: the part before *
- Sending Domain: the part after *
7.4.4 Perform Callback Verification
The Receiving Node MUST compute the passcode_hash in the exact same mannor as described in section 7.3.3,
then issue a POST request to the following endpoint on the Sending Node:
❯❯ URLJSON Payload:
https://<receiving-domain>/openmsg/auth-confirm/
❯❯ JSON
{
"receiving_openmsg_address": "012345*receivingdomain.com",
"passcode_hash": "4b46269af35d0d3....."
}
Field Descriptions:- receiving_openmsg_address: The Openmsg address of the Receiving Node (i.e., the account being connected to).
- passcode_hash: The hash as described in section 7.3.3. This confirms to the Sending Node that the callback corresponds to the initial handshake request so that the Sending Node can confirm that the request did initiate from their server.
7.4.5 Purpose
This step ensures the following:
- That the Receiving Domain issued the pass_code and that it is un-used and not expired.
- That the original handshake request was actually initiated by the declared Sending Node (i.e., the domain in the address wasn't spoofed).
- That the domain in the sender's address has a valid record of the pass_code, confirming intent to initiate the connection.
7.5.1 Handshake Verification (Step 3 - Sending Node)
This endpoint is used by the Receiving Node to confirm that the Sending Node originally initiated the
handshake. It ensures that no third-party server is impersonating the Sending Node's domain.
7.5.2 Confirm Handshake
Upon receiving the JSON POST request at:
Return a JSON error response immediately:
❯❯ URL
https://<sending-domain>/openmsg/auth-confirm/
The Receiving Node MUST:
- Extract the following fields from the request:
- receiving_openmsg_address
- passcode_hash
- Look up a previously stored handshake request that matches both:
- receiving_openmsg_address
- passcode_hash
- Validate timestamp:
- Check that the stored handshake request was created within the last 60 seconds.
- If the handshake has expired, return an error (see below).
- Delete or invalidate the pending handshake entry to prevent replay or reuse.
Return a JSON error response immediately:
❯❯ JSON
{
"error": true,
"error_code": "handshake_unknown",
"error_message": "unknown pending authorization with openmsg address and pass_code"
}
OR
❯❯ JSON
{
"error": true,
"error_code": "handshake_expired",
"error_message": "expired handshake, over 60 seconds old"
}
If the handshake is valid and timely:
❯❯ JSON
{
"success": true
}
7.5.3 Purpose
This step ensures the following:
- That original request did in fact originate from the domain in the sending_openmsg_address.
- That the handshake request is fresh and hasnt expired.
7.6 Finalizing the Handshake (Returning Auth/Ident/Encryption Keys)
Once the Receiving Node has successfully validated the pass code and verified the handshake via the callback
to the Sending Node, it proceeds to finalize the handshake.
7.6.1 – Callback Response Handling
Upon receiving the callback response (auth-confirm) from the Sending Node, the Receiving Node MUST:
If the callback response contains an error message, the Receiving Node MUST stop all further processing and return this error immediately to the Sending Node:
Proceed to next step
- Parse the response and check for the presence of an error flag.
- If the response indicates an error, the handshake MUST be aborted and an error response returned to the original handshake initiator.
If the callback response contains an error message, the Receiving Node MUST stop all further processing and return this error immediately to the Sending Node:
❯❯ JSON
{
"error": true,
"error_code": "<error_code from callback>",
"error_message": "Error: <message from callback>"
}
If there is another error, such as a 500 or 404, this SHOULD be returned to the Sending Node, for example:
❯❯ JSON
{
"error": true,
"error_code": "http_error_status",
"error_message": "Error: 500 error received from auth-confirm script"
}
In the case of success:Proceed to next step
7.6.2 – Generating Shared Connection Keys
If the callback is successful (
success: true), the Receiving Node MUST
generate the following cryptographic materials:
auth_code: A 32-byte random string (hex encoded)ident_code: A 32-byte random string (hex encoded)message_crypt_key: A 32-byte symmetric key (hex encoded), generated using libsodium's SECTRETBOX KEYGEN (e.g., sodium_crypto_secretbox_keygen()
⚠ Note: The two codes,auth_codeandident_code, MUST be cryptographically secure random values. All 3 values MUST be converted into hexadecimal (see: 4. Data Element Formats).
7.6.3 – Invalidating Previous Handshakes
Before storing the new connection, the Receiving Node MUST:
- Check for any existing stored connection entries involving the same combination of the two addresses:
sending_openmsg_addressreceiving_openmsg_address
- Delete any existing stored connection entries involving the same combination of the two addresses:
sending_openmsg_addressreceiving_openmsg_address
7.6.4 Storing New Connection - Receiving Node
The Receiving Node MUST persist the following values:
receiving_openmsg_address- Receiver’s Openmsg addresssending_openmsg_address- Sender’s Openmsg addresssending_openmsg_address_name- Human-readable display name of the sendersending_allow_replies- Boolean indicating whether the sender accepts replies (defaults totrueif not explicitly provided)auth_code- Shared auth code for message validationident_code- Shared identifier used to recognize the connectionmessage_crypt_key- Symmetric encryption key used to encrypt/decrypt messages
⚠ Note: Ifsending_allow_repliesis omitted ornull, it MUST default totrue.
7.6.5 – Returning the Shared Codes
The last stage of the handshake is returning the following JSON back to the Sending Node:
❯❯ URL
- success: TRUE
- auth_code
- ident_code
- message_crypt_key
- receiver_openmsg_address_name
❯❯ JSON
{
"success": "TRUE",
"auth_code": "<shared auth_code>",
"ident_code": "<shared ident_code>",
"message_crypt_key": "<shared message_crypt_key>",
"receiver_openmsg_address_name": "Jim Beam"
}
7.6.6 – Storing New Connection - Sending Node
The Sending Node MUST persist the following values:
receiving_openmsg_address- Receiver’s Openmsg addresssending_openmsg_address- Sender’s Openmsg addresssending_openmsg_address_name- Human-readable display name of the sendersending_allow_replies- Boolean indicating whether the sender accepts replies (defaults totrueif not explicitly provided)auth_code- Shared auth code for message validationident_code- Shared identifier used to recognize the connectionmessage_crypt_key- Symmetric encryption key used to encrypt/decrypt messages
8. Sending a Message
8.1 Node Roles
Once a successful handshake has been performed between two Nodes, both Nodes will have the same auth_code,
ident_code, and message_crypt_key stored for the respective Openmsg addresses. Either Node can now act as a
Sending Node or a Receiving Node. These roles are dynamic and depend only on the direction of the message
being sent.
8.2 The Message Protocol
Overview
Before a message can be sent between two users, their respective Nodes MUST have completed a successful
handshake.
In order to send a message, the Sending Node MUST have the full openmsg_address of the recipient Both Nodes MUST have the same shared auth_code, ident_code, and message_crypt_key associated with that recipient, obtained during the handshake
To send a message securely and verifiably, the Sending Node MUST construct a secure payload and MUST prepare for a verification callback. The Receiving Node performs several validation steps, including a callback to the senders domain before decrypting and storing the message.
The message sending process in Openmsg consists of two components:
Upon successful validation, the Receiving Node should:
When sending or receiving a message, Nodes MUST responde with set error codes (error_code) and response codes (response_code) so that errors can be handled effeciently. The plain text error messgage (error_message) can be customized.
In order to send a message, the Sending Node MUST have the full openmsg_address of the recipient Both Nodes MUST have the same shared auth_code, ident_code, and message_crypt_key associated with that recipient, obtained during the handshake
To send a message securely and verifiably, the Sending Node MUST construct a secure payload and MUST prepare for a verification callback. The Receiving Node performs several validation steps, including a callback to the senders domain before decrypting and storing the message.
The message sending process in Openmsg consists of two components:
- Sending Component – the logic on the Sending Node responsible for composing, encrypting, and POSTing the message to the appropriate endpoint on the recipient’s Node.
- Receiving Component – the logic on the Receiving Node responsible for validating, decrypting, and storing or displaying the incoming message.
- encrypt the message using the message_crypt_key and a message_nonce
- create a hash (message_hash) of the encrypted message & nonce (message_package) using a salt (message_salt), the auth_code and the current unix timestamp (message_timestamp)
- store the message_hash, message_nonce and the message_timestamp for later validation during a callback
- send specific data to the receiving node
❯❯ URLThe Receiving Node MUST be listening for incoming POSTs at the above endpoint, where it will:
https://<receiving-domain>/openmsg/message-receive/
- Locate the connected user details using the ident_code
- Verify the authenticity and freshness of the message data by recreating the hash using the auth_code it already has
- Performs a callback by issuing an HTTPS POST request to the domain specified in the Sender's Openmsg address at:
❯❯ URLThis step verifies that the message was genuinely initiated by the correct Sending Node and prevents domain spoofing or unauthorized message attempts.
https://<sending-domain>/openmsg/message-confirm/
Upon successful validation, the Receiving Node should:
- Decrypt the message using the message_crypt_key
- Store or process the message for the recipient user
When sending or receiving a message, Nodes MUST responde with set error codes (error_code) and response codes (response_code) so that errors can be handled effeciently. The plain text error messgage (error_message) can be customized.
❯❯ Plain Text
Errors:
error = TRUE
response_code | error_code | Plain Text
SM_E000 | unknown_error | message_data_missing | unknown error // Other error
SM_E001 | ident_code_invalid | user not known
SM_E002 | message_rejected_byuser | This address does not accept incomming messages // (Can be used for
noreply addresses or as a quiet way for a User to block a sender)
SM_E003 | message_unknown | Unknown Message. No outgoing message matching these details or message was
sent from an unauthorized domain domain.
SM_E004 | message_hash_invalid | could not recreate hash - auth_code mismatch, assuming message_timestamp
and message_package were sent correctly
SM_E005 | message_expired | Message sent over 60 seconds ago. Expired message request.
SM_E006 | message_decryption_error | The message decryption failed. Encryption key mis-match or corrupt
message, assuming nonce was sent correctly
SM_E007 | message_nonce_error | This message encryption nonce has been used before for this connection
SM_E008 | message_rejected_temp | Message rejected temporary - server is being maintained
SM_E009 | message_rejected_perm | Message rejected permanently (service has closed down, messages from
sending domain or IP address are blocked)
Success:
success = TRUE
response_code | Plain Text
SM_S888 | Message received successfully and accepted
8.2.1 Constructing and Sending the Message (Step 1 – Sending Node → Receiving Node)
To initiate sending a message, the Sending Node MUST perform the following steps:
8.2.2 Encrypt the message
Nonce
The Sending Node MUST generate a cryptographically secure message nonce using libsodium's SECTRETBOX NONCEBYTES. A new nonce MUST be freshly created for each message. The binary nonce will be used for encryption (
Message Encryption
The Sending Node MUST encrypt the plaintext message using the shared message_crypt_key and the new message_nonce using libsodium's SECRETBOX: sodium_crypto_secretbox (message_encrypted). A message_package MUST then be created ready to send to the Receiving Node.
Message Package
The Message Package (
The Sending Node MUST generate a cryptographically secure message nonce using libsodium's SECTRETBOX NONCEBYTES. A new nonce MUST be freshly created for each message. The binary nonce will be used for encryption (
message_nonce). This nonce MUST also be Base64 encoded using
Sodium's binary to base64 function with the URL SAFE NO PADDING flag MUST be set (message_nonce_encoded). The Base64 version MUST be persisted ready for a
verification callback from the Receiving Node.Message Encryption
The Sending Node MUST encrypt the plaintext message using the shared message_crypt_key and the new message_nonce using libsodium's SECRETBOX: sodium_crypto_secretbox (message_encrypted). A message_package MUST then be created ready to send to the Receiving Node.
Message Package
The Message Package (
message_package) MUST be a string concatenation of:
❯❯ Pseudo Codein that order, with no extra characters or spaces added. This string MUST then be Base64 encoded using Sodium's binary to base64 function. The URL SAFE NO PADDING flag MUST be set.
message_nonce + message_encrypted
8.2.3 Generate a Message Hash
A Message Hash (
Message Hash Salt
The Sending Node MUST enerate a random salt (
Message Timestamp
Record the current Unix timestamp (
Compute the message_hash
Generate the message_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
Persist the outgoing message metadata ("Outgoing Message")
The Sending Node MUST persist the following fields temporarily:
Optional but best practice: The Sending Node MAY also store the following fields temporarily:
message_hash) MUST now be created by the Sending Node. The
Message Hash is used by the Receiving Node verify that the Sending Node has the correct auth_code, that the
timestamp is genuine and the message is fresh, and that the message or any of the data hasn't been altered.
The Message Hash is also used in a callback request to verify the Sending Node.Message Hash Salt
The Sending Node MUST enerate a random salt (
message_salt). The salt MUST be
a randomly ganerated string containing uniformly selected random bytes, 16 bytes in length. A new Salt MUST
be generated for each Hash. It MUST then be converted from binary to hexadecimal before use. The output MUST
be an ASCII string containing the hexadecimal representation of the random bytes. A Salt MUST NOT be reused.
Message Timestamp
Record the current Unix timestamp (
message_timestamp) as a string.
Compute the message_hash
Generate the message_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
❯❯ Pseudo Codein that order, with no extra characters or spaces added.
message_package + auth_code + message_salt + message_timestamp
Persist the outgoing message metadata ("Outgoing Message")
The Sending Node MUST persist the following fields temporarily:
message_hashmessage_nonce_encodedmessage_timestamp
Optional but best practice: The Sending Node MAY also store the following fields temporarily:
sender's openmsg addressident_codemessage_text
8.2.4 Send the Message to the Receiving Node via HTTPS POST:
The Sending Node MUST issue a POST request to the following endpoint on the Receiving Node:
JSON Payload:
Field Descriptions:
❯❯ URLThe receiving domain is extracted from the Receiver's Openmsg Address. See 7.3.2.
https://<receiving-domain>/openmsg/message-receive/
JSON Payload:
❯❯ JSON
{
"receiving_openmsg_address_id": "678901",
"sending_openmsg_address_name": "New Display Name",
"ident_code": "<shared-ident-code>",
"message_package": "<base64-encoded nonce and encrypted message>",
"message_hash": "<hash value>",
"message_salt": "<randomly-generated hexadecimal salt>",
"message_timestamp": 1721225033,
"openmsg_version": 1.0
}
Field Descriptions:
- receiving_openmsg_address_id: The numeric user ID of the recipient on the Receiving Node. (Only the User ID part is sent not the whole address).
- sending_openmsg_address_name: Human-readable display name of the sender. Only include this if the Sending User wishes to update the Receiving User with a new display name.
- ident_code: The ident_code created during the Handshake
- openmsg_version: The current version of the Openmsg protocol being used by the Sending Node
8.3.1 Validating the Message & Callback (Step 2 – Receiving Node → Sending Node)
Once the Receiving Node receives an incoming message POST, it must verify the message
Upon receiving the JSON POST request at:
Upon receiving the JSON POST request at:
❯❯ URLThe Receiving Node MUST:
https://<receiving-domain>/openmsg/message-receive/
- Extract the following fields from the request:
- receiving_openmsg_address_id *
- sending_openmsg_address_name
- ident_code *
- message_package *
- message_hash *
- message_salt *
- message_timestamp *
- openmsg_version
8.3.2 Initial Message Validation
The Receiving Node MUST now check that all the required data is present in the JSON POST. See 8.2.5 for
requiried fields. If any of the required fields are missing the Node MUST return a JSON error response
immediately:
If there is no matching connection with the fields
❯❯ JSON
{
"error": true,
"response_code", "SM_E000"
"error_code": "message_data_missing",
"error_message": "Missing data from message JSON POST"
}
If all the fields are present, the Receiving Node MUST now:
- Look up the connection record matching the User
receiving_openmsg_address_idand the connection identification codeident_code - Retrieve the corresponding
auth_code - Recompute the hash using the exact same method as explained in 8.3.1. The hash MUST be created from a
concatenated string of:
❯❯ Pseudo Code
Where the data for
message_package + auth_code + message_salt + message_timestampmessage_package,message_salt,message_timestampMUST be taken from the POST originating from the Sending Node. Theauth_codeMUST be the Auth Code that was retreived by the Receiving Node in points 1 & 2 in this list of actions. - The resulting hash MUST be compared with the message_hash received from the Sending Node
- Verifie that message_timestamp is within an acceptable freshness window (e.g., no older than 60 seconds)
If there is no matching connection with the fields
receiving_openmsg_address_id & ident_code, if
the hash does not match or the timestamp is expired, the message MUST be rejected. Return a JSON error
response immediately:
❯❯ JSON
{
"error": true,
"response_code", "SM_E001"
"error_code": "ident_code_invalid",
"error_message": "No matching connection between these two users. ident_code not valid."
}
OR
❯❯ JSON
{
"error": true,
"response_code", "SM_E004"
"error_code": "message_hash_invalid",
"error_message": "There was an error with the message hash or authorization."
}
OR
❯❯ JSON
{
"error": true,
"response_code", "SM_E005"
"error_code": "message_expired",
"error_message": "Message / message hash is too old."
}
8.3.3 Extracting Callback Details
To prevent unauthorized domains from sending forged messages (even if they have the correct keys), the
Receiving Node performs a domain verification callback. The Receiving Node MUST:
- Look up the sender's Openmsg address (
sending_openmsg_address) associated with the connection (using the receiver's openmsg address and (ident_code). - Extract the domain from the (
sending_openmsg_address): - Sending User ID: the part before *
- Sending Domain: the part after *
8.3.4 Domain Verification via Callback
The Receiving Node MUST then issue a POST request to the following endpoint on the Sending Node:
❯❯ URLJSON Payload:
https://<sending-domain>/openmsg/message-confirm/
❯❯ JSON
{
"message_nonce": "<Base64 encoded nonce value>",
"message_hash": "<hash value>"
}
The message nonce sent for verification in the JSON payload MUST be the Base64 encoded version. See 8.2.2
8.3.5 Purpose
This step ensures the following:
- That the sender of the message knew the shared secret
auth_code - That the message is fresh and that no details have been altered
- That the original message request was actually initiated by the declared Sending Node (i.e., the domain in the address wasn't spoofed).
8.4.1 Message Verification (Step 3 - Sending Node)
This endpoint is used by the Receiving Node to confirm that the correct Sending Node originally initiated
the message. It ensures that no third-party server is impersonating the Sending Node's domain.
8.4.2 Confirm the Message
Upon receiving the JSON POST request at:
Return a JSON error response immediately:
❯❯ URLThe Receiving Node MUST:
https://<sending-domain>/openmsg/message-confirm/
- Extract the following fields from the request:
- message_nonce
- message_hash
- Look up a previously stored outgoing message that matches both:
- message_nonce
- message_hash
- Validate timestamp:
- Check that the stored outgoing message was initiated within the last 60 seconds.
- If the outgoing message has expired, return an error (see below).
Return a JSON error response immediately:
❯❯ JSON
{
"error": true,
"response_code": "SM_E000",
"error_code": "missing_data",
"error_message": "Missing message_hash or message_nonce "
}
OR
❯❯ JSON
{
"error": true,
"response_code": "SM_E003",
"error_code": "message_unknown",
"error_message": "Unknown Message. No outgoing message matching these details or message was sent from an unauthorized domain domain."
}
OR
❯❯ JSON
{
"error": true,
"response_code": "SM_E005",
"error_code": "message_expired",
"error_message": "Message sent over 60 seconds ago. Expired message request."
}
If the message is valid and timely:
❯❯ JSON
{
"success": true
}
8.4.3 Purpose
This step ensures the following:
-
That original request did in fact originate from the domain in the senders Openmsg address.
That the message is fresh and hasnt expired.
8.5.1 Final Verification and Message Storage (Step 4 - Receiving Node)
Once the Receiving Node has successfully validated the message hash and verified the Sending Node via the
callback to the senders domain, it proceeds to finalize the message.
8.5.2 Check the Nonce is Unique
Upon receiving a successful callback response from the Sending Node, the Receiving Node SHOULD check that
the Sending Node has not used the same Nonse before for that Sender/User connection. If the same Nonce has
been used before for that connection then the message SHOULD be rejected:
❯❯ JSON
{
"error": true,
"response_code": "SM_E007",
"error_code": "message_nonce_error",
"error_message": "This message encryption nonce has been used before for this sender/receiver connection."
}
8.5.3 Decrypt the Message
The Receiving Node MUST now attempt to decrypt the message (message_encrypted) using the message_crypt_key
and message_nonce. The Receiver MUST use libsodium's "secretbox open" function. The Receiving Node MUST use
the binary encoded version of message_crypt_key. As message_crypt_key will have been stored as hexdecimal
encoding, it MUST be encoded into binary using libsodium's hexdecimal to binary function. The result will be
the decrypted plaintext message (message_decrypted).
If the decryption fails, such as due to the message_crypt_key not being correct, the message MUST be rejected immediately:
❯❯ Pseudo Code
message_decrypted = sodium_crypto_secretbox_open(message_encrypted, message_nonce, sodium_hex2bin(message_crypt_key))
In the case of a failure:If the decryption fails, such as due to the message_crypt_key not being correct, the message MUST be rejected immediately:
❯❯ JSON
{
"error": true,
"response_code": "SM_E006",
"error_code": "message_decryption_error",
"error_message": "The message decryption failed. Encryption key mis-match or corrupt message."
}
8.5.4 Store the Message
Once the message has been decrypted successfully, it needs saving for the end User to access. It would be up
to each Node to decide what format or encoding to store the message in. The Receiving Node MUST re-encrypt
the message before persisting.
In the case of success:
The following JSON POST MUST be returned upon successful receipt of a message:
⚠ WARNING ⚠
The encrypted version of the message (message_encrypted) that was received from the Sending Node should not be stored as the main or only copy of the message; If the connected users refresh their encryption key, all stored messages would be unreadable and without the possibility of being decrypted (unless previous encryption keys are persisted for each message). The Node SHOULD instead have a private encryption key for that User's messages. The decrypted messages MUST be re-encrypted using the Receiver's own encryption key. The message MUST NOT be stored as plain text.
E2EE: End 2 End Encryption is recommended. See 9.0 - End 2 End Encryption.The Receiving Node MUST persist the message alongside the Receiver's Openmsg Address, the ident_code AND the message_hash.
⚠ Note: The Message Hash (message_hash) will be used for identifying
the message between Nodes for future operations such as Read Receipts, therefore storing message_hash is
imperative.
The Receiving Node MUST persist the Nonce (message_nonce) against the ident_code to prevent
future reuse (nonce replay protection).In the case of success:
The following JSON POST MUST be returned upon successful receipt of a message:
❯❯ JSON
{
"success": true,
"response_code": "SM_S888"
}
8.6 Mark as Sent (Step 5 - Sending Node)
Once the Receiving Node has successfully received the message and sent a SUCCESS response to the Sending
Node the Sending Node MUST mark the message as "Sent":
The Sending Node MUST delete the Outgoing Message (persisted in 8.2.3)
The Sending Node SHOULD persist the sent message data
The Sending Node MUST delete the Outgoing Message (persisted in 8.2.3)
The Sending Node SHOULD persist the sent message data
message_hash against
sending_openmsg_address and ident_code. The
Receiving Node might make a JSON POST to the Sending Node in the Future to mark the message as
"Read" when the Receiving User reads the message.
9. Read Receipts
9.1 Send Read Receipt / Mark as Read
Role-term Clarification:
- The Receiving Node is the original receiver of the message.
- The Sending Node is the original sender of the message.
9.1.1 Send the Read Receipt to the Sending Node via HTTPS POST:
In order to inform the Sending Node that the Receiver read the message, the Receiving Node MUST issue a POST
request to the following endpoint on the Sending Node:
The Receiving Node does not need to wait for a response from the Sending Node.
❯❯ URL
https://<sending-domain>/openmsg/message-read/
With the following JSON:
❯❯ JSON
{
"read": true,
"ident_code": "<shared-ident-code>",
"message_hash": "<message hash value>",
"read_timestamp": 1721225033
}
Field Descriptions:
- read: This must always be set to
true - ident_code: The shared ident_code created during the Handshake between the two users
- message_hash: The message_hash created in 8.2.3 and persisted in 8.5.4 as part of the Sent/Received Message
- read_timestamp: The Unix Timestamp of when the message was classed as being read by the receiver. This MUST NOT be a future timestamp
The Receiving Node does not need to wait for a response from the Sending Node.
9.2 Receiving the Read Receipt:
The Sending Node can decide to receive Read Receipts.
If it does, once the Sending Node receives an incoming message POST, it MUST verify the data.
Upon receiving the JSON POST request at:
Upon receiving the JSON POST request at:
❯❯ URLThe Sending Node MUST:
https://<sending-domain>/openmsg/message-read/
- Extract the following fields from the request:
- read
- ident_code
- message_hash
- read_timestamp
10. Login With Openmsg (omLogin)
Overview
"Login With Openmsg" (omLogin) is a method of using Openmsg to sign up for an account on a 3rd party website
or application without the user having to provide their email address.
It allows the website or application to communicate with the user securly through their Openmsg
account.
In this process the Sending Node will be the website that the User is signing up to and the Receiving Node is the User's Node.
The two main steps for omLogin are:
In this process the Sending Node will be the website that the User is signing up to and the Receiving Node is the User's Node.
The two main steps for omLogin are:
- Initial Handshake and connection between the two users as outlined in section 7 (the two users, for example, could be a website and an individual user)
- User authentication using an omLogin code provided by the user who is loging in. This is initiated by the Sending Node through a Login Verification Request
10.1 Initial Handshake
Step one of omLogin is the same Initial Handshake process from section 7, with a few additions.
There are technically no differences between a normal connection and one created to allow a user to login to a website using omLogin. The changes outlined below are only so the Receiving Node can allow the User to only create omLogin codes for specific connections. Without the knowledge of which connections are User-to-User, and which have been created to enable the User to login using omLogin, the Receiving Node (the User's service provider) would have to show an option to the User to create an (unusable) omLogin Code for every connection on their account. This would be harmless but pointless.
There are technically no differences between a normal connection and one created to allow a user to login to a website using omLogin. The changes outlined below are only so the Receiving Node can allow the User to only create omLogin codes for specific connections. Without the knowledge of which connections are User-to-User, and which have been created to enable the User to login using omLogin, the Receiving Node (the User's service provider) would have to show an option to the User to create an (unusable) omLogin Code for every connection on their account. This would be harmless but pointless.
10.1.1 Sending Node → Receiving Node
The User will provide their Openmsg address and a PassCode and the website will initiate the
Handshake.
The process is exactly the same except for an extra data field sent by the Sending Node to signify to the Receiving Node that the User will be using This connection to login, and therefore omLogin Codes will need to be made available for creation.
The extra data field will be added to the JSON payload as outlined in section 7.3.4. The Extra field name is
The process is exactly the same except for an extra data field sent by the Sending Node to signify to the Receiving Node that the User will be using This connection to login, and therefore omLogin Codes will need to be made available for creation.
The extra data field will be added to the JSON payload as outlined in section 7.3.4. The Extra field name is
omlogin_connection and must be set to true.
The JSON payload would therefore be similar to the following example:
❯❯ JSON
{
"receiving_openmsg_address_id": "012345",
"pass_code": "456123",
"sending_openmsg_address": "987654*sendingdomain.com",
"sending_openmsg_address_name": "Alice Smith",
"sending_allow_replies": true,
"omlogin_connection": true,
"openmsg_version": 1.0
}
For full details of the standard Initial Handshake, see section 7.
10.1.2 Receiving Node
The Receiving Node SHOULD persist
omlogin_connection=true against this
connection so it can allow the User to create omLogin Codes.
10.2 User Login
Overview
Step two of omLogin can only occure after a successful Inital Handshake. After this, the two Users will be
connected, and the connection MAY be marked as an omLogin connection on the User's Node.
To begin the Login Verification Request ("LVR"), the Sending Node must possess:
To begin the Login Verification Request ("LVR"), the Sending Node must possess:
- A valid omLogin Code issued by the user on the Receiving Node.
- The user's full Openmsg Address, from which the domain of the Receiving Node is derived.
❯❯ URLUpon receiving the request, the Receiving Node:
https://<receiving-domain>/openmsg/omlogin/
- Verifies the omLogin Code for the user associated with the provided Openmsg address.
- Validates the Code by generating a Hash that only a valid Node would be able to create and returns it via a JSON response.
- Validates the Hash and logs the user in.
10.2.1 User: Create an omLogin Code (Receiving Node)
Overview
An omLogin Code is generated by the Receiving Node, usually at the request of the User,
and given* to the Sending Node along with the Users Openmsg Address to allow the Sending Node to be able to
initiate the Login.
*For example, this will be entered into an online form.
*For example, this will be entered into an online form.
Creation
Each of the 6 letters in an omLogin Code (
omLogin_code) MUST be uppercase and MUST be a true random
selection. The omLogin Code MUST be persisted for later verification along with the timestamp of when it was created.
It MUST be set to expire after 60 seconds.
10.2.2 User Verification Request (Sending Node → Receiving Node)
To initiate the Login Verification Request, the Sending Node MUST
send a HTTPS POST request to the following endpoint on the Receiving Node:
❯❯ URL
https://<receiving-domain>/openmsg/omlogin/
The receiving domain is extracted from the Receiver's Openmsg Address as shown in section 7.3.2.
10.2.3 Sending Node Responsibilities
Before initiating the LVR, the Sending Node MUST create a omLogin_hash and possess the following data:
An omLogin Code Hash (
omLogin Code Hash Salt
The Sending Node MUST generate a random salt (
Login Attempt Timestamp
Record the current Unix timestamp (
Compute the omLogin_hash
Generate the omLogin_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
- auth_code (as created during the handshake)
- ident_code (as created during the handshake)
- omLogin_hash (see below)
- current_timstamp (Current Unix timestamp)
These are used later to validate the response from the Receiving Node confirming the legitimacy of the
LVR. The timestamp will be used during this validation to make sure the LVR is fresh.
An omLogin Code Hash (
omLogin_hash) MUST now be created by the Sending Node. The
omLogin Code Hash is used by the Receiving Node to verify that the Sending Node has the correct auth_code, that the
timestamp is genuine and the login attempt is fresh.omLogin Code Hash Salt
The Sending Node MUST generate a random salt (
omLogin_hash_salt). The salt MUST be
a randomly ganerated string containing uniformly selected random bytes, 16 bytes in length. A new Salt MUST
be generated for each Hash. It MUST then be converted from binary to hexadecimal before use. The output MUST
be an ASCII string containing the hexadecimal representation of the random bytes. A Salt MUST NOT be reused.
Login Attempt Timestamp
Record the current Unix timestamp (
login_timestamp) as a string.
Compute the omLogin_hash
Generate the omLogin_hash. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
❯❯ Pseudo Code
omLogin_code + auth_code + omLogin_hash_salt + login_timestamp
in that order, with no extra characters or spaces added.
10.2.4 Request Payload
The Sending Node sends the following JSON-encoded data in the POST request body:
❯❯ JSON
{
"receiving_openmsg_address_id": "012345",
"ident_code": "<shared-ident-code>",
"omLogin_code": "ABCXYZ",
"omLogin_hash": "<hash value>",
"omLogin_hash_salt": "<randomly-generated hexadecimal salt>",
"login_timestamp": 1721225033
}
Field Descriptions:
- receiving_openmsg_address_id: The numeric user ID of the recipient on the Receiving Node. (Only the User ID part is sent not the whole address).
- ident_code: The ident_code created during the Handshake
- omLogin_code: A single-use, time-limited pass code previously issued by the recipient to allow this connection attempt.
- omLogin_hash: The hash created above.
- omLogin_hash_salt: The randomly-generated hexadecimal salt.
10.2.3 User Verification (Receiving Node)
Upon receiving the JSON POST request at:
If the omLogin_code does not match any omLogin Codes created for the User, the validation MUST fail.
If the login_timestamp or stored omLogin_code is more than 60 seconds old, the validation MUST fail.
❯❯ URLThe Receiving Node MUST:
https://<receiving-domain>/openmsg/auth/
- Extract the following fields from the request:
- ident_code
- omLogin_code
- omLogin_hash
- omLogin_hash_salt
- login_timestamp
- Verify that the provided omLogin_code is:
- Valid for the receiving User (as identified by ident_code and receiving_openmsg_address_id), else validation MUST fail.
- Not expired (it MUST have been issued within the last 60 seconds), else validation MUST fail.
- Then:
- Retrieve the stored auth_code that is associated with the ident_code (stored in section 7.6.4)
- Recalculate omLogin_hash using the auth_code. The recalculated omLogin_hash and the received omLogin_hash MUST match, else validation MUST fail.
If the omLogin_code does not match any omLogin Codes created for the User, the validation MUST fail.
If the login_timestamp or stored omLogin_code is more than 60 seconds old, the validation MUST fail.
10.2.4 Recalculate Hash
The Receiving Node must now attempt to recreate the Hash omLogin_hash using the received values of
omLogin_code, omLogin_hash_salt, login_timestamp and also using the stored auth_code.
The hash must be recreated in the exact same mannor as described in section 10.2.3.
A matching Hash will prove that the Sending Node holds the shared private auth_code, and isn't a malicious node sniffing for active login codes.
If the received hash and the calculated hash do not match, the validation MUST fail. If validation fails:
Return a JSON error response immediately:
If the received hash and the calculated hash do not match, the validation MUST fail. If validation fails:
Return a JSON error response immediately:
❯❯ JSON
{
"error": true,
"error_code": "omlogin_code_invalid",
"error_message": "omlogin code not valid"
}
OR
❯❯ JSON
{
"error": true,
"error_code": "omlogin_code_expired",
"error_message": "expired omlogin code, over 1 minute old"
}
If validation succeeds:- The omLogin_code MUST be deleted or marked as used, to prevent reuse.
- Return a JSON success response immediately (See 10.2.4 User Verification Confirmation).
10.2.5 User Verification Confirmation (Receiving Node → Sending Node)
If the validation is suucessful, the Receiving Node now MUST:
- Calculate a new Hash.
- Return a JSON success response immediately
10.2.6 Calculate a New Hash
The Receiving Node MUST now calculate a new HASH using a new Salt which will be included in the JSON response.
The Sending Node will recreate this using the private auth_code, which will act as proof that the
User Verification Confirmation came from the correct Node. and not a malicious man-in-the-middle.
omLogin Code Hash Salt
The Receiving Node MUST generate a random salt (
Login Attempt Timestamp
The Unix timestamp (
Compute the omLogin_hash_2
Generate the omLogin_hash_2. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
The Receiving Node MUST generate a random salt (
omLogin_hash_salt_2). The salt MUST be
a randomly ganerated string containing uniformly selected random bytes, 16 bytes in length. A new Salt MUST
be generated for each Hash. It MUST then be converted from binary to hexadecimal before use. The output MUST
be an ASCII string containing the hexadecimal representation of the random bytes. A Salt MUST NOT be reused.
Login Attempt Timestamp
The Unix timestamp (
login_timestamp) as a string as provided by the Sending Node earlier.
Compute the omLogin_hash_2
Generate the omLogin_hash_2. The hash MUST use SHA-256. The hash MUST be created from a concatenated string of:
❯❯ Pseudo Code
omLogin_code + auth_code + omLogin_hash_salt_2 + login_timestamp
in that order, with no extra characters or spaces added.
The Receiving Node respondes with the following JSON-encoded data:
❯❯ JSON
{
"omLogin_hash_2": "<hash value>",
"omLogin_hash_salt_2": "<randomly-generated hexadecimal salt>"
}
Field Descriptions:
- omLogin_hash_2: The hash created above by the Sending Node.
- omLogin_hash_salt_2: The randomly-generated hexadecimal salt created by the Sending Node.
10.2.7 Login Verification (Sending Node)
Upon receiving the JSON response, the Sending Node MUST:
- Extract the following fields from the request:
- omLogin_hash_2
- omLogin_hash_salt_2
- Check that omLogin_hash_salt_2 is different to omLogin_hash_salt. They MUST be different, else login MUST fail.
- Recalculate omLogin_hash_2 using the auth_code that it has stored, and also using omLogin_hash_salt_2. The recalculated omLogin_hash_2 and the received omLogin_hash_2 MUST match, otherwise login MUST fail.
- Then:
- Either log the user in if all steps are valid, or fail login.