From 58392841cd950ebd077a9a6467f927f028171af2 Mon Sep 17 00:00:00 2001 From: kaichao Date: Wed, 21 Jan 2026 17:24:20 +0800 Subject: [PATCH] Double ratchet FFI usage in Nim (#14) * feat: ffi * feat: ffi interface and header generation * feat: nim ffi example * chore: doc * fix: encrypt state clean * chore: zeroize when drop --- .gitignore | 3 + Cargo.lock | 295 +++++++++++++++++- double-ratchets/Cargo.toml | 12 + double-ratchets/README.md | 9 + .../ffi-nim-example/ffi_nim_example.nimble | 13 + .../ffi-nim-example/src/ffi_nim_example.nim | 144 +++++++++ double-ratchets/src/bin/generate-headers.rs | 5 + double-ratchets/src/ffi/doubleratchet.rs | 81 +++++ double-ratchets/src/ffi/key.rs | 22 ++ double-ratchets/src/ffi/mod.rs | 10 + double-ratchets/src/ffi/utils.rs | 13 + double-ratchets/src/keypair.rs | 3 +- double-ratchets/src/lib.rs | 1 + double-ratchets/src/state.rs | 2 +- double_ratchet.h | 114 +++++++ 15 files changed, 720 insertions(+), 7 deletions(-) create mode 100644 double-ratchets/ffi-nim-example/ffi_nim_example.nimble create mode 100644 double-ratchets/ffi-nim-example/src/ffi_nim_example.nim create mode 100644 double-ratchets/src/bin/generate-headers.rs create mode 100644 double-ratchets/src/ffi/doubleratchet.rs create mode 100644 double-ratchets/src/ffi/key.rs create mode 100644 double-ratchets/src/ffi/mod.rs create mode 100644 double-ratchets/src/ffi/utils.rs create mode 100644 double_ratchet.h diff --git a/.gitignore b/.gitignore index eb5b53f..e00b4cb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ target #.idea/ */.DS_Store + +# Compiled binary +**/ffi_nim_example diff --git a/Cargo.lock b/Cargo.lock index 6168294..1297bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,7 +114,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -137,10 +137,53 @@ dependencies = [ "hkdf", "rand", "rand_core", + "safer-ffi", "thiserror", "x25519-dalek", + "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "extension-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +dependencies = [ + "ext-trait", +] + +[[package]] +name = "extern-c" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -168,6 +211,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hkdf" version = "0.12.4" @@ -186,6 +235,16 @@ dependencies = [ "digest", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.4" @@ -195,6 +254,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + [[package]] name = "libc" version = "0.2.178" @@ -208,12 +276,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "macro_rules_attribute" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "poly1305" version = "0.8.0" @@ -234,6 +330,25 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -291,6 +406,50 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safer-ffi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" +dependencies = [ + "extern-c", + "inventory", + "libc", + "macro_rules_attribute", + "paste", + "safer_ffi-proc_macros", + "scopeguard", + "stabby", + "uninit", + "unwind_safe", + "with_builtin_macros", +] + +[[package]] +name = "safer_ffi-proc_macros" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" +dependencies = [ + "macro_rules_attribute", + "prettyplease", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.27" @@ -324,7 +483,48 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "stabby" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8" +dependencies = [ + "rustversion", + "stabby-abi", +] + +[[package]] +name = "stabby-abi" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a" +dependencies = [ + "rustc_version", + "rustversion", + "sha2-const-stable", + "stabby-macros", +] + +[[package]] +name = "stabby-macros" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "rand", + "syn 1.0.109", ] [[package]] @@ -333,6 +533,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.111" @@ -361,7 +572,37 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", ] [[package]] @@ -376,6 +617,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "uninit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" +dependencies = [ + "extension-traits", +] + [[package]] name = "universal-hash" version = "0.5.1" @@ -386,6 +636,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unwind_safe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" + [[package]] name = "version_check" version = "0.9.5" @@ -398,6 +654,35 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "with_builtin_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -427,7 +712,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -447,5 +732,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] diff --git a/double-ratchets/Cargo.toml b/double-ratchets/Cargo.toml index 8832e51..3685df2 100644 --- a/double-ratchets/Cargo.toml +++ b/double-ratchets/Cargo.toml @@ -3,6 +3,13 @@ name = "double-ratchets" version = "0.0.1" edition = "2024" +[lib] +crate-type = ["rlib", "cdylib"] + +[[bin]] +name = "generate-headers" +required-features = ["headers"] + [dependencies] x25519-dalek = { version="2.0.1", features=["static_secrets"] } chacha20poly1305 = "0.10.1" @@ -11,3 +18,8 @@ rand = "0.8.5" hkdf = "0.12.4" thiserror = "2" blake2 = "0.10.6" +safer-ffi = "0.1.13" +zeroize = "1.8.2" + +[features] +headers = ["safer-ffi/headers"] diff --git a/double-ratchets/README.md b/double-ratchets/README.md index e5e4dd9..bad8a7b 100644 --- a/double-ratchets/README.md +++ b/double-ratchets/README.md @@ -20,3 +20,12 @@ Run examples, ``` cargo run --example double_ratchet_basic ``` + +Run Nim FFI example, + +```bash +# In the root folder (libchat) +cargo build --release +# In ffi-nim-example folder +nimble run +``` diff --git a/double-ratchets/ffi-nim-example/ffi_nim_example.nimble b/double-ratchets/ffi-nim-example/ffi_nim_example.nimble new file mode 100644 index 0000000..a89d2a5 --- /dev/null +++ b/double-ratchets/ffi-nim-example/ffi_nim_example.nimble @@ -0,0 +1,13 @@ +# Package + +version = "0.1.0" +author = "kaichaosun" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" +bin = @["ffi_nim_example"] + + +# Dependencies + +requires "nim >= 2.2.4" diff --git a/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim b/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim new file mode 100644 index 0000000..2437c43 --- /dev/null +++ b/double-ratchets/ffi-nim-example/src/ffi_nim_example.nim @@ -0,0 +1,144 @@ +when defined(macosx): + {.passL: "-Wl,-rpath,@executable_path/../../target/release".} +when defined(linux): + {.passL: "-Wl,-rpath,'$ORIGIN/../../target/release'".} + +# Portable dynlib name with override capability (-d:RLN_LIB:"...") +when defined(macosx): + const DR_LIB* {.strdefine.} = "libdouble_ratchets.dylib" +elif defined(linux): + const DR_LIB* {.strdefine.} = "libdouble_ratchets.so" +elif defined(windows): + const DR_LIB* {.strdefine.} = "double_ratchets.dll" +else: + const DR_LIB* {.strdefine.} = "double_ratchets" + +type FFIRatchetState* = object + +type FFIEncryptResult* = object + +type FFIInstallationKeyPair* = object + +type CSize* = csize_t + +type Vec_uint8* = object + dataPtr*: ptr uint8 + len*: CSize + cap*: CSize + +type Array_uint8_32* {.bycopy.} = object + idx*: array[32, uint8] + +type CResult_Vec_uint8_Vec_uint8* {.bycopy.} = object ## + ok*: Vec_uint8 + err*: Vec_uint8 + +proc double_ratchet_init_receiver*( + shared_secret: Array_uint8_32, keypair: ptr FFIInstallationKeyPair +): ptr FFIRatchetState {.importc, dynlib: DR_LIB.} + +proc double_ratchet_init_sender*( + shared_secret: Array_uint8_32, remote_pub: Array_uint8_32 +): ptr FFIRatchetState {.importc, dynlib: DR_LIB.} + +proc double_ratchet_encrypt_message*( + state: ptr FFIRatchetState, plaintext: ptr Vec_uint8 +): ptr FFIEncryptResult {.importc, dynlib: DR_LIB.} + +proc double_ratchet_descrypt_message*( + state: ptr FFIRatchetState, encrypted: ptr FFIEncryptResult +): CResult_Vec_uint8_Vec_uint8 {.importc, dynlib: DR_LIB.} + +proc ratchet_state_destroy*(state: ptr FFIRatchetState) {.importc, dynlib: DR_LIB.} + +proc encrypt_result_destroy*(result: ptr FFIEncryptResult) {.importc, dynlib: DR_LIB.} + +proc installation_key_pair_generate*(): ptr FFIInstallationKeyPair {. + importc, dynlib: DR_LIB +.} + +proc installation_key_pair_public*( + keypair: ptr FFIInstallationKeyPair +): Array_uint8_32 {.importc, dynlib: DR_LIB.} + +proc installation_key_pair_destroy*( + keypair: ptr FFIInstallationKeyPair +) {.importc, dynlib: DR_LIB.} + +proc ffi_c_string_free*(s: Vec_uint8) {.importc, cdecl, dynlib: DR_LIB.} + +proc asString*(v: Vec_uint8): string = + if v.dataPtr.isNil or v.len == 0: + return "" + result = newString(v.len.int) + copyMem(addr result[0], v.dataPtr, v.len.int) + +when isMainModule: + echo("start run") + + # === Shared secret (like X3DH) === + var sharedSecret: Array_uint8_32 + for i in 0 .. 31: + sharedSecret.idx[i] = 42'u8 + + # === Bob generates DH keypair === + let bobKey = installation_key_pair_generate() + let bobPub = installation_key_pair_public(bobKey) + echo("bob public key:", bobPub) + + # === Alice initializes as sender === + let alice = double_ratchet_init_sender(sharedSecret, bobPub) + # # === Bob initializes as receiver === + let bob = double_ratchet_init_receiver(sharedSecret, bobKey) + + # # === Alice sends message to Bob === + var msg1: array[3, uint8] = [11'u8, 12, 13] + var msg1Vec = Vec_uint8( + dataPtr: cast[ptr uint8](addr msg1[0]), len: CSize(msg1.len), cap: CSize(msg1.len) + ) + + let enc1 = double_ratchet_encrypt_message(alice, addr msg1Vec) + let dec1 = double_ratchet_descrypt_message(bob, enc1) + + encrypt_result_destroy(enc1) + + if dec1.err.dataPtr != nil: + echo "Bob failed to decrypt: ", asString(dec1.err) + ffi_c_string_free(dec1.err) + ratchet_state_destroy(alice) + ratchet_state_destroy(bob) + installation_key_pair_destroy(bobKey) + quit 1 + let res1 = dec1.ok + var plaintext1: array[3, uint8] + copyMem(addr plaintext1[0], res1.dataPtr, res1.len.int) + echo "Bob received: ", plaintext1 + + # # === Bob replies (triggers DH ratchet) === + var msg2: array[3, uint8] = [1'u8, 2, 3] + var msg2Vec = Vec_uint8( + dataPtr: cast[ptr uint8](addr msg2[0]), len: CSize(msg1.len), cap: CSize(msg1.len) + ) + let enc2 = double_ratchet_encrypt_message(bob, addr msg2Vec) + let dec2 = double_ratchet_descrypt_message(alice, enc2) + + encrypt_result_destroy(enc2) + + if dec2.err.dataPtr != nil: + echo "Alice failed to decrypt: ", asString(dec2.err) + ffi_c_string_free(dec2.err) + ratchet_state_destroy(alice) + ratchet_state_destroy(bob) + installation_key_pair_destroy(bobKey) + quit 1 + let res2 = dec2.ok + var plaintext2: array[3, uint8] + copyMem(addr plaintext2[0], res2.dataPtr, res2.len.int) + echo "Alice received: ", plaintext2 + + # # === Cleanup === + ratchet_state_destroy(alice) + ratchet_state_destroy(bob) + installation_key_pair_destroy(bobKey) + + echo("==end==\n") diff --git a/double-ratchets/src/bin/generate-headers.rs b/double-ratchets/src/bin/generate-headers.rs new file mode 100644 index 0000000..4c853ea --- /dev/null +++ b/double-ratchets/src/bin/generate-headers.rs @@ -0,0 +1,5 @@ +use double_ratchets::ffi; + +fn main() -> std::io::Result<()> { + ffi::generate_headers() +} diff --git a/double-ratchets/src/ffi/doubleratchet.rs b/double-ratchets/src/ffi/doubleratchet.rs new file mode 100644 index 0000000..e09cebd --- /dev/null +++ b/double-ratchets/src/ffi/doubleratchet.rs @@ -0,0 +1,81 @@ +use safer_ffi::prelude::*; +use x25519_dalek::PublicKey; + +use crate::{ + Header, RatchetState, + ffi::{key::FFIInstallationKeyPair, utils::CResult}, +}; + +#[derive_ReprC] +#[repr(opaque)] +pub struct FFIRatchetState(pub(crate) RatchetState); + +#[derive_ReprC] +#[repr(opaque)] +pub struct FFIEncryptResult { + pub ciphertext: Vec, + pub header: Header, +} + +#[ffi_export] +fn double_ratchet_init_sender( + shared_secret: [u8; 32], + remote_pub: [u8; 32], +) -> repr_c::Box { + let state = RatchetState::init_sender(shared_secret, PublicKey::from(remote_pub)); + Box::new(FFIRatchetState(state)).into() +} + +#[ffi_export] +fn double_ratchet_init_receiver( + shared_secret: [u8; 32], + keypair: &FFIInstallationKeyPair, +) -> repr_c::Box { + let state = RatchetState::init_receiver(shared_secret, keypair.0.clone()); + Box::new(FFIRatchetState(state)).into() +} + +#[ffi_export] +fn double_ratchet_encrypt_message( + state: &mut FFIRatchetState, + plaintext: &repr_c::Vec, +) -> repr_c::Box { + let encrypted = state.0.encrypt_message(plaintext); + let result = FFIEncryptResult { + ciphertext: encrypted.0, + header: encrypted.1, + }; + Box::new(result).into() +} + +//TODO rename decrypt +#[ffi_export] +fn double_ratchet_descrypt_message( + state: &mut FFIRatchetState, + encrypted: &FFIEncryptResult, +) -> CResult, repr_c::String> { + let decrypted = state + .0 + .decrypt_message(&encrypted.ciphertext, encrypted.header.clone()); + + match decrypted { + Ok(plaintext) => CResult { + ok: Some(plaintext.into()), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +fn ratchet_state_destroy(state: repr_c::Box) { + drop(state) +} + +#[ffi_export] +fn encrypt_result_destroy(result: repr_c::Box) { + drop(result) +} diff --git a/double-ratchets/src/ffi/key.rs b/double-ratchets/src/ffi/key.rs new file mode 100644 index 0000000..ee8a496 --- /dev/null +++ b/double-ratchets/src/ffi/key.rs @@ -0,0 +1,22 @@ +use safer_ffi::prelude::*; + +use crate::InstallationKeyPair; + +#[derive_ReprC] +#[repr(opaque)] +pub struct FFIInstallationKeyPair(pub(crate) InstallationKeyPair); + +#[ffi_export] +fn installation_key_pair_generate() -> repr_c::Box { + Box::new(FFIInstallationKeyPair(InstallationKeyPair::generate())).into() +} + +#[ffi_export] +fn installation_key_pair_public(keypair: &FFIInstallationKeyPair) -> [u8; 32] { + keypair.0.public().clone().to_bytes() +} + +#[ffi_export] +fn installation_key_pair_destroy(keypair: repr_c::Box) { + drop(keypair) +} diff --git a/double-ratchets/src/ffi/mod.rs b/double-ratchets/src/ffi/mod.rs new file mode 100644 index 0000000..f67b19a --- /dev/null +++ b/double-ratchets/src/ffi/mod.rs @@ -0,0 +1,10 @@ +pub mod doubleratchet; +pub mod key; +pub mod utils; + +#[cfg(feature = "headers")] +pub fn generate_headers() -> std::io::Result<()> { + safer_ffi::headers::builder() + .to_file("double_ratchet.h")? + .generate() +} diff --git a/double-ratchets/src/ffi/utils.rs b/double-ratchets/src/ffi/utils.rs new file mode 100644 index 0000000..f50033f --- /dev/null +++ b/double-ratchets/src/ffi/utils.rs @@ -0,0 +1,13 @@ +use safer_ffi::prelude::*; + +#[derive_ReprC] +#[repr(C)] +pub struct CResult { + pub ok: Option, + pub err: Option, +} + +#[ffi_export] +pub fn ffi_c_string_free(s: repr_c::String) { + drop(s); +} diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs index 26463a4..7d9dc07 100644 --- a/double-ratchets/src/keypair.rs +++ b/double-ratchets/src/keypair.rs @@ -1,9 +1,10 @@ use rand_core::OsRng; use x25519_dalek::{PublicKey, StaticSecret}; +use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::types::SharedSecret; -#[derive(Clone)] +#[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct InstallationKeyPair { secret: StaticSecret, public: PublicKey, diff --git a/double-ratchets/src/lib.rs b/double-ratchets/src/lib.rs index 8f51567..3bd8b46 100644 --- a/double-ratchets/src/lib.rs +++ b/double-ratchets/src/lib.rs @@ -1,5 +1,6 @@ pub mod aead; pub mod errors; +pub mod ffi; pub mod hkdf; pub mod keypair; pub mod state; diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index b34ef46..9aec0cd 100644 --- a/double-ratchets/src/state.rs +++ b/double-ratchets/src/state.rs @@ -35,7 +35,7 @@ pub struct RatchetState { } /// Public header attached to every encrypted message (unencrypted but authenticated). -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Header { pub dh_pub: PublicKey, pub msg_num: u32, diff --git a/double_ratchet.h b/double_ratchet.h new file mode 100644 index 0000000..fd343d1 --- /dev/null +++ b/double_ratchet.h @@ -0,0 +1,114 @@ +/*! \file */ +/******************************************* + * * + * File auto-generated by `::safer_ffi`. * + * * + * Do not manually edit this file. * + * * + *******************************************/ + +#ifndef __RUST_DOUBLE_RATCHETS__ +#define __RUST_DOUBLE_RATCHETS__ +#ifdef __cplusplus +extern "C" { +#endif + +/** */ +typedef struct FFIRatchetState FFIRatchetState_t; + +/** */ +typedef struct FFIEncryptResult FFIEncryptResult_t; + + +#include +#include + +/** \brief + * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout + */ +typedef struct Vec_uint8 { + /** */ + uint8_t * ptr; + + /** */ + size_t len; + + /** */ + size_t cap; +} Vec_uint8_t; + +/** */ +typedef struct CResult_Vec_uint8_Vec_uint8 { + /** */ + Vec_uint8_t ok; + + /** */ + Vec_uint8_t err; +} CResult_Vec_uint8_Vec_uint8_t; + +/** */ +CResult_Vec_uint8_Vec_uint8_t +double_ratchet_descrypt_message ( + FFIRatchetState_t * state, + FFIEncryptResult_t const * encrypted); + +/** */ +FFIEncryptResult_t * +double_ratchet_encrypt_message ( + FFIRatchetState_t * state, + Vec_uint8_t const * plaintext); + +typedef struct { + uint8_t idx[32]; +} uint8_32_array_t; + +/** */ +typedef struct FFIInstallationKeyPair FFIInstallationKeyPair_t; + +/** */ +FFIRatchetState_t * +double_ratchet_init_receiver ( + uint8_32_array_t shared_secret, + FFIInstallationKeyPair_t const * keypair); + +/** */ +FFIRatchetState_t * +double_ratchet_init_sender ( + uint8_32_array_t shared_secret, + uint8_32_array_t remote_pub); + +/** */ +void +encrypt_result_destroy ( + FFIEncryptResult_t * result); + +/** */ +void +ffi_c_string_free ( + Vec_uint8_t s); + +/** */ +void +installation_key_pair_destroy ( + FFIInstallationKeyPair_t * keypair); + +/** */ +FFIInstallationKeyPair_t * +installation_key_pair_generate (void); + +/** */ +uint8_32_array_t +installation_key_pair_public ( + FFIInstallationKeyPair_t const * keypair); + +/** */ +void +ratchet_state_destroy ( + FFIRatchetState_t * state); + + +#ifdef __cplusplus +} /* extern \"C\" */ +#endif + +#endif /* __RUST_DOUBLE_RATCHETS__ */