kdbx_rs/xml/
serialize.rs

1use super::decoders::{encode_datetime, encode_uuid};
2use crate::database::{Database, Entry, Field, Group, MemoryProtection, Meta, Times, Value};
3use base64::prelude::{Engine, BASE64_STANDARD};
4use cipher::StreamCipher;
5use std::io::Write;
6use thiserror::Error;
7use xml::writer::events::XmlEvent;
8use xml::writer::EventWriter as XmlWriter;
9
10#[derive(Debug, Error)]
11/// Failures to write an XML file
12pub enum Error {
13    /// Underlying XML writer had an error
14    #[error("Could not write XML: {0}")]
15    Xml(#[from] xml::writer::Error),
16    /// Failure of stream cipher when encrypting a value
17    #[error("Error encountered encrypting with a stream cipher: {0}")]
18    Cipher(String),
19}
20
21type Result<T> = std::result::Result<T, Error>;
22
23fn write_bool_tag<W: Write>(writer: &mut XmlWriter<W>, name: &str, value: bool) -> Result<()> {
24    writer.write(XmlEvent::start_element(name))?;
25    writer.write(XmlEvent::characters(if value { "True" } else { "False" }))?;
26    writer.write(XmlEvent::end_element())?;
27    Ok(())
28}
29
30fn write_string_tag<W: Write, S: AsRef<str>>(
31    writer: &mut XmlWriter<W>,
32    name: &str,
33    value: S,
34) -> Result<()> {
35    writer.write(XmlEvent::start_element(name))?;
36    writer.write(XmlEvent::characters(value.as_ref()))?;
37    writer.write(XmlEvent::end_element())?;
38    Ok(())
39}
40
41fn write_field<W: Write, S: StreamCipher + ?Sized>(
42    writer: &mut XmlWriter<W>,
43    wrapper: &str,
44    field: &Field,
45    stream_cipher: &mut S,
46) -> Result<()> {
47    writer.write(XmlEvent::start_element(wrapper))?;
48    write_string_tag(writer, "Key", &field.key)?;
49    match &field.value {
50        Value::Protected(v) => {
51            writer.write(XmlEvent::start_element("Value").attr("Protected", "True"))?;
52            let mut encrypt_buf = v.clone().into_bytes();
53            stream_cipher
54                .try_apply_keystream(&mut encrypt_buf)
55                .map_err(|e| Error::Cipher(format!("Encryption cipher failed: {}", e)))?;
56            let encrypted = BASE64_STANDARD.encode(&encrypt_buf);
57            writer.write(XmlEvent::characters(&encrypted))?;
58            writer.write(XmlEvent::end_element())?;
59        }
60        Value::Standard(v) => write_string_tag(writer, "Value", v)?,
61        Value::Empty | Value::ProtectEmpty => {
62            writer.write(XmlEvent::start_element("Value"))?;
63            writer.write(XmlEvent::end_element())?;
64        }
65    }
66    writer.write(XmlEvent::end_element())?;
67    Ok(())
68}
69
70fn write_memory_protection<W: Write>(
71    writer: &mut XmlWriter<W>,
72    protection: &MemoryProtection,
73) -> Result<()> {
74    writer.write(XmlEvent::start_element("MemoryProtection"))?;
75    write_bool_tag(writer, "ProtectUserName", protection.protect_user_name)?;
76    write_bool_tag(writer, "ProtectPassword", protection.protect_password)?;
77    write_bool_tag(writer, "ProtectTitle", protection.protect_title)?;
78    write_bool_tag(writer, "ProtectNotes", protection.protect_notes)?;
79    write_bool_tag(writer, "ProtectURL", protection.protect_url)?;
80    writer.write(XmlEvent::end_element())?;
81    Ok(())
82}
83
84fn write_meta<W: Write, S: StreamCipher + ?Sized>(
85    writer: &mut XmlWriter<W>,
86    meta: &Meta,
87    stream_cipher: &mut S,
88) -> Result<()> {
89    writer.write(XmlEvent::start_element("Meta"))?;
90    write_string_tag(writer, "Generator", "kdbx-rs")?;
91    write_string_tag(writer, "DatabaseName", &meta.database_name)?;
92    write_string_tag(writer, "DatabaseDescription", &meta.database_description)?;
93    writer.write(XmlEvent::start_element("CustomData"))?;
94    for field in &meta.custom_data {
95        write_field(writer, "Item", field, stream_cipher)?;
96    }
97    writer.write(XmlEvent::end_element())?;
98    write_memory_protection(writer, &meta.memory_protection)?;
99    writer.write(XmlEvent::end_element())?;
100    Ok(())
101}
102
103fn write_times<W: Write>(writer: &mut XmlWriter<W>, times: &Times) -> Result<()> {
104    writer.write(XmlEvent::start_element("Times"))?;
105    write_string_tag(
106        writer,
107        "LastModificationTime",
108        encode_datetime(times.last_modification_time),
109    )?;
110    write_string_tag(writer, "CreationTime", encode_datetime(times.creation_time))?;
111    write_string_tag(
112        writer,
113        "LastAccessTime",
114        encode_datetime(times.last_access_time),
115    )?;
116    write_string_tag(
117        writer,
118        "LocationChanged",
119        encode_datetime(times.location_changed),
120    )?;
121    write_string_tag(writer, "ExpiryTime", encode_datetime(times.expiry_time))?;
122    write_string_tag(writer, "UsageCount", times.usage_count.to_string())?;
123    write_bool_tag(writer, "Expires", times.expires)?;
124    writer.write(XmlEvent::end_element())?;
125    Ok(())
126}
127
128fn write_entry<W: Write, S: StreamCipher + ?Sized>(
129    writer: &mut XmlWriter<W>,
130    entry: &Entry,
131    stream_cipher: &mut S,
132) -> Result<()> {
133    writer.write(XmlEvent::start_element("Entry"))?;
134    write_string_tag(writer, "UUID", &encode_uuid(entry.uuid()))?;
135    write_times(writer, &entry.times)?;
136    for field in entry.fields() {
137        write_field(writer, "String", field, stream_cipher)?;
138    }
139    if !entry.history.is_empty() {
140        writer.write(XmlEvent::start_element("History"))?;
141        for old_entry in entry.history.entries() {
142            write_entry(writer, old_entry, stream_cipher)?;
143        }
144        writer.write(XmlEvent::end_element())?;
145    }
146    writer.write(XmlEvent::end_element())?;
147    Ok(())
148}
149
150fn write_group<W: Write, S: StreamCipher + ?Sized>(
151    writer: &mut XmlWriter<W>,
152    group: &Group,
153    stream_cipher: &mut S,
154) -> Result<()> {
155    writer.write(XmlEvent::start_element("Group"))?;
156    write_string_tag(writer, "UUID", encode_uuid(group.uuid()))?;
157    write_string_tag(writer, "Name", group.name())?;
158    write_times(writer, &group.times)?;
159    for entry in group.entries() {
160        write_entry(writer, entry, stream_cipher)?;
161    }
162    for group in group.groups() {
163        write_group(writer, group, stream_cipher)?;
164    }
165    writer.write(XmlEvent::end_element())?;
166    Ok(())
167}
168
169/// Write the decrypted XML for a database to a file
170///
171/// If you need to obtain a stream cipher, try
172/// [`InnerStreamCipherAlgorithm::stream_cipher`][crate::binary::InnerStreamCipherAlgorithm#stream_cipher]
173/// if the XML contains encrypted data, or [`utils::NullStreamCipher`][crate::utils::NullStreamCipher]
174/// if it does not (such as an export from the official client).
175pub fn write_xml<W: Write, S: StreamCipher + ?Sized>(
176    output: W,
177    database: &Database,
178    stream_cipher: &mut S,
179) -> Result<()> {
180    let config = xml::EmitterConfig::default()
181        .perform_indent(true)
182        .indent_string("\t");
183    let mut writer = xml::EventWriter::new_with_config(output, config);
184    writer.write(XmlEvent::start_element("KeePassFile"))?;
185    write_meta(&mut writer, &database.meta, stream_cipher)?;
186    writer.write(XmlEvent::start_element("Root"))?;
187    for group in &database.groups {
188        write_group(&mut writer, group, stream_cipher)?;
189    }
190    writer.write(XmlEvent::end_element())?;
191    writer.write(XmlEvent::end_element())?;
192    Ok(())
193}