Packages

Gleam wrapper for Signal Protocol cryptographic primitives

Current section

Files

Jump to
Raw

src/utils.gleam

import pre_key_bundle
import session
import signal_protocol.{
type IdentityKeyPair, type PreKey, type PreKeyBundle, type Session,
type SignedPreKey,
}
/// Generates a complete set of keys for a new user.
pub fn generate_user_keys() -> Result(
#(IdentityKeyPair, PreKey, SignedPreKey),
String,
) {
case signal_protocol.generate_identity_key_pair() {
Ok(identity_key_pair) -> {
case signal_protocol.generate_pre_key(1) {
Ok(pre_key) -> {
case
signal_protocol.generate_signed_pre_key(
identity_key_pair.public_key,
1,
)
{
Ok(signed_pre_key) -> {
Ok(#(identity_key_pair, pre_key, signed_pre_key))
}
Error(e) -> Error("Failed to generate signed pre-key: " <> e)
}
}
Error(e) -> Error("Failed to generate pre-key: " <> e)
}
}
Error(e) -> Error("Failed to generate identity key pair: " <> e)
}
}
/// Creates a pre-key bundle from user keys.
pub fn create_user_bundle(
registration_id: Int,
identity_key: String,
pre_key: PreKey,
signed_pre_key: SignedPreKey,
) -> Result(PreKeyBundle, String) {
pre_key_bundle.create(
registration_id,
identity_key,
pre_key,
signed_pre_key,
"0",
// Base key will be generated during session creation
)
}
/// Establishes a session between two users using their pre-key bundles.
pub fn establish_session(
local_identity_key: String,
local_registration_id: Int,
local_pre_key: PreKey,
local_signed_pre_key: SignedPreKey,
remote_identity_key: String,
remote_registration_id: Int,
remote_pre_key: PreKey,
remote_signed_pre_key: SignedPreKey,
) -> Result(#(Session, Session), String) {
// Create local bundle
case
create_user_bundle(
local_registration_id,
local_identity_key,
local_pre_key,
local_signed_pre_key,
)
{
Ok(local_bundle) -> {
// Create remote bundle
case
create_user_bundle(
remote_registration_id,
remote_identity_key,
remote_pre_key,
remote_signed_pre_key,
)
{
Ok(remote_bundle) -> {
// Create local session
case session.create(local_identity_key, remote_identity_key) {
Ok(local_session) -> {
// Create remote session
case session.create(remote_identity_key, local_identity_key) {
Ok(remote_session) -> {
// Process bundles
case
session.process_pre_key_bundle(local_session, remote_bundle)
{
Ok(Nil) -> {
case
session.process_pre_key_bundle(
remote_session,
local_bundle,
)
{
Ok(Nil) -> {
Ok(#(local_session, remote_session))
}
Error(e) ->
Error(
"Failed to process local bundle on remote session: "
<> e,
)
}
}
Error(e) ->
Error(
"Failed to process remote bundle on local session: "
<> e,
)
}
}
Error(e) -> Error("Failed to create remote session: " <> e)
}
}
Error(e) -> Error("Failed to create local session: " <> e)
}
}
Error(e) -> Error("Failed to create remote bundle: " <> e)
}
}
Error(e) -> Error("Failed to create local bundle: " <> e)
}
}
/// Sends a message and returns both the ciphertext and the session.
pub fn send_message_with_session(
session: Session,
message: String,
) -> Result(#(String, Session), String) {
case session.encrypt_message(session, message) {
Ok(ciphertext) -> {
Ok(#(ciphertext, session))
}
Error(e) -> Error("Failed to encrypt message: " <> e)
}
}
/// Receives a message and returns both the plaintext and the session.
pub fn receive_message_with_session(
session: Session,
ciphertext: String,
) -> Result(#(String, Session), String) {
case session.decrypt_message(session, ciphertext) {
Ok(message) -> {
Ok(#(message, session))
}
Error(e) -> Error("Failed to decrypt message: " <> e)
}
}
/// Performs a complete message exchange between two sessions.
pub fn exchange_messages(
local_session: Session,
remote_session: Session,
message: String,
) -> Result(#(String, Session, Session), String) {
case send_message_with_session(local_session, message) {
Ok(#(ciphertext, local_session)) -> {
case receive_message_with_session(remote_session, ciphertext) {
Ok(#(received_message, remote_session)) -> {
Ok(#(received_message, local_session, remote_session))
}
Error(e) -> Error("Failed to receive message: " <> e)
}
}
Error(e) -> Error("Failed to send message: " <> e)
}
}
/// Verifies that a message exchange was successful by comparing the sent and received messages.
pub fn verify_message_exchange(
sent_message: String,
received_message: String,
) -> Result(Nil, String) {
case sent_message == received_message {
True -> Ok(Nil)
False ->
Error(
"Message verification failed: sent and received messages do not match",
)
}
}