olm_rs/account.rs
1// Copyright 2020 Johannes Hayeß
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module wraps around all functions following the pattern `olm_account_*`.
16
17use crate::errors::{self, OlmAccountError, OlmSessionError};
18use crate::getrandom;
19use crate::session::{OlmSession, PreKeyMessage};
20use crate::{ByteBuf, PicklingMode};
21
22#[cfg(feature = "deserialization")]
23use std::collections::{hash_map::Iter, hash_map::Keys, hash_map::Values, HashMap};
24use std::ffi::CStr;
25
26#[cfg(feature = "deserialization")]
27use serde::Deserialize;
28#[cfg(feature = "deserialization")]
29use serde_json::Value;
30use zeroize::Zeroizing;
31
32/// An olm account manages all cryptographic keys used on a device.
33/// ```
34/// use olm_rs::account::OlmAccount;
35///
36/// let olm_account = OlmAccount::new();
37/// println!("{:?}", olm_account.identity_keys());
38/// ```
39pub struct OlmAccount {
40 /// Pointer by which libolm acquires the data saved in an instance of OlmAccount
41 pub(crate) olm_account_ptr: *mut olm_sys::OlmAccount,
42 _olm_account_buf: ByteBuf,
43}
44
45#[cfg(feature = "deserialization")]
46/// Struct representing the parsed result of [`OlmAccount::identity_keys()`].
47#[derive(Deserialize, Debug, PartialEq)]
48pub struct IdentityKeys {
49 #[serde(flatten)]
50 keys: HashMap<String, String>,
51}
52
53#[cfg(feature = "deserialization")]
54impl IdentityKeys {
55 /// Get the public part of the ed25519 key of the account.
56 pub fn ed25519(&self) -> &str {
57 &self.keys["ed25519"]
58 }
59
60 /// Get the public part of the curve25519 key of the account.
61 pub fn curve25519(&self) -> &str {
62 &self.keys["curve25519"]
63 }
64
65 /// Get a reference to the key of the given key type.
66 pub fn get(&self, key_type: &str) -> Option<&str> {
67 let ret = self.keys.get(key_type);
68 ret.map(|x| &**x)
69 }
70
71 /// An iterator visiting all public keys of the account.
72 pub fn values(&self) -> Values<String, String> {
73 self.keys.values()
74 }
75
76 /// An iterator visiting all key types of the account.
77 pub fn keys(&self) -> Keys<String, String> {
78 self.keys.keys()
79 }
80
81 /// An iterator visiting all key-type, key pairs of the account.
82 pub fn iter(&self) -> Iter<String, String> {
83 self.keys.iter()
84 }
85
86 /// Returns true if the account contains a key with the given key type.
87 pub fn contains_key(&self, key_type: &str) -> bool {
88 self.keys.contains_key(key_type)
89 }
90}
91
92#[cfg(feature = "deserialization")]
93/// Struct representing the parsed result of [`OlmAccount::fallback_key()`].
94#[derive(Deserialize, Debug, PartialEq)]
95pub struct FallbackKey {
96 index: String,
97 key: String,
98}
99
100#[cfg(feature = "deserialization")]
101impl FallbackKey {
102 /// Get the public part of this fallback key.
103 pub fn curve25519(&self) -> &str {
104 &self.key
105 }
106
107 /// Get the index associated with this fallback key.
108 pub fn index(&self) -> &str {
109 &self.index
110 }
111}
112
113#[cfg(feature = "deserialization")]
114#[derive(Deserialize, Debug, PartialEq)]
115/// Struct representing the the one-time keys.
116/// The keys can be accessed in a map-like fashion.
117pub struct OneTimeKeys {
118 #[serde(flatten)]
119 keys: HashMap<String, HashMap<String, String>>,
120}
121
122#[cfg(feature = "deserialization")]
123impl OneTimeKeys {
124 /// Get the HashMap containing the curve25519 one-time keys.
125 /// This is the same as using `get("curve25519").unwrap()`
126 pub fn curve25519(&self) -> &HashMap<String, String> {
127 &self.keys["curve25519"]
128 }
129
130 /// Get a reference to the hashmap corresponding to given key type.
131 pub fn get(&self, key_type: &str) -> Option<&HashMap<String, String>> {
132 self.keys.get(key_type)
133 }
134
135 /// An iterator visiting all one-time key hashmaps in an arbitrary order.
136 pub fn values(&self) -> Values<String, HashMap<String, String>> {
137 self.keys.values()
138 }
139
140 /// An iterator visiting all one-time key types in an arbitrary order.
141 pub fn keys(&self) -> Keys<String, HashMap<String, String>> {
142 self.keys.keys()
143 }
144
145 /// An iterator visiting all one-time key types and their respective
146 /// key hashmaps in an arbitrary order.
147 pub fn iter(&self) -> Iter<String, HashMap<String, String>> {
148 self.keys.iter()
149 }
150
151 /// Returns `true` if the struct contains the given key type.
152 /// This does not mean that there are any keys for the given key type.
153 pub fn contains_key(&self, key_type: &str) -> bool {
154 self.keys.contains_key(key_type)
155 }
156}
157
158impl OlmAccount {
159 /// Creates a new instance of OlmAccount. During the instantiation the Ed25519 fingerprint key pair
160 /// and the Curve25519 identity key pair are generated. For more information see
161 /// [here](https://matrix.org/docs/guides/e2e_implementation.html#keys-used-in-end-to-end-encryption).
162 ///
163 /// # C-API equivalent
164 /// `olm_create_account`
165 ///
166 /// # Panics
167 /// * `NOT_ENOUGH_RANDOM` for OlmAccount's creation
168 ///
169 pub fn new() -> Self {
170 // allocate buffer for OlmAccount to be written into
171 let mut olm_account_buf = ByteBuf::new(unsafe { olm_sys::olm_account_size() });
172
173 // let libolm populate the allocated memory
174 let olm_account_ptr = unsafe { olm_sys::olm_account(olm_account_buf.as_mut_void_ptr()) };
175
176 let create_error = {
177 // determine optimal length of the random buffer
178 let random_len = unsafe { olm_sys::olm_create_account_random_length(olm_account_ptr) };
179 let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
180 getrandom(&mut random_buf);
181
182 // create OlmAccount with supplied random data
183 unsafe {
184 olm_sys::olm_create_account(
185 olm_account_ptr,
186 random_buf.as_mut_ptr() as *mut _,
187 random_len,
188 )
189 }
190 };
191
192 if create_error == errors::olm_error() {
193 errors::handle_fatal_error(Self::last_error(olm_account_ptr));
194 }
195 OlmAccount {
196 olm_account_ptr,
197 _olm_account_buf: olm_account_buf,
198 }
199 }
200
201 /// Serialises an [`OlmAccount`] to encrypted Base64.
202 ///
203 /// # C-API equivalent
204 /// `olm_pickle_account`
205 ///
206 /// # Example
207 /// ```
208 /// use olm_rs::account::OlmAccount;
209 /// use olm_rs::PicklingMode;
210 ///
211 /// let identity_keys;
212 /// let olm_account = OlmAccount::new();
213 /// identity_keys = olm_account.identity_keys();
214 /// let pickled = olm_account.pickle(PicklingMode::Unencrypted);
215 /// let olm_account_2 = OlmAccount::unpickle(pickled, PicklingMode::Unencrypted).unwrap();
216 /// let identity_keys_2 = olm_account_2.identity_keys();
217 ///
218 /// assert_eq!(identity_keys, identity_keys_2);
219 /// ```
220 ///
221 /// # Panics
222 /// * `OUTPUT_BUFFER_TOO_SMALL` for OlmAccount's pickled buffer
223 /// * on malformed UTF-8 coding for pickling provided by libolm
224 ///
225 pub fn pickle(&self, mode: PicklingMode) -> String {
226 let mut pickled_buf: Vec<u8> =
227 vec![0; unsafe { olm_sys::olm_pickle_account_length(self.olm_account_ptr) }];
228
229 let pickle_error = {
230 let key = Zeroizing::new(crate::convert_pickling_mode_to_key(mode));
231
232 unsafe {
233 olm_sys::olm_pickle_account(
234 self.olm_account_ptr,
235 key.as_ptr() as *const _,
236 key.len(),
237 pickled_buf.as_mut_ptr() as *mut _,
238 pickled_buf.len(),
239 )
240 }
241 };
242
243 let pickled_result = String::from_utf8(pickled_buf).unwrap();
244
245 if pickle_error == errors::olm_error() {
246 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
247 }
248
249 pickled_result
250 }
251
252 /// Deserialises from encrypted Base64 that was previously obtained by pickling an [`OlmAccount`].
253 ///
254 /// # C-API equivalent
255 /// `olm_unpickle_account`
256 ///
257 /// # Errors
258 /// * `BadAccountKey` if the key doesn't match the one the account was encrypted with
259 /// * `InvalidBase64` if decoding the supplied `pickled` string slice fails
260 ///
261 pub fn unpickle(mut pickled: String, mode: PicklingMode) -> Result<Self, OlmAccountError> {
262 let pickled_len = pickled.len();
263 let pickled_buf = Box::new(unsafe { pickled.as_bytes_mut() });
264
265 let mut olm_account_buf = ByteBuf::new(unsafe { olm_sys::olm_account_size() });
266 let olm_account_ptr = unsafe { olm_sys::olm_account(olm_account_buf.as_mut_void_ptr()) };
267
268 let unpickle_error = {
269 let key = Zeroizing::new(crate::convert_pickling_mode_to_key(mode));
270
271 unsafe {
272 olm_sys::olm_unpickle_account(
273 olm_account_ptr,
274 key.as_ptr() as *const _,
275 key.len(),
276 pickled_buf.as_mut_ptr() as *mut _,
277 pickled_len,
278 )
279 }
280 };
281
282 if unpickle_error == errors::olm_error() {
283 Err(Self::last_error(olm_account_ptr))
284 } else {
285 Ok(OlmAccount {
286 olm_account_ptr,
287 _olm_account_buf: olm_account_buf,
288 })
289 }
290 }
291
292 /// Returns the account's public identity keys already formatted as JSON and BASE64.
293 ///
294 /// # C-API equivalent
295 /// `olm_account_identity_keys`
296 ///
297 /// # Panics
298 /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied identity keys buffer
299 /// * on malformed UTF-8 coding of the identity keys provided by libolm
300 ///
301 pub fn identity_keys(&self) -> String {
302 // get buffer length of identity keys
303 let keys_len = unsafe { olm_sys::olm_account_identity_keys_length(self.olm_account_ptr) };
304 let mut identity_keys_buf: Vec<u8> = vec![0; keys_len];
305
306 // write keys data in the keys buffer
307 let identity_keys_error = unsafe {
308 olm_sys::olm_account_identity_keys(
309 self.olm_account_ptr,
310 identity_keys_buf.as_mut_ptr() as *mut _,
311 keys_len,
312 )
313 };
314
315 // String is constructed from the keys buffer and memory is freed after exiting the scope.
316 // No memory should be leaked.
317 let identity_keys_result = String::from_utf8(identity_keys_buf).unwrap();
318
319 if identity_keys_error == errors::olm_error() {
320 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
321 }
322
323 identity_keys_result
324 }
325
326 /// Returns the account's public identity keys.
327 #[cfg(feature = "deserialization")]
328 pub fn parsed_identity_keys(&self) -> IdentityKeys {
329 serde_json::from_str(&self.identity_keys()).expect("Can't deserialize identity keys")
330 }
331
332 /// Returns the last error that occurred for an OlmAccount.
333 /// Since error codes are encoded as CStrings by libolm,
334 /// OlmAccountError::Unknown is returned on an unknown error code.
335 fn last_error(olm_account_ptr: *mut olm_sys::OlmAccount) -> OlmAccountError {
336 let error;
337 // get CString error code and convert to String
338 unsafe {
339 let error_raw = olm_sys::olm_account_last_error(olm_account_ptr);
340 error = CStr::from_ptr(error_raw).to_str().unwrap();
341 }
342
343 match error {
344 "BAD_ACCOUNT_KEY" => OlmAccountError::BadAccountKey,
345 "BAD_MESSAGE_KEY_ID" => OlmAccountError::BadMessageKeyId,
346 "INVALID_BASE64" => OlmAccountError::InvalidBase64,
347 "NOT_ENOUGH_RANDOM" => OlmAccountError::NotEnoughRandom,
348 "OUTPUT_BUFFER_TOO_SMALL" => OlmAccountError::OutputBufferTooSmall,
349 _ => OlmAccountError::Unknown,
350 }
351 }
352
353 /// Returns the signature of the supplied byte slice.
354 ///
355 /// # C-API equivalent
356 /// `olm_account_sign`
357 ///
358 /// # Panics
359 /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied signature buffer
360 /// * on malformed UTF-8 coding of the signature provided by libolm
361 ///
362 pub fn sign(&self, message: &str) -> String {
363 let message_buf = message.as_bytes();
364 let message_ptr = message_buf.as_ptr() as *const _;
365
366 let signature_len = unsafe { olm_sys::olm_account_signature_length(self.olm_account_ptr) };
367 let mut signature_buf: Vec<u8> = vec![0; signature_len];
368
369 let signature_error = unsafe {
370 olm_sys::olm_account_sign(
371 self.olm_account_ptr,
372 message_ptr,
373 message_buf.len(),
374 signature_buf.as_mut_ptr() as *mut _,
375 signature_len,
376 )
377 };
378
379 let signature_result = String::from_utf8(signature_buf).unwrap();
380
381 if signature_error == errors::olm_error() {
382 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
383 }
384
385 signature_result
386 }
387
388 /// Maximum number of one time keys that this OlmAccount can currently hold.
389 ///
390 /// # C-API equivalent
391 /// `olm_account_max_number_of_one_time_keys`
392 ///
393 pub fn max_number_of_one_time_keys(&self) -> usize {
394 unsafe { olm_sys::olm_account_max_number_of_one_time_keys(self.olm_account_ptr) }
395 }
396
397 /// Generates the supplied number of one time keys.
398 ///
399 /// # C-API equivalent
400 /// `olm_account_generate_one_time_keys`
401 ///
402 /// # Panics
403 /// * `NOT_ENOUGH_RANDOM` for the creation of one time keys
404 ///
405 pub fn generate_one_time_keys(&self, number_of_keys: usize) {
406 // Get correct length for the random buffer
407 let random_len = unsafe {
408 olm_sys::olm_account_generate_one_time_keys_random_length(
409 self.olm_account_ptr,
410 number_of_keys,
411 )
412 };
413
414 let generate_error = {
415 // Construct and populate random buffer
416 let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
417 getrandom(&mut random_buf);
418
419 // Call function for generating one time keys
420 unsafe {
421 olm_sys::olm_account_generate_one_time_keys(
422 self.olm_account_ptr,
423 number_of_keys,
424 random_buf.as_mut_ptr() as *mut _,
425 random_len,
426 )
427 }
428 };
429
430 if generate_error == errors::olm_error() {
431 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
432 }
433 }
434
435 /// Gets the OlmAccount's one time keys formatted as JSON.
436 ///
437 /// # C-API equivalent
438 /// `olm_account_one_time_keys`
439 ///
440 /// # Panics
441 /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied one time keys buffer
442 /// * on malformed UTF-8 coding of the keys provided by libolm
443 ///
444 pub fn one_time_keys(&self) -> String {
445 // get buffer length of OTKs
446 let otks_len = unsafe { olm_sys::olm_account_one_time_keys_length(self.olm_account_ptr) };
447 let mut otks_buf: Vec<u8> = vec![0; otks_len];
448
449 // write OTKs data in the OTKs buffer
450 let otks_error = unsafe {
451 olm_sys::olm_account_one_time_keys(
452 self.olm_account_ptr,
453 otks_buf.as_mut_ptr() as *mut _,
454 otks_len,
455 )
456 };
457
458 // String is constructed from the OTKs buffer and memory is freed after exiting the scope.
459 let otks_result = String::from_utf8(otks_buf).unwrap();
460
461 if otks_error == errors::olm_error() {
462 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
463 }
464
465 otks_result
466 }
467
468 #[cfg(feature = "deserialization")]
469 /// Returns the account's one-time keys.
470 pub fn parsed_one_time_keys(&self) -> OneTimeKeys {
471 serde_json::from_str(&self.one_time_keys()).expect("Can't deserialize one-time keys.")
472 }
473
474 /// Mark the current set of one time keys as published.
475 ///
476 /// # C-API equivalent
477 /// `olm_account_mark_keys_as_published`
478 ///
479 pub fn mark_keys_as_published(&self) {
480 unsafe {
481 olm_sys::olm_account_mark_keys_as_published(self.olm_account_ptr);
482 }
483 }
484
485 /// Remove the one time key used to create the supplied session.
486 ///
487 /// # C-API equivalent
488 /// `olm_remove_one_time_keys`
489 ///
490 /// # Errors
491 /// * `BAD_MESSAGE_KEY_ID` when the account doesn't hold a matching one time key
492 ///
493 pub fn remove_one_time_keys(&self, session: &OlmSession) -> Result<(), OlmAccountError> {
494 let remove_error = unsafe {
495 olm_sys::olm_remove_one_time_keys(self.olm_account_ptr, session.olm_session_ptr)
496 };
497
498 if remove_error == errors::olm_error() {
499 Err(Self::last_error(self.olm_account_ptr))
500 } else {
501 Ok(())
502 }
503 }
504
505 /// Generates a new fallback key. Only one previous fallback key is stored.
506 ///
507 /// # C-API equivalent
508 /// `olm_account_generate_fallback_key`
509 ///
510 /// # Panics
511 /// * `NOT_ENOUGH_RANDOM`
512 ///
513 pub fn generate_fallback_key(&self) {
514 // determine optimal length of the random buffer
515 let random_len = unsafe {
516 olm_sys::olm_account_generate_fallback_key_random_length(self.olm_account_ptr)
517 };
518 let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
519 getrandom(&mut random_buf);
520
521 // write keys data in the keys buffer
522 let fallback_key_error = unsafe {
523 olm_sys::olm_account_generate_fallback_key(
524 self.olm_account_ptr,
525 random_buf.as_mut_ptr() as *mut _,
526 random_len,
527 )
528 };
529
530 if fallback_key_error == errors::olm_error() {
531 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
532 }
533 }
534
535 /// Output fallback key of this account in JSON format.
536 ///
537 /// This is what the output looks like before generating an
538 /// initial fallback key:
539 /// ```json
540 /// {
541 /// "curve25519": {}
542 /// }
543 /// ```
544 ///
545 /// And after:
546 /// ```json
547 /// {
548 /// "curve25519": {
549 /// "AAAAAQ": "u4XQpRre6j7peD4clRq9d56kRbwnVEAsavIiZHHZekY"
550 /// }
551 /// }
552 /// ```
553 ///
554 /// # C-API equivalent
555 /// `olm_account_one_time_key`
556 ///
557 /// # Panics
558 /// * `OUTPUT_BUFFER_TOO_SMALL`
559 ///
560 pub fn fallback_key(&self) -> String {
561 // get buffer length of fallback keys
562 let fallback_len =
563 unsafe { olm_sys::olm_account_unpublished_fallback_key_length(self.olm_account_ptr) };
564 let mut fallback_buf: Vec<u8> = vec![0; fallback_len];
565
566 // write fallbacks key data in the fallback key buffer
567 let fallback_error = unsafe {
568 olm_sys::olm_account_unpublished_fallback_key(
569 self.olm_account_ptr,
570 fallback_buf.as_mut_ptr() as *mut _,
571 fallback_len,
572 )
573 };
574
575 if fallback_error == errors::olm_error() {
576 errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
577 }
578
579 // String is constructed from the fallback key buffer and memory is freed after exiting the scope.
580 String::from_utf8(fallback_buf).unwrap()
581 }
582
583 #[cfg(feature = "deserialization")]
584 /// Returns the account's fallback key. `None` if no fallback key has been generated yet.
585 pub fn parsed_fallback_key(&self) -> Option<FallbackKey> {
586 let parsed_fallback: Value =
587 serde_json::from_str(&self.fallback_key()).expect("Fallback key isn't in JSON format.");
588
589 // We make some assumptions about the structure of the parsed JSON.
590 // 1) At the top level is the index "curve25519" that contains an object
591 // 2) This object is either empty or contains a singular entry to a (key) string
592 parsed_fallback["curve25519"]
593 .as_object()
594 .unwrap()
595 .iter()
596 .next()
597 .map(|(index, key_value)| FallbackKey {
598 index: index.clone(),
599 key: key_value.as_str().unwrap().to_string(),
600 })
601 }
602
603 /// Creates an inbound session for sending/receiving messages from a received 'prekey' message.
604 ///
605 /// # Arguments
606 ///
607 /// * `message` - An Olm pre-key message that was encrypted for this
608 /// account.
609 ///
610 /// # Errors
611 /// * `InvalidBase64`
612 /// * `BadMessageVersion`
613 /// * `BadMessageFormat`
614 /// * `BadMessageKeyId`
615 ///
616 pub fn create_inbound_session(
617 &self,
618 message: PreKeyMessage,
619 ) -> Result<OlmSession, OlmSessionError> {
620 OlmSession::create_inbound_session(self, message)
621 }
622
623 /// Creates an inbound session for sending/receiving messages from a received 'prekey' message.
624 ///
625 /// * `their_identity_key` - The identity key of an Olm account that
626 /// encrypted this Olm message.
627 ///
628 /// * `message` - An Olm pre-key message that was encrypted for this
629 /// account.
630 ///
631 /// # Errors
632 /// * `InvalidBase64`
633 /// * `BadMessageVersion`
634 /// * `BadMessageFormat`
635 /// * `BadMessageKeyId`
636 ///
637 pub fn create_inbound_session_from(
638 &self,
639 their_identity_key: &str,
640 message: PreKeyMessage,
641 ) -> Result<OlmSession, OlmSessionError> {
642 OlmSession::create_inbound_session_from(self, their_identity_key, message)
643 }
644
645 /// Creates an outbound session for sending messages to a specific
646 /// identity and one time key.
647 ///
648 /// # Errors
649 /// * `InvalidBase64` for invalid base64 coding on supplied arguments
650 ///
651 /// # Panics
652 /// * `NotEnoughRandom` if not enough random data was supplied
653 ///
654 pub fn create_outbound_session(
655 &self,
656 their_identity_key: &str,
657 their_one_time_key: &str,
658 ) -> Result<OlmSession, OlmSessionError> {
659 OlmSession::create_outbound_session(self, their_identity_key, their_one_time_key)
660 }
661}
662
663impl Default for OlmAccount {
664 fn default() -> Self {
665 Self::new()
666 }
667}
668
669impl Drop for OlmAccount {
670 fn drop(&mut self) {
671 unsafe {
672 olm_sys::olm_clear_account(self.olm_account_ptr);
673 }
674 }
675}
676
677#[cfg(test)]
678mod test {
679 use super::OlmAccount;
680 use serde_json::Value;
681
682 #[test]
683 fn fallback_key() {
684 let account = OlmAccount::new();
685
686 assert!(account.parsed_fallback_key().is_none());
687
688 account.generate_fallback_key();
689
690 let parsed_fallback = account.parsed_fallback_key().unwrap();
691 let manually_parsed_fallback: Value = serde_json::from_str(&account.fallback_key())
692 .expect("Fallback key isn't in JSON format.");
693 assert_eq!(
694 parsed_fallback.curve25519(),
695 manually_parsed_fallback["curve25519"][parsed_fallback.index()]
696 );
697 }
698
699 #[cfg(feature = "deserialization")]
700 #[test]
701 fn parsed_keys() {
702 let account = OlmAccount::new();
703 let identity_keys = json::parse(&account.identity_keys()).unwrap();
704 let identity_keys_parsed = account.parsed_identity_keys();
705 assert_eq!(
706 identity_keys_parsed.curve25519(),
707 identity_keys["curve25519"]
708 );
709 assert_eq!(identity_keys_parsed.ed25519(), identity_keys["ed25519"]);
710 }
711}