Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85826678
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/cpp/src/account.rs b/cpp/src/account.rs
index f33fdfa..7dd1e24 100644
--- a/cpp/src/account.rs
+++ b/cpp/src/account.rs
@@ -1,138 +1,155 @@
use super::{
ffi::{InboundCreationResult, OlmMessageParts, OneTimeKey},
Curve25519PublicKey, Ed25519PublicKey, Ed25519Signature, Session,
};
pub struct OlmMessage(pub(crate) vodozemac::olm::OlmMessage);
impl OlmMessage {
pub fn to_parts(&self) -> OlmMessageParts {
let (message_type, ciphertext) = self.0.clone().to_parts();
OlmMessageParts {
ciphertext,
message_type,
}
}
}
pub fn olm_message_from_parts(parts: &OlmMessageParts) -> Result<Box<OlmMessage>, anyhow::Error> {
Ok(OlmMessage(vodozemac::olm::OlmMessage::from_parts(
parts.message_type,
&parts.ciphertext,
)?)
.into())
}
+pub struct OneTimeKeyGenerationResult(vodozemac::olm::OneTimeKeyGenerationResult);
+
+impl OneTimeKeyGenerationResult {
+ pub fn created(self: &Self) -> Vec<Curve25519PublicKey> {
+ self.0.created.iter().map(|key| Curve25519PublicKey(*key)).collect()
+ }
+ pub fn removed(self: &Self) -> Vec<Curve25519PublicKey> {
+ self.0.removed.iter().map(|key| Curve25519PublicKey(*key)).collect()
+ }
+}
+
pub struct Account(vodozemac::olm::Account);
pub fn new_account() -> Box<Account> {
Account(vodozemac::olm::Account::new()).into()
}
pub fn account_from_pickle(
pickle: &str,
pickle_key: &[u8; 32],
) -> Result<Box<Account>, anyhow::Error> {
let pickle = vodozemac::olm::AccountPickle::from_encrypted(pickle, pickle_key)?;
Ok(Account(vodozemac::olm::Account::from_pickle(pickle)).into())
}
pub fn account_from_libolm_pickle(
pickle: &str,
pickle_key: &[u8],
) -> Result<Box<Account>, anyhow::Error> {
let res = vodozemac::olm::Account::from_libolm_pickle(pickle, pickle_key)?;
Ok(Account(res).into())
}
impl From<vodozemac::olm::InboundCreationResult> for InboundCreationResult {
fn from(v: vodozemac::olm::InboundCreationResult) -> Self {
Self {
session: Session(v.session).into(),
plaintext: v.plaintext,
}
}
}
impl Account {
pub fn ed25519_key(&self) -> Box<Ed25519PublicKey> {
Ed25519PublicKey(self.0.ed25519_key()).into()
}
pub fn curve25519_key(&self) -> Box<Curve25519PublicKey> {
Curve25519PublicKey(self.0.curve25519_key()).into()
}
pub fn sign(&self, message: &str) -> Box<Ed25519Signature> {
Ed25519Signature(self.0.sign(message)).into()
}
- pub fn generate_one_time_keys(&mut self, count: usize) {
- self.0.generate_one_time_keys(count);
+ pub fn generate_one_time_keys(&mut self, count: usize) -> Box<OneTimeKeyGenerationResult> {
+ OneTimeKeyGenerationResult(self.0.generate_one_time_keys(count)).into()
}
pub fn one_time_keys(&self) -> Vec<OneTimeKey> {
self.0
.one_time_keys()
.into_iter()
.map(|(key_id, key)| OneTimeKey {
key_id: key_id.to_base64(),
key: Curve25519PublicKey(key).into(),
})
.collect()
}
- pub fn generate_fallback_key(&mut self) {
- self.0.generate_fallback_key();
+ // cxx bridge does not support Option yet, that's why we are returning a vector
+ // of length either 0 or 1.
+ pub fn generate_fallback_key(&mut self) -> Vec<Curve25519PublicKey> {
+ let mut result = Vec::new();
+ if let Some(key) = self.0.generate_fallback_key() {
+ result.push(Curve25519PublicKey(key));
+ }
+ result
}
pub fn fallback_key(&self) -> Vec<OneTimeKey> {
self.0
.fallback_key()
.into_iter()
.map(|(key_id, key)| OneTimeKey {
key_id: key_id.to_base64(),
key: Curve25519PublicKey(key).into(),
})
.collect()
}
pub fn mark_keys_as_published(&mut self) {
self.0.mark_keys_as_published()
}
pub fn max_number_of_one_time_keys(&self) -> usize {
self.0.max_number_of_one_time_keys()
}
pub fn create_outbound_session(
&self,
identity_key: &Curve25519PublicKey,
one_time_key: &Curve25519PublicKey,
) -> Result<Box<Session>, vodozemac::KeyError> {
let session = self
.0
.create_outbound_session(vodozemac::olm::SessionConfig::version_1(), identity_key.0, one_time_key.0);
Ok(Box::new(Session(session)))
}
pub fn create_inbound_session(
&mut self,
identity_key: &Curve25519PublicKey,
message: &OlmMessage,
) -> Result<InboundCreationResult, anyhow::Error> {
if let vodozemac::olm::OlmMessage::PreKey(m) = &message.0 {
let result = self.0.create_inbound_session(identity_key.0, m)?;
Ok(result.into())
} else {
anyhow::bail!("Invalid message type, a pre-key message is required")
}
}
pub fn pickle(&self, pickle_key: &[u8; 32]) -> String {
self.0.pickle().encrypt(pickle_key)
}
}
diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs
index 4d4dd7a..370d40b 100644
--- a/cpp/src/lib.rs
+++ b/cpp/src/lib.rs
@@ -1,189 +1,193 @@
mod account;
mod group_sessions;
mod sas;
mod session;
mod types;
-use account::{account_from_pickle, account_from_libolm_pickle, new_account, olm_message_from_parts, Account, OlmMessage};
+use account::{account_from_pickle, account_from_libolm_pickle, new_account, olm_message_from_parts, Account, OlmMessage, OneTimeKeyGenerationResult};
use group_sessions::{
exported_session_key_from_base64, group_session_from_pickle, group_session_from_libolm_pickle, import_inbound_group_session,
inbound_group_session_from_pickle, inbound_group_session_from_libolm_pickle, megolm_message_from_base64, new_group_session,
new_inbound_group_session, session_key_from_base64, ExportedSessionKey, GroupSession,
InboundGroupSession, MegolmMessage, SessionKey,
};
use sas::{mac_from_base64, new_sas, EstablishedSas, Mac, Sas, SasBytes};
use session::{session_from_pickle, session_from_libolm_pickle, Session};
use types::{
curve_key_from_base64, ed25519_key_from_base64, ed25519_signature_from_base64, Curve25519PublicKey, Ed25519PublicKey,
Ed25519Signature,
};
#[cxx::bridge]
mod ffi {
#[namespace = "vodozemac::olm"]
struct OlmMessageParts {
message_type: usize,
ciphertext: String,
}
#[namespace = "vodozemac::olm"]
pub struct InboundCreationResult {
pub session: Box<Session>,
pub plaintext: Vec<u8>,
}
#[namespace = "vodozemac::olm"]
struct OneTimeKey {
key_id: String,
key: Box<Curve25519PublicKey>,
}
#[namespace = "vodozemac::olm"]
#[derive(PartialEq, Eq)]
struct SessionKeys {
identity_key: Box<Curve25519PublicKey>,
base_key: Box<Curve25519PublicKey>,
one_time_key: Box<Curve25519PublicKey>,
}
#[namespace = "vodozemac::types"]
extern "Rust" {
type Curve25519PublicKey;
fn curve_key_from_base64(key: &str) -> Result<Box<Curve25519PublicKey>>;
fn to_base64(self: &Curve25519PublicKey) -> String;
type Ed25519PublicKey;
fn ed25519_key_from_base64(key: &str) -> Result<Box<Ed25519PublicKey>>;
fn to_base64(self: &Ed25519PublicKey) -> String;
fn verify(self: &Ed25519PublicKey, message: &str, signature: &Ed25519Signature) -> Result<()>;
type Ed25519Signature;
fn ed25519_signature_from_base64(key: &str) -> Result<Box<Ed25519Signature>>;
fn to_base64(self: &Ed25519Signature) -> String;
}
#[namespace = "vodozemac::olm"]
extern "Rust" {
type Account;
fn new_account() -> Box<Account>;
fn ed25519_key(self: &Account) -> Box<Ed25519PublicKey>;
fn curve25519_key(self: &Account) -> Box<Curve25519PublicKey>;
fn sign(self: &Account, message: &str) -> Box<Ed25519Signature>;
- fn generate_one_time_keys(self: &mut Account, count: usize);
+ fn generate_one_time_keys(self: &mut Account, count: usize) -> Box<OneTimeKeyGenerationResult>;
fn one_time_keys(self: &Account) -> Vec<OneTimeKey>;
- fn generate_fallback_key(self: &mut Account);
+ fn generate_fallback_key(self: &mut Account) -> Vec<Curve25519PublicKey>;
fn fallback_key(self: &Account) -> Vec<OneTimeKey>;
fn mark_keys_as_published(self: &mut Account);
fn max_number_of_one_time_keys(self: &Account) -> usize;
fn account_from_pickle(pickle: &str, pickle_key: &[u8; 32]) -> Result<Box<Account>>;
fn account_from_libolm_pickle(pickle: &str, pickle_key: &[u8]) -> Result<Box<Account>>;
fn pickle(self: &Account, pickle_key: &[u8; 32]) -> String;
fn create_outbound_session(
self: &Account,
identity_key: &Curve25519PublicKey,
one_time_key: &Curve25519PublicKey,
) -> Result<Box<Session>>;
fn create_inbound_session(
self: &mut Account,
identity_key: &Curve25519PublicKey,
message: &OlmMessage,
) -> Result<InboundCreationResult>;
+ type OneTimeKeyGenerationResult;
+ fn created(self: &OneTimeKeyGenerationResult) -> Vec<Curve25519PublicKey>;
+ fn removed(self: &OneTimeKeyGenerationResult) -> Vec<Curve25519PublicKey>;
+
type Session;
fn session_id(self: &Session) -> String;
fn session_keys(self: &Session) -> SessionKeys;
fn session_matches(self: &Session, message: &OlmMessage) -> bool;
fn encrypt(self: &mut Session, plaintext: &str) -> Box<OlmMessage>;
fn decrypt(self: &mut Session, message: &OlmMessage) -> Result<Vec<u8>>;
fn session_from_pickle(pickle: &str, pickle_key: &[u8; 32]) -> Result<Box<Session>>;
fn session_from_libolm_pickle(pickle: &str, pickle_key: &[u8]) -> Result<Box<Session>>;
fn pickle(self: &Session, pickle_key: &[u8; 32]) -> String;
type OlmMessage;
fn to_parts(self: &OlmMessage) -> OlmMessageParts;
fn olm_message_from_parts(parts: &OlmMessageParts) -> Result<Box<OlmMessage>>;
}
#[namespace = "vodozemac::megolm"]
struct DecryptedMessage {
plaintext: Vec<u8>,
message_index: u32,
}
#[namespace = "vodozemac::megolm"]
extern "Rust" {
type MegolmMessage;
fn megolm_message_from_base64(message: &str) -> Result<Box<MegolmMessage>>;
fn to_base64(self: &MegolmMessage) -> String;
type SessionKey;
fn session_key_from_base64(key: &str) -> Result<Box<SessionKey>>;
fn to_base64(self: &SessionKey) -> String;
type ExportedSessionKey;
fn exported_session_key_from_base64(key: &str) -> Result<Box<ExportedSessionKey>>;
fn to_base64(self: &ExportedSessionKey) -> String;
type GroupSession;
fn new_group_session() -> Box<GroupSession>;
fn encrypt(self: &mut GroupSession, plaintext: &str) -> Box<MegolmMessage>;
fn session_id(self: &GroupSession) -> String;
fn session_key(self: &GroupSession) -> Box<SessionKey>;
fn message_index(self: &GroupSession) -> u32;
fn pickle(self: &GroupSession, pickle_key: &[u8; 32]) -> String;
fn group_session_from_pickle(
pickle: &str,
pickle_key: &[u8; 32],
) -> Result<Box<GroupSession>>;
fn group_session_from_libolm_pickle(
pickle: &str,
pickle_key: &[u8],
) -> Result<Box<GroupSession>>;
type InboundGroupSession;
fn new_inbound_group_session(session_key: &SessionKey) -> Box<InboundGroupSession>;
fn import_inbound_group_session(
session_key: &ExportedSessionKey,
) -> Box<InboundGroupSession>;
fn decrypt(
self: &mut InboundGroupSession,
message: &MegolmMessage,
) -> Result<DecryptedMessage>;
fn session_id(self: &InboundGroupSession) -> String;
fn first_known_index(self: &InboundGroupSession) -> u32;
fn export_at(
self: &mut InboundGroupSession,
message_index: u32,
) -> Result<Box<ExportedSessionKey>>;
fn pickle(self: &InboundGroupSession, pickle_key: &[u8; 32]) -> String;
fn inbound_group_session_from_pickle(
pickle: &str,
pickle_key: &[u8; 32],
) -> Result<Box<InboundGroupSession>>;
fn inbound_group_session_from_libolm_pickle(
pickle: &str,
pickle_key: &[u8],
) -> Result<Box<InboundGroupSession>>;
}
#[namespace = "vodozemac::sas"]
extern "Rust" {
type Mac;
fn mac_from_base64(mac: &str) -> Result<Box<Mac>>;
fn to_base64(self: &Mac) -> String;
type Sas;
fn new_sas() -> Box<Sas>;
fn public_key(self: &Sas) -> Box<Curve25519PublicKey>;
fn diffie_hellman(
self: &mut Sas,
other_public_key: &Curve25519PublicKey,
) -> Result<Box<EstablishedSas>>;
type EstablishedSas;
fn bytes(self: &EstablishedSas, info: &str) -> Box<SasBytes>;
fn calculate_mac(self: &EstablishedSas, input: &str, info: &str) -> Box<Mac>;
fn verify_mac(self: &EstablishedSas, input: &str, info: &str, mac: &Mac) -> Result<()>;
type SasBytes;
fn emoji_indices(self: &SasBytes) -> [u8; 7];
fn decimals(self: &SasBytes) -> [u16; 3];
}
}
diff --git a/cpp/tests/account.cpp b/cpp/tests/account.cpp
index 89b94c3..08fff12 100644
--- a/cpp/tests/account.cpp
+++ b/cpp/tests/account.cpp
@@ -1,124 +1,174 @@
#include "../../target/cxxbridge/vodozemac/src/lib.rs.h"
#include <string>
#include <unordered_set>
#include <olm/olm.h>
#include <boost/json.hpp>
#include "gtest/gtest.h"
#include "util.hpp"
using namespace rust;
using namespace vodozemac;
TEST(AccountTest, AccountCreation) {
auto alice = olm::new_account();
auto key = alice->ed25519_key();
auto encoded_key = key->to_base64();
EXPECT_NE(encoded_key.length(), 0);
}
TEST(AccountTest, OneTimeKeyGeneration) {
auto alice = olm::new_account();
EXPECT_EQ(alice->one_time_keys().size(), 0);
- alice->generate_one_time_keys(10);
+ auto result = alice->generate_one_time_keys(10);
+ EXPECT_EQ(result->created().size(), 10);
EXPECT_EQ(alice->one_time_keys().size(), 10);
alice->mark_keys_as_published();
EXPECT_EQ(alice->one_time_keys().size(), 0);
}
+TEST(AccountTest, OneTimeKeysRemovedWhenTooMany) {
+ auto alice = olm::new_account();
+
+ // Vodozemac will remove old one-time keys when the total number of generated one-time keys exceeds an internal constant.
+ // That internal constant is not a part of the public API, that's why we keep generating new keys until the first removal.
+ static constexpr std::size_t upper_bound = 1000000;
+ // The long variable names are needed for better error messages in case the test fails.
+ std::size_t total_number_of_generated_one_time_keys = 0;
+ std::size_t total_number_of_one_time_keys_removed = 0;
+ while (total_number_of_one_time_keys_removed == 0 && total_number_of_generated_one_time_keys < upper_bound) {
+ auto result = alice->generate_one_time_keys(alice->max_number_of_one_time_keys());
+ total_number_of_generated_one_time_keys += result->created().size();
+ total_number_of_one_time_keys_removed += result->removed().size();
+ }
+
+ EXPECT_GT(total_number_of_one_time_keys_removed, 0) << " Generated " << upper_bound << " one-time keys, but no keys were removed.";
+ EXPECT_EQ(alice->one_time_keys().size() + total_number_of_one_time_keys_removed, total_number_of_generated_one_time_keys);
+}
+
TEST(AccountTest, FallbackKeyGeneration) {
auto alice = olm::new_account();
EXPECT_EQ(alice->fallback_key().size(), 0);
- alice->generate_fallback_key();
+ auto previous_fallback_key = alice->generate_fallback_key();
EXPECT_EQ(alice->fallback_key().size(), 1);
+ EXPECT_EQ(previous_fallback_key.size(), 0);
alice->mark_keys_as_published();
- EXPECT_EQ(alice->one_time_keys().size(), 0);
+ EXPECT_EQ(alice->fallback_key().size(), 0);
+}
+
+TEST(AccountTest, FallbackKeyDeprecation) {
+ auto alice = olm::new_account();
+ EXPECT_EQ(alice->fallback_key().size(), 0);
+
+ auto previous_fallback_key = alice->generate_fallback_key();
+ EXPECT_EQ(alice->fallback_key().size(), 1);
+ EXPECT_EQ(previous_fallback_key.size(), 0) <<
+ "When a fallback key is generated for the first time, there should not be any old fallback keys to remove.";
+
+ alice->mark_keys_as_published();
+ EXPECT_EQ(alice->fallback_key().size(), 0);
+
+ // olm::Account stores up to two fallback keys.
+ // The old fallback key should not be removed, yet.
+ previous_fallback_key = alice->generate_fallback_key();
+ EXPECT_EQ(alice->fallback_key().size(), 1);
+ EXPECT_EQ(previous_fallback_key.size(), 0) <<
+ "After a fallback key has been generated twice, the old fallback key should not be removed yet.";
+
+ alice->mark_keys_as_published();
+ EXPECT_EQ(alice->fallback_key().size(), 0);
+
+ // Now we expect the old fallback key to be removed.
+ previous_fallback_key = alice->generate_fallback_key();
+ EXPECT_EQ(alice->fallback_key().size(), 1);
+ EXPECT_EQ(previous_fallback_key.size(), 1) <<
+ "After a fallback key has been generated three times, the old fallback key should be removed.";
}
TEST(AccountTest, MaxKeysTest) {
auto alice = olm::new_account();
auto max_keys = alice->max_number_of_one_time_keys();
EXPECT_GT(max_keys, 0);
EXPECT_LT(max_keys, 1000);
}
TEST(AccountTest, PickleTest) {
auto alice = olm::new_account();
auto pickle = alice->pickle(PICKLE_KEY);
auto unpickled = olm::account_from_pickle(pickle, PICKLE_KEY);
EXPECT_EQ(alice->curve25519_key()->to_base64(),
unpickled->curve25519_key()->to_base64());
}
TEST(AccountTest, PickleFromLibolmTest) {
auto [data, alice] = new_olm_account();
auto random = gen_random(olm_account_generate_one_time_keys_random_length(alice, 10));
check_olm_error(olm_account_generate_one_time_keys(alice, 10, random.data(), random.size()));
auto pickle = std::string(olm_pickle_account_length(alice), '\0');
check_olm_error(olm_pickle_account(alice, OLM_PICKLE_KEY.data(), OLM_PICKLE_KEY.size(), pickle.data(), pickle.size()));
auto unpickled = olm::account_from_libolm_pickle(pickle, Slice<const unsigned char>(OLM_PICKLE_KEY.data(), OLM_PICKLE_KEY.size()));
{
auto identity_keys = std::string(olm_account_identity_keys_length(alice), '\0');
check_olm_error(olm_account_identity_keys(alice, identity_keys.data(), identity_keys.size()));
auto parsed_keys = boost::json::parse(identity_keys).as_object();
auto curve25519_key = String(as_std_string(parsed_keys.at("curve25519").as_string()));
auto ed25519_key = String(as_std_string(parsed_keys.at("ed25519").as_string()));
EXPECT_EQ(curve25519_key,
unpickled->curve25519_key()->to_base64());
EXPECT_EQ(ed25519_key,
unpickled->ed25519_key()->to_base64());
}
{
auto one_time_keys = std::string(olm_account_one_time_keys_length(alice), '\0');
check_olm_error(olm_account_one_time_keys(alice, one_time_keys.data(), one_time_keys.size()));
auto parsed_keys = boost::json::parse(one_time_keys).as_object().at("curve25519").as_object();
// The key id will change from libolm to vodozemac, but the key themselves won't.
auto one_time_keys_set = std::unordered_set<std::string>();
for (const auto &pair : parsed_keys) {
one_time_keys_set.insert(as_std_string(pair.value().as_string()));
}
auto unpickled_one_time_keys = unpickled->one_time_keys();
auto unpickled_one_time_keys_set = std::unordered_set<std::string>();
for (const auto &k : unpickled_one_time_keys) {
unpickled_one_time_keys_set.insert(static_cast<std::string>(k.key->to_base64()));
}
EXPECT_EQ(one_time_keys_set, unpickled_one_time_keys_set);
}
}
TEST(AccountTest, SignAndVerify) {
auto alice = olm::new_account();
auto key = alice->ed25519_key();
std::string str = "{}";
auto signature = alice->sign(Str(str));
EXPECT_NO_THROW(key->verify(Str(str), *signature));
{
auto cloned_signature = types::ed25519_signature_from_base64(signature->to_base64());
EXPECT_NO_THROW(key->verify(Str(str), *cloned_signature));
}
{
auto cloned_key = types::ed25519_key_from_base64(key->to_base64());
EXPECT_NO_THROW(cloned_key->verify(Str(str), *signature));
}
std::string str2 = "{\"foo\": \"bar\"}";
auto signature2 = alice->sign(Str(str2));
EXPECT_ANY_THROW(key->verify(Str(str2), *signature));
EXPECT_ANY_THROW(key->verify(Str(str), *signature2));
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Oct 30, 8:58 AM (17 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799935
Default Alt Text
(20 KB)
Attached To
Mode
rVB vodozemac-bindings
Attached
Detach File
Event Timeline