Send Message
See first: Handshake: Guide
See also: Send Message: Reference
Before you can send a message, you first need to have completed a successful handshake to establish trust between the Sending Node and the Receiving Node/User.
To sucessfully send a message, the Sending Node needs to know the openmsg_address. Both the Sending Node and the Receiving Node need to have the same auth_code, ident_code and message_crypt_key which were established in the handshake. For more detail about these, see the Reference using the link above.
Step 1
The Sending Node encrypts the message using the message_crypt_key and a message_nonce. It also creates a hash (message_hash) of the encrypted message (message_package) using a salt (message_salt), the auth_code and the current unix timestamp (message_timestamp).
The Sending Node should store the message_hash, message_nonce and the message_timestamp temporarily in table '
The Sending Node sends to the Receiving Node the openmsg_address and ident_code, the encrypted message (message_package) and message_nonce, the message_hash, message_salt and message_timestamp (but not the auth_code or message_crypt_key as the Receiving Node has these already).
Step 2a
The Receiving Node now does a check on the information to verify the hash. It does a database lookup in table '
Step 2b
The Receiving Node does a database lookup and gets the sending_openmsg_address that is assosiated with openmsg_address_id and ident_code. It then does a curl request to the domain in the sending_openmsg_address sending the openmsg_address_id and message_hash for the Sending Node to verify that the message came from that domain. This means that even if the auth_code, ident_code etc are leaked, only the domain in the openmsg_address or sending_openmsg_address can use them.
Step 2c
Once the Sending Node verifies that the request came from them, the Receiving Node will now check that message_nonce in message_package hasnt been used before, and then decrypt the message and store it on the User's account. Also store message_nonce so it cant be used again.
See also: Send Message: Reference
Before you can send a message, you first need to have completed a successful handshake to establish trust between the Sending Node and the Receiving Node/User.
To sucessfully send a message, the Sending Node needs to know the openmsg_address. Both the Sending Node and the Receiving Node need to have the same auth_code, ident_code and message_crypt_key which were established in the handshake. For more detail about these, see the Reference using the link above.
Step 1
The Sending Node encrypts the message using the message_crypt_key and a message_nonce. It also creates a hash (message_hash) of the encrypted message (message_package) using a salt (message_salt), the auth_code and the current unix timestamp (message_timestamp).
The Sending Node should store the message_hash, message_nonce and the message_timestamp temporarily in table '
openmsg_messages_outbox
' ready for the Receiving Node to verify that the request came from the correct domain.The Sending Node sends to the Receiving Node the openmsg_address and ident_code, the encrypted message (message_package) and message_nonce, the message_hash, message_salt and message_timestamp (but not the auth_code or message_crypt_key as the Receiving Node has these already).
Step 2a
The Receiving Node now does a check on the information to verify the hash. It does a database lookup in table '
openmsg_user_connections
' for a row that has openmsg_address and ident_code and retreives the corresponding auth_code. It then creates a hash of "$message_package.$auth_code.$message_salt.$message_timestamp" and compares this to the message_hash that was sent to it. If they match then the sender must have the secret auth_code to have created the hash. The Receiving Node also checks that message_timestamp isnt expired. Step 2b
The Receiving Node does a database lookup and gets the sending_openmsg_address that is assosiated with openmsg_address_id and ident_code. It then does a curl request to the domain in the sending_openmsg_address sending the openmsg_address_id and message_hash for the Sending Node to verify that the message came from that domain. This means that even if the auth_code, ident_code etc are leaked, only the domain in the openmsg_address or sending_openmsg_address can use them.
Step 2c
Once the Sending Node verifies that the request came from them, the Receiving Node will now check that message_nonce in message_package hasnt been used before, and then decrypt the message and store it on the User's account. Also store message_nonce so it cant be used again.
Sending Component
Send Message
The Sending Node creates message_nonce and message_salt. message_encrypted is created by encrypting message_text using the message_crypt_key. message_package is then created by simply joining message_nonce and message_encrypted together (and base64 encoded). The nonce and salt should only be used for this message and new ones should be created for each message.
The Sending Node then creates a message_hash from message_package, auth_code, message_salt and message_timestamp all stitched together as one string.
The Sending Node should store openmsg_address, the message_hash and the message_timestamp temporarily in table '
The Sending Node sends to the Receiving Node the openmsg_address_id and ident_code, the encrypted message (message_package) and message_nonce, the message_hash, message_salt and message_timestamp (but not the auth_code as the Receiving Node has this already).
The Sending Node then creates a message_hash from message_package, auth_code, message_salt and message_timestamp all stitched together as one string.
The Sending Node should store openmsg_address, the message_hash and the message_timestamp temporarily in table '
openmsg_messages_outbox
' ready for the Receiving Node to verify that the request came from that domain.The Sending Node sends to the Receiving Node the openmsg_address_id and ident_code, the encrypted message (message_package) and message_nonce, the message_hash, message_salt and message_timestamp (but not the auth_code as the Receiving Node has this already).
PHP
send-message.php
<?php
// ##### See: openmsg.io/docs/v1
// This page should be in the Users login protected area
require "../openmsg_settings.php";
$message_text = "Hello Openmsg user!"; // message to be sent
$sending_openmsg_address = "1000000*".$my_openmsg_domain; // To Do: Enter the from address for the Sending Node
$receiving_openmsg_address = "1000001*".$my_openmsg_domain; // To Do: The address where the message is being sent to
function send_message($db, $message_text, $sending_openmsg_address, $receiving_openmsg_address, $sandbox_dir){
$stmt = $db->prepare("SELECT auth_code, ident_code, message_crypt_key FROM openmsg_user_connections WHERE self_openmsg_address = ? AND other_openmsg_address = ?");
$stmt->bind_param("ss", $sending_openmsg_address, $receiving_openmsg_address);
$stmt->execute(); // To Do: Un-comment
$stmt->store_result();
$stmt->bind_result($auth_code, $ident_code, $message_crypt_key);
$stmt->fetch();
$matching_connections = $stmt->num_rows;
$stmt->close();
if(!($matching_connections > 0 && $auth_code && $ident_code && $message_crypt_key)){
$response = array("error"=>TRUE, "error_message"=>"No matching connection between these two users $sending_openmsg_address, $receiving_openmsg_address", "response_code"=>"");
return($response);
}
$receiving_openmsg_address_id = explode("*", $receiving_openmsg_address)[0];
$receiving_openmsg_address_domain = explode("*", $receiving_openmsg_address)[1];
$message_nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$message_nonce_encoded = base64_encode($message_nonce);
$message_encrypted = sodium_crypto_secretbox($message_text, $message_nonce, sodium_hex2bin($message_crypt_key));
$message_package = base64_encode($message_nonce . $message_encrypted);
$message_salt = bin2hex(random_bytes(16));
$message_timestamp = time();
$message_hash = hash("sha256", $message_package.$auth_code.$message_salt.$message_timestamp);
// Save message_hash and message_nonce to database table "openmsg_messages_outbox".
// The Receiving Node will use this in a return curl request to confirm the origin domain
$stmt = $db->prepare("INSERT INTO openmsg_messages_outbox (self_openmsg_address, ident_code, message_hash, message_nonce, message_text)
VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssss", $sending_openmsg_address, $ident_code, $message_hash, $message_nonce_encoded, $message_text);
$stmt->execute(); // To Do: Un-comment
$stmt->close();
// ##### Start CURL request #####
// The url is created from the domain in the Openmsg address
$url = "https://".$receiving_openmsg_address_domain."/openmsg".$sandbox_dir."/message-receive/";
// JSON data
$data =
array(
"receiving_openmsg_address_id" => $receiving_openmsg_address_id,
"sending_openmsg_address_name" => $sending_openmsg_address_name, // only include this if you want to update the name assosiated with the sender. The Receiving Node should update this on their end.
"ident_code" => $ident_code,
"message_package" => $message_package,
"message_hash" => $message_hash,
"message_salt" => $message_salt,
"message_timestamp" => $message_timestamp,
"openmsg_version" => 1.0,
// Verified account data can be left blank
"verified_account" => array(
"verified_account_signature" => $verified_account_signature,
"verified_account_name" => $verified_account_name,
"verified_account_expires" => $verified_account_timestamp
)
);
$data = json_encode($data);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
$info = curl_getinfo($curl);
if ($info["http_code"] == 301 || $info["http_code"] == 302) {
$redirectUrl = $info["redirect_url"]; // Not always present; you may need to parse headers manually
curl_setopt($curl, CURLOPT_URL, $redirectUrl);
$response = curl_exec($curl);
}
$response = json_decode($response, true);
curl_close($curl);
// Check for errors
if($err = curl_error($curl)) return(array("error"=>TRUE, "response_code"=>"SM_E000", "error_message"=>"Error: $err"));
if(($curl_status = curl_getinfo($curl, CURLINFO_HTTP_CODE)) != 200) return(array("error"=>TRUE, "response_code"=>$response_code, "error_message"=>"cURL response status: $curl_status"));
if($response["error"]) return(array("error"=>TRUE, "response_code"=>$response_code, "error_message"=>"Error: ".$response["error_message"]));
if(($success = $response["success"]) != TRUE) return(array("error"=>TRUE, "response_code"=>$response_code, "error_message"=>"Unsuccessful"));
// Save message data in "Sent" table
$stmt = $db->prepare("INSERT INTO openmsg_messages_sent (self_openmsg_address, ident_code, message_hash, message_text)
VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssss", $sending_openmsg_address, $ident_code, $message_hash, $message_text);
$stmt->execute(); // To Do: Un-comment
$stmt->close();
$stmt = $db->prepare("DELETE FROM openmsg_messages_outbox WHERE message_hash = ? AND message_nonce = ?");
$stmt->bind_param("ss", $message_hash, $message_nonce_encoded);
$stmt->execute(); // To Do: Un-comment
$response = array("success"=>TRUE, "response_code"=>$response["response_code"]);
return($response);
}
$response = send_message($db, $message_text, $sending_openmsg_address, $receiving_openmsg_address, $sandbox_dir);
echo print_r($response);
/*
Response codes:
SM_S888 = success, message accepted and delivered
Errors:
SM_E000 = unknown error
SM_E001 = user not known
SM_E002 = sender not authorized
SM_E003 = message did not originate from correct domain
SM_E004 = could not recreate hash // auth_code mismatch, assuming message_timestamp and message_package were sent correctly
SM_E005 = hash expired
SM_E005 = could not decrypt message // message_key mismatch, assuming nonce was sent correctly
*/
?>
When the Receiving Node receives the openmsg_address_id and ident_code from the Sending Node, it will make a cURL post back to the Sending Node to the domain from sending_openmsg_address. The Sending Node will check its database for an outgoing message with a matching message_hash and message_nonce, and will confirm that the request did originate from them.
After a message has sucessfully been delivered,
After a message has sucessfully been delivered,
PHP
/openmsg/message-confirm
<?php
// ##### See: openmsg.io/docs/v1
require "../openmsg_settings.php";
$data = json_decode(file_get_contents("php://input"), true);
$message_hash = $data["message_hash"];
$message_nonce = $data["message_nonce"];
function message_confirm ($db, $message_hash, $message_nonce){
// Query database table "outbox" to check for a pending outgoing message that matches message_hash, message_nonce and message_timestamp that hasnt expired. The outbox message should expire after 60 seconds.
if(!$message_hash || !$message_nonce){
$response = array("error"=>TRUE, "response_code"=>"SM_E003", "error_message"=>"Missing message_hash or message_nonce (qUfd6)");
return($response);
}
$stmt = $db->prepare("SELECT * FROM openmsg_messages_outbox WHERE message_hash = ? AND message_nonce = ?");
$stmt->bind_param("ss", $message_hash, $message_nonce);
$stmt->execute(); // To Do: Un-comment
$stmt->store_result();
$stmt->fetch();
$matching_messages = $stmt->num_rows;
$stmt->close();
if($matching_messages == 0) {
// If there are errors then return error and error_message
$response = array("error"=>TRUE, "response_code"=>"SM_E003", "error_message"=>"unknown pending message (poyQ2)");
return($response);
}
$response = array("success"=>TRUE);
return($response);
}
$response = message_confirm ($db, $message_hash, $message_nonce); // $db = database connection
echo json_encode($response);
?>
Upon delivery of the message, the Receiving Node should remove the row from the '
openmsg_messages_outbox
' table. The message should be added to the 'openmsg_messages_sent
' table. This record should include ident_code, message_hash and message_text. See the 'Setup' page for full table fields.
Receiving Component
Verify Message
Verify message hash, check expiry, origin, and decrypt message
The Receiving Node now checks the message hash using the auth_code that is assosiated with the ident_code in its database (this is set during the handshake), and the message_timestamp. If the Receiving Node can recreate the hash, then the auth_code the Receiving Node has must match the auth_code the Sending Node used to create the original hash, and message_timestamp must be accurate.
The Receiving Node then checks that the pending message hasnt expired. message_timestamp should not be more than 60 seconds old.
The Receiving Node then extracts the domain from sending_openmsg_address and makes a curl request to that domain, asking if it recently sent a message using message_hash and message_nonce. The Receiving Node then decrypts the message using message_crypt_key.
The Receiving Node now checks the message hash using the auth_code that is assosiated with the ident_code in its database (this is set during the handshake), and the message_timestamp. If the Receiving Node can recreate the hash, then the auth_code the Receiving Node has must match the auth_code the Sending Node used to create the original hash, and message_timestamp must be accurate.
The Receiving Node then checks that the pending message hasnt expired. message_timestamp should not be more than 60 seconds old.
The Receiving Node then extracts the domain from sending_openmsg_address and makes a curl request to that domain, asking if it recently sent a message using message_hash and message_nonce. The Receiving Node then decrypts the message using message_crypt_key.
PHP
/openmsg/message-receive
<?php
// ##### See: openmsg.io/docs/v1
require "../openmsg_settings.php";
$data = json_decode(file_get_contents("php://input"), true);
$receiving_openmsg_address_id = $data["receiving_openmsg_address_id"];
$sending_openmsg_address_name = $data["sending_openmsg_address_name"];
$ident_code = $data["ident_code"];
$message_package = $data["message_package"];
$message_hash = $data["message_hash"];
$message_salt = $data["message_salt"];
$message_timestamp = $data["message_timestamp"];
//require "account-verify.php"; // this will be added in future versions
$verified_account_signature = $data["verified_account"]["verified_account_signature"];
$verified_account_name = $data["verified_account"]["verified_account_name"];
$verified_account_expires = $data["verified_account"]["verified_account_expires"];
function message_check ($db, $receiving_openmsg_address_id, $ident_code, $message_package, $message_hash, $message_salt, $message_timestamp, $my_openmsg_domain, $sandbox_dir){
if(!$receiving_openmsg_address_id || !$ident_code || !$message_package || !$message_hash || !$message_salt || !$message_timestamp || !$my_openmsg_domain) {
// If the database doesnt contain an ident_code / openmsg_address_id combo
// or there are other errors then return error and error_message
$response = array("error"=>TRUE, "response_code"=>"SM_E000", "error_message"=>"Missing data (wMv4J)");
return($response);
}
$receiving_openmsg_address = $receiving_openmsg_address_id."*".$my_openmsg_domain;
// Query database to retreive auth_code, message_crypt_key and other_openmsg_address that matches ident_code and openmsg_address_id
$stmt = $db->prepare("SELECT auth_code, message_crypt_key, other_openmsg_address FROM openmsg_user_connections WHERE self_openmsg_address = ? AND ident_code = ?");
$stmt->bind_param("ss", $receiving_openmsg_address, $ident_code);
$stmt->execute(); // To Do: Un-comment
$stmt->store_result();
$stmt->bind_result($auth_code, $message_crypt_key, $sending_openmsg_address);
$stmt->fetch();
$matching_connections = $stmt->num_rows;
$stmt->close();
if(!$auth_code || !$ident_code || !$message_crypt_key || !$sending_openmsg_address) {
// If the database doesnt contain an ident_code / openmsg_address_id combo
// or there are other errors then return error and error_message
$response = array("error"=>TRUE, "response_code"=>"SM_E001", "error_message"=>"Could not find user::: $receiving_openmsg_address (rB6Xl)");
return($response);
}
// Split the from Openmsg address to get the the domain
$sending_openmsg_address_domain = explode("*", $sending_openmsg_address)[1];
//Check the hash is valid
$message_hash_test = hash("sha256", $message_package.$auth_code.$message_salt.$message_timestamp);
if($message_hash!=$message_hash_test) return (array("error"=>TRUE, "response_code"=>"SM_E004", "error_message"=>"There was an error with the authorization (4NxWV)"));
$message_hash_expiry_seconds = 60;
if(($message_timestamp+$message_hash_expiry_seconds) < time()) return (array("error"=>TRUE, "response_code"=>"SM_E005", "error_message"=>"Hash is too old (kmqVE)"));
// Decode the message package
$message_package_decoded = base64_decode($message_package);
// Split the package into nonce and ciphertext
$message_nonce = mb_substr($message_package_decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, "8bit");
$message_nonce_encoded = base64_encode($message_nonce);
$message_encrypted = mb_substr($message_package_decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, "8bit");
// ##### Start CURL request #####
// The url is created from the domain in the initiating Openmsg address
// The originating domain checks its temporary records for a outgoing message with message_hash and message_nonce
// This confirms that the request has come from the domain in the sending_openmsg_address
$url = "https://".$sending_openmsg_address_domain."/openmsg".$sandbox_dir."/message-confirm/";
// JSON data
$data =
array(
"message_hash" => $message_hash,
"message_nonce" => $message_nonce_encoded
);
$data = json_encode($data);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
$info = curl_getinfo($curl);
if ($info["http_code"] == 301 || $info["http_code"] == 302) {
$redirectUrl = $info["redirect_url"]; // Not always present; you may need to parse headers manually
curl_setopt($curl, CURLOPT_URL, $redirectUrl);
$response = curl_exec($curl);
}
$response = json_decode($response, true);
curl_close($curl);
// Check for errors
$err = curl_error($curl);
$error = $response["error"];
$error_message = $response["error_message"];
curl_close($curl);
if($err = curl_error($curl)) return(array("error"=>TRUE, "response_code"=>"SM_E000", "error_message"=>"Error: $err $sending_openmsg_address (vSiFb)"));
$curl_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if($curl_status == 404) return(array("error"=>TRUE, "response_code"=>"SM_E003", "error_message"=>"No reply from host $sending_openmsg_address_domain - cURL response status: $curl_status (i9ZsS)"));
if($curl_status != 200) return(array("error"=>TRUE, "response_code"=>"SM_E000", "error_message"=>"$sending_openmsg_address_domain cURL response status: $curl_status (0xOIw)"));
if($response["error"]) return(array("error"=>TRUE, "response_code"=>$response["response_code"], "error_message"=>"Error: ".$response["error_message"]." (PqN9h)"));
if(($success = $response["success"]) != TRUE) return(array("error"=>TRUE, "response_code"=>"SM_E000", "error_message"=>"Unsuccessful - unknown reason (x8rXc)"));
// Now attempt to decrypt the message
$message_decrypted = sodium_crypto_secretbox_open($message_encrypted, $message_nonce, sodium_hex2bin($message_crypt_key));
if ($message_decrypted === false) {
return(array("error"=>TRUE, "response_code"=>"SM_E005", "error_message"=>"Invalid key or corrupt message (QctWn)"));
}
$message_text = $message_decrypted;
// $message_text should be re-encrypted using your own encrytion key before saving:
// $message_text = sodium_crypto_secretbox($message_text ...
// Receiving Node saves the message for the User to access
$stmt = $db->prepare("INSERT INTO openmsg_messages_inbox (self_openmsg_address, ident_code, message_hash, message_text)
VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssss", $receiving_openmsg_address, $ident_code, $message_hash, $message_text);
$stmt->execute(); // To Do: Un-comment
$stmt->close();
// To Do: Receiving Node to update other_openmsg_address_name if new name is provided
$response = array("success"=> TRUE, "response_code"=>"SM_S888");
return($response);
}
$response = message_check ($db, $receiving_openmsg_address_id, $ident_code, $message_package,
$message_hash, $message_salt, $message_timestamp, $my_openmsg_domain, $sandbox_dir); // $db = database connection
echo json_encode($response);
?>
The Receiving Node should save the message to their database ready for the User to open. It should not save message_package or the message encrypted using the original encryption key. The is because if message_key is ever compromised and needs to be changed, old messages would not be able to be decrypted. The Receiving Node should store the message using their own encryption.
!Important
The Receiving Node MUST receive a valid auth_code to authorize the sender. This is core to Openmsg.
To Finish
Recap
- The Sending Node encrypts the message using the stored message_crypt_key and a new message_nonce
- The Sending Node combines message_nonce and message_encrypted into one string and base64 encodes it. This is now the message_package
- The Sending Node creates message_hash of a combined string of message_package, auth_code, message_salt and message_timestamp
- The Sending Node sends openmsg_address_id, ident_code, message_hash, message_package, message_salt, message_timestamp to the Receiving Node
- The Receiving Node queries their database to retreive auth_code, message_key and sending_openmsg_address that matches ident_code and openmsg_address_id
- The Receiving Node checks that the hash is valid by recreating it using the data from the Sending Node and its own auth_code, and comparing it against the hash the Sending Node sent
- The Receiving Node checks that the message hash has not expired (is not over 60 seconds old)
- The Receiving Node sends a curl request to the domain in sending_openmsg_address, along with message_hash and message_nonce to confirm that the domain in the from address sent the message. The Sending Node removes the message row from its 'outbox'
- The Receiving Node verifies that it can decrypt the message
- The Receiving Node sends back to the Sending Node that delivery was successful
- The Sending Node can save the send message in a "Sent" table, using message_hash as its ID
Next
You should now have:
- Set up your database tables
- Uploaded the protocol files to your webiste
- Uploaded the test web forms to your website (somewhere password protected)
- Setting up two test Users
- Generating a new pass_code
- Initiate a handshake
- Sending a message from one user to another.