bitwarden_exporters/
lib.rs

1use std::fmt;
2
3use bitwarden_vault::{
4    CipherRepromptType, CipherView, Fido2CredentialFullView, LoginUriView, UriMatchType,
5};
6use chrono::{DateTime, Utc};
7use uuid::Uuid;
8
9#[cfg(feature = "uniffi")]
10uniffi::setup_scaffolding!();
11#[cfg(feature = "uniffi")]
12mod uniffi_support;
13
14mod csv;
15mod cxf;
16pub use cxf::Account;
17mod encrypted_json;
18mod exporter_client;
19mod json;
20mod models;
21pub use exporter_client::{ExporterClient, ExporterClientExt};
22mod error;
23mod export;
24pub use error::ExportError;
25
26#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
27#[cfg_attr(
28    feature = "wasm",
29    derive(serde::Serialize, serde::Deserialize, tsify_next::Tsify),
30    tsify(into_wasm_abi, from_wasm_abi)
31)]
32pub enum ExportFormat {
33    Csv,
34    Json,
35    EncryptedJson { password: String },
36}
37
38/// Export representation of a Bitwarden folder.
39///
40/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
41/// that is not tied to the internal vault models. We may revisit this in the future.
42pub struct Folder {
43    pub id: Uuid,
44    pub name: String,
45}
46
47/// Export representation of a Bitwarden cipher.
48///
49/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
50/// that is not tied to the internal vault models. We may revisit this in the future.
51#[derive(Clone)]
52pub struct Cipher {
53    pub id: Uuid,
54    pub folder_id: Option<Uuid>,
55
56    pub name: String,
57    pub notes: Option<String>,
58
59    pub r#type: CipherType,
60
61    pub favorite: bool,
62    pub reprompt: u8,
63
64    pub fields: Vec<Field>,
65
66    pub revision_date: DateTime<Utc>,
67    pub creation_date: DateTime<Utc>,
68    pub deleted_date: Option<DateTime<Utc>>,
69}
70
71/// Import representation of a Bitwarden cipher.
72///
73/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
74/// that is not tied to the internal vault models. We may revisit this in the future.
75#[derive(Clone)]
76pub struct ImportingCipher {
77    pub folder_id: Option<Uuid>,
78
79    pub name: String,
80    pub notes: Option<String>,
81
82    pub r#type: CipherType,
83
84    pub favorite: bool,
85    pub reprompt: u8,
86
87    pub fields: Vec<Field>,
88
89    pub revision_date: DateTime<Utc>,
90    pub creation_date: DateTime<Utc>,
91    pub deleted_date: Option<DateTime<Utc>>,
92}
93
94impl From<ImportingCipher> for CipherView {
95    fn from(value: ImportingCipher) -> Self {
96        let login = match value.r#type {
97            CipherType::Login(login) => {
98                let l: Vec<LoginUriView> = login
99                    .login_uris
100                    .into_iter()
101                    .map(LoginUriView::from)
102                    .collect();
103
104                Some(bitwarden_vault::LoginView {
105                    username: login.username,
106                    password: login.password,
107                    password_revision_date: None,
108                    uris: if l.is_empty() { None } else { Some(l) },
109                    totp: login.totp,
110                    autofill_on_page_load: None,
111                    fido2_credentials: None,
112                })
113            }
114            _ => None,
115        };
116
117        Self {
118            id: None,
119            organization_id: None,
120            folder_id: value.folder_id,
121            collection_ids: vec![],
122            key: None,
123            name: value.name,
124            notes: None,
125            r#type: bitwarden_vault::CipherType::Login,
126            login,
127            identity: None,
128            card: None,
129            secure_note: None,
130            ssh_key: None,
131            favorite: value.favorite,
132            reprompt: CipherRepromptType::None,
133            organization_use_totp: true,
134            edit: true,
135            permissions: None,
136            view_password: true,
137            local_data: None,
138            attachments: None,
139            fields: None,
140            password_history: None,
141            creation_date: value.creation_date,
142            deleted_date: None,
143            revision_date: value.revision_date,
144        }
145    }
146}
147
148impl From<LoginUri> for bitwarden_vault::LoginUriView {
149    fn from(value: LoginUri) -> Self {
150        Self {
151            uri: value.uri,
152            r#match: value.r#match.and_then(|m| match m {
153                0 => Some(UriMatchType::Domain),
154                1 => Some(UriMatchType::Host),
155                2 => Some(UriMatchType::StartsWith),
156                3 => Some(UriMatchType::Exact),
157                4 => Some(UriMatchType::RegularExpression),
158                5 => Some(UriMatchType::Never),
159                _ => None,
160            }),
161            uri_checksum: None,
162        }
163    }
164}
165
166#[derive(Clone)]
167pub struct Field {
168    pub name: Option<String>,
169    pub value: Option<String>,
170    pub r#type: u8,
171    pub linked_id: Option<u32>,
172}
173
174#[derive(Clone)]
175pub enum CipherType {
176    Login(Box<Login>),
177    SecureNote(Box<SecureNote>),
178    Card(Box<Card>),
179    Identity(Box<Identity>),
180    SshKey(Box<SshKey>),
181}
182
183impl fmt::Display for CipherType {
184    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185        match self {
186            CipherType::Login(_) => write!(f, "login"),
187            CipherType::SecureNote(_) => write!(f, "note"),
188            CipherType::Card(_) => write!(f, "card"),
189            CipherType::Identity(_) => write!(f, "identity"),
190            CipherType::SshKey(_) => write!(f, "ssh_key"),
191        }
192    }
193}
194
195#[derive(Clone)]
196pub struct Login {
197    pub username: Option<String>,
198    pub password: Option<String>,
199    pub login_uris: Vec<LoginUri>,
200    pub totp: Option<String>,
201
202    pub fido2_credentials: Option<Vec<Fido2Credential>>,
203}
204
205#[derive(Clone)]
206pub struct LoginUri {
207    pub uri: Option<String>,
208    pub r#match: Option<u8>,
209}
210
211#[derive(Clone)]
212pub struct Fido2Credential {
213    pub credential_id: String,
214    pub key_type: String,
215    pub key_algorithm: String,
216    pub key_curve: String,
217    pub key_value: String,
218    pub rp_id: String,
219    pub user_handle: Option<String>,
220    pub user_name: Option<String>,
221    pub counter: u32,
222    pub rp_name: Option<String>,
223    pub user_display_name: Option<String>,
224    pub discoverable: String,
225    pub creation_date: DateTime<Utc>,
226}
227
228impl From<Fido2Credential> for Fido2CredentialFullView {
229    fn from(value: Fido2Credential) -> Self {
230        Fido2CredentialFullView {
231            credential_id: value.credential_id,
232            key_type: value.key_type,
233            key_algorithm: value.key_algorithm,
234            key_curve: value.key_curve,
235            key_value: value.key_value,
236            rp_id: value.rp_id,
237            user_handle: value.user_handle,
238            user_name: value.user_name,
239            counter: value.counter.to_string(),
240            rp_name: value.rp_name,
241            user_display_name: value.user_display_name,
242            discoverable: value.discoverable,
243            creation_date: value.creation_date,
244        }
245    }
246}
247
248#[derive(Clone)]
249pub struct Card {
250    pub cardholder_name: Option<String>,
251    pub exp_month: Option<String>,
252    pub exp_year: Option<String>,
253    pub code: Option<String>,
254    pub brand: Option<String>,
255    pub number: Option<String>,
256}
257
258#[derive(Clone)]
259pub struct SecureNote {
260    pub r#type: SecureNoteType,
261}
262
263#[derive(Clone)]
264pub enum SecureNoteType {
265    Generic = 0,
266}
267
268#[derive(Clone)]
269pub struct Identity {
270    pub title: Option<String>,
271    pub first_name: Option<String>,
272    pub middle_name: Option<String>,
273    pub last_name: Option<String>,
274    pub address1: Option<String>,
275    pub address2: Option<String>,
276    pub address3: Option<String>,
277    pub city: Option<String>,
278    pub state: Option<String>,
279    pub postal_code: Option<String>,
280    pub country: Option<String>,
281    pub company: Option<String>,
282    pub email: Option<String>,
283    pub phone: Option<String>,
284    pub ssn: Option<String>,
285    pub username: Option<String>,
286    pub passport_number: Option<String>,
287    pub license_number: Option<String>,
288}
289
290#[derive(Clone)]
291pub struct SshKey {
292    /// [OpenSSH private key](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key), in PEM encoding.
293    pub private_key: String,
294    /// Ssh public key (ed25519/rsa) according to [RFC4253](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6)
295    pub public_key: String,
296    /// SSH fingerprint using SHA256 in the format: `SHA256:BASE64_ENCODED_FINGERPRINT`
297    pub fingerprint: String,
298}