Skip to main content

bitwarden_importers/importers/onepassword/access/
wire.rs

1//! serde DTOs for every 1Password endpoint.
2//!
3//! These carry only the fields the client reads. serde ignores everything else on the wire, so the
4//! structs stay small while remaining forward compatible with the full server responses.
5
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9use zeroize::Zeroizing;
10
11/// The JSON "opdata" envelope as it appears on the wire.
12///
13/// It is also serialized back to the server as the request body of the encrypted POST endpoints.
14#[derive(Debug, Deserialize, Serialize)]
15pub(super) struct EncryptedEnvelope {
16    pub kid: String,
17    pub enc: String,
18    pub cty: String,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub iv: Option<String>,
21    pub data: String,
22}
23
24/// An RSA private key JWK as it appears on the wire.
25///
26/// Extra JWK members (`dp`, `dq`, `qi`, `alg`, `kty`, `ext`) are ignored: the CRT values are
27/// recomputed from `p` and `q` when the key is built.
28#[derive(Deserialize)]
29pub(super) struct RsaKeyJwk {
30    pub kid: String,
31    pub e: String,
32    pub n: String,
33    pub p: String,
34    pub q: String,
35    pub d: String,
36}
37
38/// The decrypted AES key JSON.
39#[derive(Deserialize)]
40pub(super) struct AesKeyJson {
41    pub kid: String,
42    pub k: String,
43}
44
45/// The `v1/account/keysets` payload.
46#[derive(Debug, Deserialize)]
47pub(super) struct KeysetsInfo {
48    pub keysets: Vec<KeysetInfo>,
49}
50
51/// A single keyset.
52#[derive(Debug, Deserialize)]
53pub(super) struct KeysetInfo {
54    pub uuid: String,
55    #[serde(default, rename = "encryptedBy")]
56    pub encrypted_by: String,
57    pub sn: i64,
58    #[serde(rename = "encSymKey")]
59    pub enc_sym_key: KeyDerivationInfo,
60    #[serde(rename = "encPriKey")]
61    pub enc_pri_key: EncryptedEnvelope,
62}
63
64/// An encrypted symmetric key envelope that additionally carries the KDF parameters for the master
65/// keyset (`alg`/`p2s`/`p2c`).
66#[derive(Debug, Deserialize)]
67pub(super) struct KeyDerivationInfo {
68    pub kid: String,
69    pub enc: String,
70    pub cty: String,
71    #[serde(default)]
72    pub iv: Option<String>,
73    pub data: String,
74    #[serde(default)]
75    pub alg: Option<String>,
76    #[serde(default)]
77    pub p2s: Option<String>,
78    #[serde(default)]
79    pub p2c: u32,
80}
81
82impl KeyDerivationInfo {
83    /// The envelope half, without the KDF parameters.
84    pub(super) fn envelope(&self) -> EncryptedEnvelope {
85        EncryptedEnvelope {
86            kid: self.kid.clone(),
87            enc: self.enc.clone(),
88            cty: self.cty.clone(),
89            iv: self.iv.clone(),
90            data: self.data.clone(),
91        }
92    }
93}
94
95/// Response from `v2/auth/methods`.
96///
97/// `userUuid` and `signInAddress` are only needed for an SSO login, and the server may leave them
98/// out.
99#[derive(Debug, Deserialize)]
100pub(super) struct LoginInfo {
101    #[serde(rename = "userUuid")]
102    pub user_uuid: Option<String>,
103    #[serde(rename = "signInAddress")]
104    pub sign_in_address: Option<String>,
105    #[serde(rename = "authMethods")]
106    pub auth_methods: Vec<AuthMethod>,
107}
108
109/// A single auth method offered for an account.
110#[derive(Debug, Deserialize)]
111pub(super) struct AuthMethod {
112    #[serde(rename = "type")]
113    pub kind: String,
114}
115
116/// Response from `v3/auth/sso/oidc/start`.
117#[derive(Debug, Deserialize)]
118pub(super) struct SsoLoginUrl {
119    #[serde(rename = "authRedirect")]
120    pub auth_redirect: String,
121}
122
123/// Response from `v3/auth/sso/oidc/verify`.
124///
125/// `type` is `device_not_enrolled` for a device the server has not seen, which every import is.
126#[derive(Debug, Deserialize)]
127pub(super) struct SsoSession {
128    #[serde(rename = "type")]
129    pub state: String,
130    pub user: SsoUser,
131    #[serde(rename = "ssoAuth")]
132    pub sso_auth: SsoAuth,
133}
134
135/// The user an SSO login authenticated.
136#[derive(Debug, Deserialize)]
137pub(super) struct SsoUser {
138    /// Goes into the header of every request that follows.
139    #[serde(rename = "sessionUuid")]
140    pub session_uuid: String,
141}
142
143/// What the identity provider login earned: a token to sign in with.
144#[derive(Debug, Deserialize)]
145pub(super) struct SsoAuth {
146    #[serde(rename = "signInTokenDetails")]
147    pub sign_in_token_details: SignInTokenDetails,
148}
149
150/// The sign-in token, a bearer credential for the login after the SSO step.
151#[derive(Deserialize)]
152pub(super) struct SignInTokenDetails {
153    pub token: Zeroizing<String>,
154}
155
156impl fmt::Debug for SignInTokenDetails {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.debug_struct("SignInTokenDetails").finish_non_exhaustive()
159    }
160}
161
162/// Response from `v3/auth/start`.
163///
164/// `status` drives the state machine: `ok` carries the SRP parameters, while
165/// `device-not-registered` and `device-deleted` ask the client to (re)authorize the device and
166/// retry.
167#[derive(Debug, Deserialize)]
168pub(super) struct NewSession {
169    pub status: String,
170    #[serde(rename = "sessionID")]
171    pub session_id: String,
172    #[serde(rename = "accountKeyFormat")]
173    pub key_format: Option<String>,
174    #[serde(rename = "accountKeyUuid")]
175    pub key_uuid: Option<String>,
176    #[serde(rename = "userAuth")]
177    pub auth: Option<UserAuth>,
178}
179
180/// The SRP parameters carried by a successful `NewSession`.
181#[derive(Debug, Deserialize)]
182pub(super) struct UserAuth {
183    pub method: String,
184    #[serde(rename = "alg")]
185    pub algorithm: String,
186    pub iterations: u32,
187    pub salt: String,
188}
189
190/// Response from `v1/device`, `v1/device/{uuid}/reauthorize` and the CPace `msgb` and `tagb`.
191#[derive(Debug, Deserialize)]
192pub(super) struct SuccessStatus {
193    pub success: i32,
194}
195
196/// Response from `v2/auth` (the SRP A -> B exchange).
197#[derive(Debug, Deserialize)]
198pub(super) struct AForB {
199    #[serde(rename = "userB")]
200    pub b: String,
201}
202
203/// Response from `v2/auth/confirm-key`.
204#[derive(Debug, Deserialize)]
205pub(super) struct ServerHash {
206    #[serde(rename = "serverVerifyHash")]
207    pub server_verify_hash: String,
208}
209
210/// Response from `v2/auth/complete` (decrypted).
211#[derive(Debug, Deserialize)]
212pub(super) struct AuthComplete {
213    pub mfa: Option<MfaInfo>,
214}
215
216/// The set of 2FA methods enabled for an account.
217///
218/// Only the enabled flags are modelled: TOTP is the one interactive method implemented, so the
219/// per-method parameters (WebAuthn challenge, Duo host, and so on) are not read.
220#[derive(Debug, Deserialize)]
221pub(super) struct MfaInfo {
222    #[serde(rename = "totp")]
223    pub google_auth: Option<BasicMfa>,
224    #[serde(rename = "webAuthn")]
225    pub web_authn: Option<BasicMfa>,
226    pub duo: Option<BasicMfa>,
227    #[serde(rename = "dsecret")]
228    pub remember_me: Option<BasicMfa>,
229}
230
231impl MfaInfo {
232    /// Whether TOTP (Google Authenticator) is enabled, the only interactive method supported.
233    pub(super) fn totp_enabled(&self) -> bool {
234        self.google_auth.as_ref().is_some_and(|f| f.enabled)
235    }
236
237    /// Names of the enabled 2FA methods, in the order 1Password reports them.
238    pub(super) fn enabled_methods(&self) -> Vec<&'static str> {
239        let mut methods = Vec::new();
240        for (factor, name) in [
241            (&self.google_auth, "TOTP"),
242            (&self.web_authn, "WebAuthn"),
243            (&self.duo, "Duo"),
244            (&self.remember_me, "remember-me"),
245        ] {
246            if factor.as_ref().is_some_and(|f| f.enabled) {
247                methods.push(name);
248            }
249        }
250        methods
251    }
252}
253
254/// The `{ "enabled": bool }` shared by every 2FA method entry.
255#[derive(Debug, Deserialize)]
256pub(super) struct BasicMfa {
257    pub enabled: bool,
258}
259
260/// Response from `v3/device/enrollments`.
261#[derive(Debug, Deserialize)]
262pub(super) struct SsoEnrollInfo {
263    #[serde(rename = "enrollmentUuid")]
264    pub enrollment_uuid: String,
265}
266
267/// Response from `v3/device/enrollments/status`.
268#[derive(Debug, Deserialize)]
269pub(super) struct SsoEnrollStatus {
270    pub status: String,
271}
272
273/// Response from `v3/device/enrollments/{uuid}/cpace/msga`.
274#[derive(Debug, Deserialize)]
275pub(super) struct CpaceMsgA {
276    pub msga: String,
277}
278
279/// The CPace message from the enrolled device, base64 encoded inside the `msga` response of
280/// `v3/device/enrollments/{uuid}/cpace/msga`.
281#[derive(Debug, Deserialize)]
282pub(super) struct MsgA {
283    pub ya: String,
284    pub ad: MsgAAd,
285}
286
287/// The associated data of a CPace message.
288///
289/// `session_id` is a JSON array of byte values, so an element above 255 fails to parse.
290#[derive(Debug, Deserialize)]
291pub(super) struct MsgAAd {
292    pub version: i32,
293    pub salt: String,
294    pub session_id: Vec<u8>,
295}
296
297/// Response from `v3/device/enrollments/{uuid}/cpace/taga`.
298#[derive(Debug, Deserialize)]
299pub(super) struct CpaceTagA {
300    pub taga: String,
301}
302
303/// Response from `v3/device/enrollments/{uuid}/share/credentials`.
304#[derive(Debug, Deserialize)]
305pub(super) struct SharedCredentials {
306    #[serde(rename = "encCreds")]
307    pub encrypted_credentials: EncryptedEnvelope,
308}
309
310/// The credentials the enrolled device hands over, decrypted from the `encCreds` of
311/// `v3/device/enrollments/{uuid}/share/credentials`.
312///
313/// Deliberately not `Debug`: it holds the SRP `x` and the account unlock key. The secrets are
314/// `Zeroizing` field by field, so a bundle that fails to parse halfway is wiped too.
315#[derive(Deserialize)]
316pub(super) struct CredentialBundle {
317    pub srpx: Zeroizing<String>,
318    pub auk: Auk,
319}
320
321/// The account unlock key in the shape of a JWK, of which only the key is read. Deliberately not
322/// `Debug`.
323#[derive(Deserialize)]
324pub(super) struct Auk {
325    pub k: Zeroizing<String>,
326}
327
328/// A server error body.
329#[derive(Debug, Deserialize)]
330pub(super) struct ErrorResponse {
331    #[serde(rename = "errorCode")]
332    pub code: i32,
333    #[serde(rename = "errorMessage")]
334    pub message: String,
335}
336
337/// A server failure body used by some endpoints instead of `Error`.
338#[derive(Debug, Deserialize)]
339pub(super) struct FailureReason {
340    pub reason: String,
341}
342
343/// Response from `v1/account` (decrypted). Only the vault list is used.
344#[derive(Debug, Deserialize)]
345pub(super) struct AccountInfo {
346    pub vaults: Vec<VaultInfo>,
347}
348
349/// A vault entry in the account info.
350#[derive(Debug, Deserialize)]
351pub(super) struct VaultInfo {
352    pub uuid: String,
353    #[serde(rename = "activeItemCount")]
354    pub active_item_count: Option<u32>,
355    #[serde(rename = "encAttrs")]
356    pub enc_attrs: EncryptedEnvelope,
357    pub access: Vec<VaultAccess>,
358}
359
360/// An access-control entry carrying the vault key encrypted for a key we may hold.
361#[derive(Debug, Deserialize)]
362pub(super) struct VaultAccess {
363    pub acl: i32,
364    #[serde(rename = "encVaultKey")]
365    pub enc_vault_key: EncryptedEnvelope,
366}
367
368/// Decrypted vault attributes.
369#[derive(Deserialize)]
370pub(super) struct VaultAttributes {
371    pub name: Option<String>,
372}
373
374/// A page of vault items. The last page is marked `batchComplete`.
375#[derive(Debug, Deserialize)]
376pub(super) struct VaultItemsBatch {
377    #[serde(rename = "contentVersion")]
378    pub version: i64,
379    #[serde(rename = "batchComplete")]
380    pub complete: bool,
381    pub items: Option<Vec<VaultItem>>,
382}
383
384/// A single encrypted vault item.
385#[derive(Debug, Deserialize)]
386pub(super) struct VaultItem {
387    pub uuid: String,
388    #[serde(rename = "templateUuid")]
389    pub template_uuid: String,
390    pub trashed: String,
391    #[serde(rename = "encOverview")]
392    pub enc_overview: EncryptedEnvelope,
393    #[serde(rename = "encDetails")]
394    pub enc_details: EncryptedEnvelope,
395}
396
397/// A decrypted item overview.
398#[derive(Deserialize)]
399pub struct VaultItemOverview {
400    pub title: Option<String>,
401    pub url: Option<String>,
402    #[serde(rename = "URLs")]
403    pub urls: Option<Vec<VaultItemUrl>>,
404    pub tags: Option<Vec<String>>,
405}
406
407/// A URL entry in an item overview.
408#[derive(Deserialize)]
409pub struct VaultItemUrl {
410    #[serde(rename = "u")]
411    pub url: Option<String>,
412}
413
414/// Decrypted item details.
415#[derive(Deserialize)]
416pub struct VaultItemDetails {
417    #[serde(rename = "notesPlain")]
418    pub note: Option<String>,
419    pub fields: Option<Vec<VaultItemField>>,
420    pub sections: Option<Vec<VaultItemSection>>,
421    /// The secret of a Password-category item, which carries no `fields`.
422    pub password: Option<String>,
423}
424
425/// A designation-based login field (username/password).
426#[derive(Deserialize)]
427pub struct VaultItemField {
428    pub designation: Option<String>,
429    pub value: Option<String>,
430    pub name: Option<String>,
431    /// `T` for text, `P` for password.
432    #[serde(rename = "type")]
433    pub kind: Option<String>,
434}
435
436/// A section of fields. Bitwarden custom fields are a flat list, so only the fields are read.
437#[derive(Deserialize)]
438pub struct VaultItemSection {
439    pub fields: Option<Vec<VaultItemSectionField>>,
440}
441
442/// A field inside a section. The value `v` can be any JSON type.
443#[derive(Deserialize)]
444pub struct VaultItemSectionField {
445    #[serde(rename = "n")]
446    pub id: Option<String>,
447    #[serde(rename = "t")]
448    pub name: Option<String>,
449    #[serde(rename = "v")]
450    pub value: Option<serde_json::Value>,
451    #[serde(rename = "k")]
452    pub kind: Option<String>,
453    #[serde(rename = "a")]
454    pub attributes: Option<VaultItemFieldAttributes>,
455}
456
457/// Extra attributes on a section field.
458#[derive(Deserialize)]
459pub struct VaultItemFieldAttributes {
460    #[serde(rename = "sshKeyAttributes")]
461    pub ssh_key: Option<SshKeyAttributes>,
462}
463
464/// The SSH key material carried on a `sshKey` field.
465#[derive(Deserialize)]
466pub struct SshKeyAttributes {
467    #[serde(rename = "privateKey")]
468    pub private_key: Option<String>,
469    #[serde(rename = "publicKey")]
470    pub public_key: Option<String>,
471    pub fingerprint: Option<String>,
472}