From b989a0c2f7dd2c5b7a3eeefef49af380afa3683c Mon Sep 17 00:00:00 2001 From: Clint Frederickson Date: Fri, 19 Nov 2021 11:59:42 -0700 Subject: [PATCH] Removes `chrono` and usages of `DateTime` and replaces them with`time::OffsetDateTime`. This change should not have any semantic meaning and is being done to address RUSTSEC-2020-0071 (see #244) and RUSTSEC-20200159 (see #245). This is a breaking API change because some result types include create/updated timestamps. Consumers of the library will need to add a direct dependency on `time`. Note that `chrono` already uses `time` internally, so this change should not bloat transitive dependencies. For those needing serialization support of `OffsetDateTime` timestamps, the `time` crate offers this support with the `serde` feature flag. One small point of friction here is rfc3339 support. `time` has this support on [a branch](https://github.com/time-rs/time/issues/387) that is blocked on a rust feature hitting stable, so I copied in a few lines of code from that branch to support deserializing rfc3339 timestamps. --- Cargo.toml | 18 +++++++-- src/internal.rs | 13 ++++--- src/internal/document_api.rs | 26 ++++++------- src/internal/document_api/requests.rs | 42 +++++++++++---------- src/internal/group_api.rs | 30 +++++++-------- src/internal/group_api/requests.rs | 46 ++++++++++++----------- src/internal/rest.rs | 29 ++++++++++---- src/internal/serde_rfc3339.rs | 54 +++++++++++++++++++++++++++ src/internal/user_api.rs | 34 +++++++++-------- src/internal/user_api/requests.rs | 30 ++++++++------- 10 files changed, 210 insertions(+), 112 deletions(-) create mode 100644 src/internal/serde_rfc3339.rs diff --git a/Cargo.toml b/Cargo.toml index 9f1ad1a..80031e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,13 @@ license = "AGPL-3.0-only" repository = "https://github.com/IronCoreLabs/ironoxide" documentation = "https://docs.rs/ironoxide" categories = [ "cryptography" ] -keywords = [ "cryptography", "proxy-re-encryption", "PRE", "ECC", "transform-encryption" ] +keywords = [ + "cryptography", + "proxy-re-encryption", + "PRE", + "ECC", + "transform-encryption", +] description = "A pure-Rust SDK for accessing IronCore's privacy platform" edition = "2018" @@ -16,7 +22,6 @@ async-trait = "0.1.21" base64 = "0.13" base64-serde = "0.6.1" bytes = "1" -chrono = { version = "0.4", features = [ "serde" ] } dashmap = "4" futures = "0.3.1" hex = "0.4" @@ -34,8 +39,13 @@ recrypt = "0.12" regex = "1.4" reqwest = { version = "0.11", features = [ "json" ], default-features = false } ring = { version = "0.16", features = [ "std" ] } -serde = { version = "1.0.123", features = [ "derive" ] } +serde = { version = "1.0.126", features = [ "derive" ] } serde_json = "1" +time = { version = "0.3", features = [ + "std", + "serde-human-readable", + "parsing", +] } tokio = { version = "1", features = [ "time" ] } url = "2.2" vec1 = "1.6" @@ -96,4 +106,4 @@ name = "ironoxide_bench" harness = false [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/src/internal.rs b/src/internal.rs index afa712b..a7afb72 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -7,7 +7,6 @@ use crate::internal::{ rest::{Authorization, IronCoreRequest, SignatureUrlString}, user_api::UserId, }; -use chrono::{DateTime, Utc}; use futures::Future; use lazy_static::lazy_static; use log::error; @@ -27,10 +26,12 @@ use std::{ result::Result, sync::{Mutex, MutexGuard}, }; +use time::OffsetDateTime; pub mod document_api; pub mod group_api; mod rest; +mod serde_rfc3339; pub mod user_api; lazy_static! { @@ -279,6 +280,8 @@ pub fn validate_name(name: &str, name_type: &str) -> Result { pub(in crate::internal::auth_v2) req_auth: &'a RequestAuth, - pub(in crate::internal::auth_v2) timestamp: DateTime, + pub(in crate::internal::auth_v2) timestamp: OffsetDateTime, } impl<'a> AuthV2Builder<'a> { - pub fn new(req_auth: &'a RequestAuth, timestamp: DateTime) -> AuthV2Builder { + pub fn new(req_auth: &'a RequestAuth, timestamp: OffsetDateTime) -> AuthV2Builder { AuthV2Builder { req_auth, timestamp, @@ -335,7 +338,7 @@ pub struct RequestAuth { impl RequestAuth { pub fn create_signature_v2<'a>( &'a self, - current_time: DateTime, + current_time: OffsetDateTime, sig_url: SignatureUrlString, method: Method, body: Option<&'a [u8]>, @@ -1197,7 +1200,7 @@ pub(crate) mod tests { }; let recrypt = recrypt::api::Recrypt::new(); let (_, pub_key) = recrypt.generate_key_pair()?; - let time = chrono::Utc::now(); + let time = OffsetDateTime::now_utc(); let create_gmr = |id: GroupId, needs_rotation: Option| { create_group_meta_result( id, diff --git a/src/internal/document_api.rs b/src/internal/document_api.rs index fe071ed..8d79198 100644 --- a/src/internal/document_api.rs +++ b/src/internal/document_api.rs @@ -19,7 +19,6 @@ use crate::{ }, DeviceSigningKeyPair, PolicyCache, }; -use chrono::{DateTime, Utc}; use futures::{try_join, Future}; use hex::encode; use itertools::{Either, Itertools}; @@ -39,6 +38,7 @@ use std::{ ops::DerefMut, sync::Mutex, }; +use time::OffsetDateTime; mod requests; @@ -240,11 +240,11 @@ impl DocumentListMeta { &self.0.association.typ } /// Date and time when the document was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.0.created } /// Date and time when the document was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.0.updated } } @@ -279,11 +279,11 @@ impl DocumentMetadataResult { self.0.name.as_ref() } /// Date and time when the document was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.0.created } /// Date and time when the document was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.0.updated } /// How the requesting user has access to the document @@ -370,8 +370,8 @@ impl DocumentEncryptUnmanagedResult { pub struct DocumentEncryptResult { id: DocumentId, name: Option, - updated: DateTime, - created: DateTime, + updated: OffsetDateTime, + created: OffsetDateTime, encrypted_data: Vec, grants: Vec, access_errs: Vec, @@ -390,11 +390,11 @@ impl DocumentEncryptResult { self.name.as_ref() } /// Date and time when the document was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the document was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.updated } /// Users and groups the document was successfully encrypted to @@ -413,8 +413,8 @@ impl DocumentEncryptResult { pub struct DocumentDecryptResult { id: DocumentId, name: Option, - updated: DateTime, - created: DateTime, + updated: OffsetDateTime, + created: OffsetDateTime, decrypted_data: Vec, } impl DocumentDecryptResult { @@ -431,11 +431,11 @@ impl DocumentDecryptResult { self.name.as_ref() } /// Date and time when the document was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the document was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.updated } } diff --git a/src/internal/document_api/requests.rs b/src/internal/document_api/requests.rs index ca6242d..93ac0fe 100644 --- a/src/internal/document_api/requests.rs +++ b/src/internal/document_api/requests.rs @@ -11,9 +11,9 @@ use crate::internal::{ user_api::UserId, IronOxideErr, RequestAuth, RequestErrorCode, }; -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; +use time::OffsetDateTime; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Association { @@ -130,8 +130,10 @@ pub struct DocumentMetaApiResponse { pub association: Association, pub visible_to: DocumentVisibility, pub encrypted_symmetric_key: TransformedEncryptedValue, - pub updated: DateTime, - pub created: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub updated: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub created: OffsetDateTime, } pub mod document_list { @@ -147,8 +149,10 @@ pub mod document_list { pub id: DocumentId, pub name: Option, pub association: Association, - pub created: DateTime, - pub updated: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub created: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub updated: OffsetDateTime, } /// Make GET request to document list endpoint for the current user/device context @@ -159,7 +163,7 @@ pub mod document_list { .get( "documents", RequestErrorCode::DocumentList, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -176,7 +180,7 @@ pub mod document_get { .get( &format!("documents/{}", rest::url_encode(&id.0)), RequestErrorCode::DocumentGet, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -194,7 +198,7 @@ pub mod edek_transform { "edeks/transform", edek_bytes, RequestErrorCode::EdekTransform, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -227,14 +231,15 @@ pub mod document_create { pub(crate) id: DocumentId, pub(crate) value: DocumentCreateValue, } - #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DocumentCreateResponse { pub(crate) id: DocumentId, pub(crate) name: Option, - pub(crate) updated: DateTime, - pub(crate) created: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) updated: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) created: OffsetDateTime, pub(crate) shared_with: Vec, } @@ -259,7 +264,7 @@ pub mod document_create { "documents", &req, RequestErrorCode::DocumentCreate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -310,7 +315,7 @@ pub mod policy_get { "policies", &query_params, RequestErrorCode::PolicyGet, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -334,7 +339,7 @@ pub mod document_update { &format!("documents/{}", rest::url_encode(&id.0)), &DocumentUpdateRequest { name }, RequestErrorCode::DocumentUpdate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -464,7 +469,7 @@ pub mod document_access { &format!("documents/{}/access", rest::url_encode(id.id())), &req, RequestErrorCode::DocumentGrantAccess, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -481,7 +486,7 @@ pub mod document_access { user_or_groups: revoke_list, }, RequestErrorCode::DocumentRevokeAccess, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -489,7 +494,6 @@ pub mod document_access { #[cfg(test)] mod tests { - use chrono::TimeZone; use super::*; @@ -498,8 +502,8 @@ mod tests { fn document_item_serde_format_is_expected() { use document_list::DocumentListApiResponseItem; - let created = Utc.timestamp_millis(1_551_461_529_000); - let updated = Utc.timestamp_millis(1_551_461_529_001); + let created = OffsetDateTime::from_unix_timestamp_nanos(1_551_461_529_000_000).unwrap(); + let updated = OffsetDateTime::from_unix_timestamp_nanos(1_551_461_529_001_000).unwrap(); let item = DocumentListApiResponseItem { id: DocumentId("my_id".to_string()), name: None, diff --git a/src/internal/group_api.rs b/src/internal/group_api.rs index b8e9303..b2e075e 100644 --- a/src/internal/group_api.rs +++ b/src/internal/group_api.rs @@ -12,7 +12,6 @@ use crate::{ RequestAuth, SchnorrSignature, TransformKey, WithKey, }, }; -use chrono::{DateTime, Utc}; use core::convert::identity; use futures::try_join; use itertools::{Either, Itertools}; @@ -23,6 +22,7 @@ use std::{ convert::{TryFrom, TryInto}, iter::FromIterator, }; +use time::OffsetDateTime; use vec1::Vec1; mod requests; @@ -141,8 +141,8 @@ pub struct GroupMetaResult { group_master_public_key: PublicKey, is_admin: bool, is_member: bool, - created: DateTime, - updated: DateTime, + created: OffsetDateTime, + updated: OffsetDateTime, needs_rotation: Option, } impl GroupMetaResult { @@ -163,11 +163,11 @@ impl GroupMetaResult { self.is_member } /// Date and time when the group was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the group was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.updated } /// Public key for encrypting to the group @@ -195,8 +195,8 @@ pub struct GroupCreateResult { owner: UserId, admins: Vec, members: Vec, - created: DateTime, - updated: DateTime, + created: OffsetDateTime, + updated: OffsetDateTime, needs_rotation: Option, } impl GroupCreateResult { @@ -233,11 +233,11 @@ impl GroupCreateResult { self.members.as_ref() } /// Date and time when the group was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the group was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.updated } /// Whether the group's private key needs rotation. Can only be accessed by a group administrator. @@ -261,8 +261,8 @@ pub struct GroupGetResult { owner: Option, admin_list: Option>, member_list: Option>, - created: DateTime, - updated: DateTime, + created: OffsetDateTime, + updated: OffsetDateTime, needs_rotation: Option, /// not exposed outside of the module encrypted_private_key: Option, @@ -289,11 +289,11 @@ impl GroupGetResult { self.is_member } /// Date and time when the group was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the group was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.updated } /// The owner of the group @@ -933,8 +933,8 @@ pub(crate) mod tests { group_master_public_key: PublicKey, is_admin: bool, is_member: bool, - created: DateTime, - updated: DateTime, + created: OffsetDateTime, + updated: OffsetDateTime, needs_rotation: Option, ) -> GroupMetaResult { GroupMetaResult { diff --git a/src/internal/group_api/requests.rs b/src/internal/group_api/requests.rs index c51ed67..5cc19eb 100644 --- a/src/internal/group_api/requests.rs +++ b/src/internal/group_api/requests.rs @@ -12,12 +12,12 @@ use crate::internal::{ }, IronOxideErr, RequestAuth, RequestErrorCode, SchnorrSignature, }; -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, convert::{TryFrom, TryInto}, }; +use time::OffsetDateTime; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -34,8 +34,10 @@ pub struct GroupBasicApiResponse { pub(crate) name: Option, pub(crate) permissions: HashSet, pub(crate) status: u32, - pub(crate) updated: DateTime, - pub(crate) created: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) updated: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) created: OffsetDateTime, pub(crate) group_master_public_key: PublicKey, pub(crate) needs_rotation: Option, } @@ -65,8 +67,10 @@ pub struct GroupGetApiResponse { pub(crate) name: Option, pub(crate) permissions: HashSet, pub(crate) status: u32, - pub(crate) updated: DateTime, - pub(crate) created: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) updated: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(crate) created: OffsetDateTime, pub(crate) owner: Option, pub(crate) admin_ids: Option>, pub(crate) member_ids: Option>, @@ -102,8 +106,10 @@ pub struct GroupCreateApiResponse { pub(in crate::internal) id: GroupId, pub(in crate::internal) name: Option, pub(in crate::internal) permissions: HashSet, - pub(in crate::internal) updated: DateTime, - pub(in crate::internal) created: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(in crate::internal) updated: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub(in crate::internal) created: OffsetDateTime, pub(in crate::internal) owner: UserId, pub(in crate::internal) admin_ids: Vec, pub(in crate::internal) member_ids: Vec, @@ -187,7 +193,7 @@ pub mod group_list { .get( "groups", RequestErrorCode::GroupList, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -203,7 +209,7 @@ pub mod group_list { "groups", &[("id".into(), rest::url_encode(&group_ids.join(",")))], RequestErrorCode::GroupList, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -249,7 +255,7 @@ pub mod group_create { "groups", &req, RequestErrorCode::GroupCreate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -266,7 +272,7 @@ pub mod group_get { .get( &format!("groups/{}", rest::url_encode(&id.0)), RequestErrorCode::GroupGet, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -321,7 +327,7 @@ pub mod group_update_private_key { admins, }, RequestErrorCode::GroupKeyUpdate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -343,7 +349,7 @@ pub mod group_delete { .delete_with_no_body( &format!("groups/{}", rest::url_encode(&id.0)), RequestErrorCode::GroupDelete, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -367,7 +373,7 @@ pub mod group_update { &format!("groups/{}", rest::url_encode(&id.0)), &GroupUpdateRequest { name }, RequestErrorCode::GroupUpdate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -407,7 +413,7 @@ pub mod group_add_member { signature: signature.into(), }, RequestErrorCode::GroupAddMember, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -439,7 +445,7 @@ pub mod group_add_admin { signature: signature.into(), }, RequestErrorCode::GroupAddMember, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -484,7 +490,7 @@ pub mod group_remove_entity { users: removed_users, }, error_code, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -492,16 +498,14 @@ pub mod group_remove_entity { #[cfg(test)] mod tests { - use chrono::TimeZone; - use super::*; use recrypt::api::KeyGenOps; ///This test is to ensure our lowercase admin and member permissions are handled correctly. #[test] fn group_item_serde_format_is_expected() { - let created = Utc.timestamp_millis(1_551_461_529_000); - let updated = Utc.timestamp_millis(1_551_461_529_001); + let created = OffsetDateTime::from_unix_timestamp_nanos(1_551_461_529_000_000).unwrap(); + let updated = OffsetDateTime::from_unix_timestamp_nanos(1_551_461_529_001_000).unwrap(); let mut permissions = HashSet::new(); permissions.insert(Permission::Member); permissions.insert(Permission::Admin); diff --git a/src/internal/rest.rs b/src/internal/rest.rs index b7f7be9..14d09f8 100644 --- a/src/internal/rest.rs +++ b/src/internal/rest.rs @@ -6,7 +6,6 @@ use crate::internal::{ DeviceSigningKeyPair, IronOxideErr, RequestErrorCode, OUR_REQUEST, }; use bytes::Bytes; -use chrono::{DateTime, Utc}; use lazy_static::lazy_static; use percent_encoding::{AsciiSet, CONTROLS}; use reqwest::{ @@ -20,6 +19,7 @@ use std::{ marker::PhantomData, ops::Deref, }; +use time::OffsetDateTime; lazy_static! { static ref DEFAULT_HEADERS: HeaderMap = { @@ -141,7 +141,7 @@ impl<'a> Authorization<'a> { } pub fn create_signatures_v2( - time: DateTime, + time: OffsetDateTime, segment_id: usize, user_id: &UserId, method: Method, @@ -200,18 +200,22 @@ impl SignatureUrlString { /// Representation of X-IronCore-User-Context header #[derive(Clone, Debug)] pub struct HeaderIronCoreUserContext { - timestamp: DateTime, + timestamp: OffsetDateTime, segment_id: usize, user_id: UserId, public_signing_key: [u8; 32], } +pub const fn as_unix_timestamp_millis(ts: OffsetDateTime) -> i128 { + ts.unix_timestamp() as i128 * 1_000 + ts.millisecond() as i128 +} + impl HeaderIronCoreUserContext { /// Payload of the header fn payload(&self) -> String { format!( "{},{},{},{}", - self.timestamp.timestamp_millis(), + as_unix_timestamp_millis(self.timestamp), self.segment_id, self.user_id.id(), base64::encode(&self.public_signing_key) @@ -1016,7 +1020,6 @@ pub mod json { mod tests { use super::*; use crate::internal::tests::{contains, length}; - use chrono::TimeZone; use galvanic_assert::{ matchers::{variant::*, *}, *, @@ -1130,7 +1133,7 @@ mod tests { #[test] fn ironcore_user_context_signing_and_headers_are_correct() { - let ts = Utc.timestamp_millis(123_456); + let ts = OffsetDateTime::from_unix_timestamp_nanos(123_456_000_000).unwrap(); let signing_key_bytes: [u8; 64] = [ 38, 218, 141, 117, 248, 58, 31, 187, 17, 183, 163, 49, 109, 66, 9, 132, 131, 77, 196, 31, 117, 15, 61, 29, 171, 119, 177, 31, 219, 164, 218, 221, 198, 202, 159, 250, 136, @@ -1186,7 +1189,7 @@ mod tests { #[test] fn ironcore_auth_v2_produces_expected_values() { - let ts = Utc.timestamp_millis(123_456); + let ts = OffsetDateTime::from_unix_timestamp_nanos(123_456_000_000).unwrap(); let signing_key_bytes: [u8; 64] = [ 38, 218, 141, 117, 248, 58, 31, 187, 17, 183, 163, 49, 109, 66, 9, 132, 131, 77, 196, 31, 117, 15, 61, 29, 171, 119, 177, 31, 219, 164, 218, 221, 198, 202, 159, 250, 136, @@ -1364,4 +1367,16 @@ mod tests { assert_eq!(req.url().query(), None); assert_eq!(req.url().as_str(), "https://example.com/policies") } + + #[test] + fn as_unix_timestamp_millis_works() { + // 1999999ns on the end of this. Also 1ms + let ts = OffsetDateTime::from_unix_timestamp_nanos(1_638_576_000_001_999_999).unwrap(); + + let ts_nanos = ts.unix_timestamp_nanos(); + let ts_millis = as_unix_timestamp_millis(ts.clone()); + assert_eq!(1_638_576_000_001, ts_millis); + + assert_eq!(ts_nanos as i128 / 1000000, ts_millis); + } } diff --git a/src/internal/serde_rfc3339.rs b/src/internal/serde_rfc3339.rs new file mode 100644 index 0000000..d4a08a6 --- /dev/null +++ b/src/internal/serde_rfc3339.rs @@ -0,0 +1,54 @@ +//! MAINTENANCE NOTE +//! This code is copied from https://github.com/time-rs/time/tree/serde-rfc3339 +//! Once https://github.com/time-rs/time/issues/387 is closed this file can be deleted. +//! +//! Use the well-known [RFC3339 format] when serializing and deserializing an [`OffsetDateTime`]. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6 +//! [with]: https://serde.rs/field-attrs.html#with + +use serde::de::Error as _; +use serde::ser::Error as _; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; + +/// Serialize an [`OffsetDateTime`] using the well-known RFC3339 format. +pub fn serialize( + datetime: &OffsetDateTime, + serializer: S, +) -> Result { + datetime + .format(&Rfc3339) + .map_err(S::Error::custom)? + .serialize(serializer) +} + +/// Deserialize an [`OffsetDateTime`] from its RFC3339 representation. +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result { + OffsetDateTime::parse(<_>::deserialize(deserializer)?, &Rfc3339).map_err(D::Error::custom) +} + +#[cfg(test)] +mod test { + use serde::{Deserialize, Serialize}; + use time::OffsetDateTime; + + #[test] + fn offset_date_time_can_deserialize_rfc3339_2() { + #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] + struct SoLong { + #[serde(with = "crate::internal::serde_rfc3339")] + bye_time: OffsetDateTime, + } + + let ts = SoLong { + bye_time: OffsetDateTime::from_unix_timestamp(1638576000).unwrap(), + }; + let json_string = serde_json::to_string(&ts).unwrap(); + + assert_eq!("{\"bye_time\":\"2021-12-04T00:00:00Z\"}", &json_string); + } +} diff --git a/src/internal/user_api.rs b/src/internal/user_api.rs index e8e1f99..356b6fb 100644 --- a/src/internal/user_api.rs +++ b/src/internal/user_api.rs @@ -2,7 +2,6 @@ use crate::{ crypto::aes::{self, EncryptedMasterKey}, internal::{rest::IronCoreRequest, *}, }; -use chrono::{DateTime, Utc}; use itertools::{Either, Itertools}; use jsonwebtoken::Algorithm; use rand::rngs::OsRng; @@ -13,6 +12,7 @@ use std::{ result::Result, sync::Mutex, }; +use time::OffsetDateTime; /// private module that handles interaction with the IronCore webservice mod requests; @@ -170,7 +170,7 @@ pub(crate) struct DeviceAdd { /// Signature needed for authorized device requests signature: SchnorrSignature, /// Timestamp used in the schnorr signature - signature_ts: DateTime, + signature_ts: OffsetDateTime, } /// Metadata for a user. @@ -229,9 +229,9 @@ pub struct UserDevice { id: DeviceId, name: Option, /// time the device was created - created: DateTime, + created: OffsetDateTime, /// time the device was last updated - last_updated: DateTime, + last_updated: OffsetDateTime, /// true if this UserDevice is the device making the query is_current_device: bool, } @@ -245,11 +245,11 @@ impl UserDevice { self.name.as_ref() } /// Date and time when the device was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// Date and time when the device was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.last_updated } /// Whether this is the device that was used to make the API request @@ -507,8 +507,8 @@ pub struct DeviceAddResult { signing_private_key: DeviceSigningKeyPair, device_id: DeviceId, name: Option, - created: DateTime, - last_updated: DateTime, + created: OffsetDateTime, + last_updated: OffsetDateTime, } impl DeviceAddResult { /// ID of the device @@ -538,11 +538,11 @@ impl DeviceAddResult { &self.device_private_key } /// The date and time when the device was created - pub fn created(&self) -> &DateTime { + pub fn created(&self) -> &OffsetDateTime { &self.created } /// The date and time when the device was last updated - pub fn last_updated(&self) -> &DateTime { + pub fn last_updated(&self) -> &OffsetDateTime { &self.last_updated } } @@ -563,7 +563,7 @@ pub async fn generate_device_key( jwt: &Jwt, password: Password, device_name: Option, - signing_ts: &DateTime, + signing_ts: &OffsetDateTime, request: &IronCoreRequest, ) -> Result { // verify that this user exists @@ -693,7 +693,7 @@ fn generate_device_add( recrypt: &Recrypt>, jwt: &Jwt, user_master_keypair: &KeyPair, - signing_ts: &DateTime, + signing_ts: &OffsetDateTime, ) -> Result { let signing_keypair = recrypt.generate_ed25519_key_pair(); let (recrypt_priv_key, recrypt_pub_key) = recrypt.generate_key_pair()?; @@ -725,10 +725,10 @@ fn gen_device_add_signature( jwt: &Jwt, user_master_keypair: &KeyPair, transform_key: &TransformKey, - signing_ts: &DateTime, + signing_ts: &OffsetDateTime, ) -> SchnorrSignature { struct SignedMessage<'a> { - timestamp: &'a DateTime, + timestamp: &'a OffsetDateTime, transform_key: &'a TransformKey, jwt: &'a Jwt, user_public_key: &'a PublicKey, @@ -737,7 +737,11 @@ fn gen_device_add_signature( impl<'a> recrypt::api::Hashable for SignedMessage<'a> { fn to_bytes(&self) -> Vec { let mut vec: Vec = vec![]; - vec.extend_from_slice(self.timestamp.timestamp_millis().to_string().as_bytes()); + vec.extend_from_slice( + rest::as_unix_timestamp_millis(self.timestamp.clone()) + .to_string() + .as_bytes(), + ); vec.extend_from_slice(&self.transform_key.to_bytes()); vec.extend_from_slice(&self.jwt.to_utf8()); vec.extend_from_slice(&self.user_public_key.as_bytes()); diff --git a/src/internal/user_api/requests.rs b/src/internal/user_api/requests.rs index b935ecb..84b9072 100644 --- a/src/internal/user_api/requests.rs +++ b/src/internal/user_api/requests.rs @@ -14,9 +14,9 @@ use crate::{ IronOxideErr, RequestAuth, RequestErrorCode, }, }; -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; +use time::OffsetDateTime; use crate::internal::auth_v2::AuthV2Builder; #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -152,7 +152,7 @@ pub mod user_get { .get( "users/current", RequestErrorCode::UserGetCurrent, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -207,7 +207,7 @@ pub mod user_update_private_key { augmentation_factor: augmenting_key, }, RequestErrorCode::UserKeyUpdate, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -295,7 +295,7 @@ pub mod user_key_list { "users", &[("id".into(), rest::url_encode(&user_ids.join(",")))], RequestErrorCode::UserKeyList, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } else { @@ -336,8 +336,10 @@ pub mod device_add { pub device_id: DeviceId, pub device_public_key: PublicKey, pub name: Option, - pub created: DateTime, - pub updated: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub created: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + pub updated: OffsetDateTime, } pub(crate) async fn user_device_add( @@ -347,7 +349,7 @@ pub mod device_add { request: &IronCoreRequest, ) -> Result { let req_body: DeviceAddReq = DeviceAddReq { - timestamp: device_add.signature_ts.timestamp_millis() as u64, + timestamp: rest::as_unix_timestamp_millis(device_add.signature_ts) as u64, user_public_key: device_add.user_public_key.clone().into(), signature: device_add.signature.clone().into(), device: Device { @@ -367,7 +369,7 @@ pub mod device_add { } pub mod device_list { - use chrono::{DateTime, Utc}; + use time::OffsetDateTime; use crate::internal::user_api::{DeviceId, DeviceName, UserDevice}; @@ -379,8 +381,10 @@ pub mod device_list { #[serde(rename = "id")] device_id: DeviceId, name: Option, - created: DateTime, - updated: DateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + created: OffsetDateTime, + #[serde(with = "crate::internal::serde_rfc3339")] + updated: OffsetDateTime, is_current_device: bool, } @@ -394,7 +398,7 @@ pub mod device_list { .get( &format!("users/{}/devices", rest::url_encode(&auth.account_id().0)), RequestErrorCode::UserDeviceList, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -433,7 +437,7 @@ pub mod device_delete { device_id.0 ), RequestErrorCode::UserDeviceDelete, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await } @@ -448,7 +452,7 @@ pub mod device_delete { rest::url_encode(&auth.account_id().0) ), RequestErrorCode::UserDeviceDelete, - AuthV2Builder::new(auth, Utc::now()), + AuthV2Builder::new(auth, OffsetDateTime::now_utc()), ) .await }