bitwarden_auth/registration/open_org_invite_crypto/
serialization.rs1use std::str::FromStr;
7
8use bitwarden_crypto::safe::{DataEnvelope, SecretProtectedKeyEnvelope};
9use bitwarden_encoding::{B64Url, FromStrVisitor};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use super::SealedOpenOrgInviteData;
14
15#[derive(Serialize, Deserialize)]
18struct SealedOpenOrgInviteDataWire {
19 #[serde(rename = "d", with = "serde_bytes")]
22 data_envelope: Vec<u8>,
23 #[serde(rename = "k", with = "serde_bytes")]
25 key_envelope: Vec<u8>,
26}
27
28#[derive(Debug, Error)]
30pub enum SealedOpenOrgInviteDataError {
31 #[error("Sealed open org invite data is malformed")]
33 Malformed,
34}
35
36impl FromStr for SealedOpenOrgInviteData {
37 type Err = SealedOpenOrgInviteDataError;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 let outer = B64Url::try_from(s).map_err(|_| SealedOpenOrgInviteDataError::Malformed)?;
41 let wire: SealedOpenOrgInviteDataWire = ciborium::de::from_reader(outer.as_bytes())
42 .map_err(|_| SealedOpenOrgInviteDataError::Malformed)?;
43 let data_envelope = DataEnvelope::from(wire.data_envelope);
44 let key_envelope = SecretProtectedKeyEnvelope::try_from(&wire.key_envelope)
45 .map_err(|_| SealedOpenOrgInviteDataError::Malformed)?;
46 Ok(SealedOpenOrgInviteData {
47 data_envelope,
48 key_envelope,
49 })
50 }
51}
52
53impl From<&SealedOpenOrgInviteData> for String {
54 fn from(val: &SealedOpenOrgInviteData) -> Self {
55 let data_bytes: Vec<u8> = (&val.data_envelope).into();
56 let key_bytes: Vec<u8> = (&val.key_envelope).into();
57 let wire = SealedOpenOrgInviteDataWire {
58 data_envelope: data_bytes,
59 key_envelope: key_bytes,
60 };
61 let mut buf = Vec::new();
62 ciborium::ser::into_writer(&wire, &mut buf)
63 .expect("CBOR encoding of two byte fields cannot fail");
64 B64Url::from(buf).to_string()
65 }
66}
67
68impl From<SealedOpenOrgInviteData> for String {
69 fn from(val: SealedOpenOrgInviteData) -> Self {
70 (&val).into()
71 }
72}
73
74impl Serialize for SealedOpenOrgInviteData {
75 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: serde::Serializer,
78 {
79 serializer.serialize_str(&String::from(self))
80 }
81}
82
83impl<'de> Deserialize<'de> for SealedOpenOrgInviteData {
84 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85 where
86 D: serde::Deserializer<'de>,
87 {
88 deserializer.deserialize_str(FromStrVisitor::new())
89 }
90}
91
92bitwarden_ffi::impl_wire_string!(
93 SealedOpenOrgInviteData,
94 parse = |wire: String| wire.parse(),
95 format = String::from,
96);
97
98#[cfg(test)]
99mod tests {
100 use bitwarden_encoding::B64Url;
101
102 use super::*;
103 use crate::registration::open_org_invite_crypto::{OpenOrgInvite, SealedOpenOrgInviteData};
104
105 fn sample_input() -> OpenOrgInvite {
106 OpenOrgInvite {
107 organization_id: "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".to_string(),
108 invite_link_code: "abcd1234efgh5678".to_string(),
109 invite_secret: "raw-invite-secret-material-base64url".to_string(),
110 }
111 }
112
113 #[test]
114 fn sealed_data_wire_is_valid_base64url() {
115 let (sealed_data, _) =
116 SealedOpenOrgInviteData::seal(sample_input()).expect("seal should succeed");
117
118 let wire = String::from(&sealed_data);
119 let decoded =
120 B64Url::try_from(wire.as_str()).expect("sealed_data wire must be valid base64url");
121 assert_eq!(B64Url::from(decoded.as_bytes()).to_string(), wire);
122 }
123
124 #[test]
125 fn parse_rejects_truncated_wire() {
126 let (sealed_data, _) =
127 SealedOpenOrgInviteData::seal(sample_input()).expect("seal should succeed");
128
129 let mut wire = String::from(&sealed_data);
130 wire.truncate(wire.len() / 2);
131
132 let err = wire
133 .parse::<SealedOpenOrgInviteData>()
134 .expect_err("truncated wire must be rejected at parse time");
135 assert!(matches!(err, SealedOpenOrgInviteDataError::Malformed));
136 }
137
138 #[test]
139 fn parse_rejects_malformed_base64url() {
140 let err = "not-valid-base64url!"
141 .parse::<SealedOpenOrgInviteData>()
142 .expect_err("malformed base64url must be rejected at parse time");
143 assert!(matches!(err, SealedOpenOrgInviteDataError::Malformed));
144
145 assert!(B64Url::try_from("not-valid-base64url!").is_err());
146 }
147
148 #[test]
149 fn parse_rejects_valid_cbor_with_bad_key_envelope_bytes() {
150 #[derive(serde::Serialize)]
153 struct FakeWire<'a> {
154 #[serde(rename = "d", with = "serde_bytes")]
155 d: &'a [u8],
156 #[serde(rename = "k", with = "serde_bytes")]
157 k: &'a [u8],
158 }
159 let fake = FakeWire {
160 d: &[1, 2, 3, 4], k: &[0xff, 0xff, 0xff], };
163 let mut buf = Vec::new();
164 ciborium::ser::into_writer(&fake, &mut buf).unwrap();
165 let wire = B64Url::from(buf).to_string();
166
167 let err = wire
168 .parse::<SealedOpenOrgInviteData>()
169 .expect_err("bad key-envelope bytes must be rejected at parse time");
170 assert!(matches!(err, SealedOpenOrgInviteDataError::Malformed));
171 }
172}