Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
20 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Fri, Oct 30, 8:58 AM (16 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799935
Default Alt Text
(20 KB)

Event Timeline