1
//! Keyring migration support for legacy formats
2

            
3
use std::path::{Path, PathBuf};
4

            
5
use oo7::{Secret, file::UnlockedKeyring};
6

            
7
use crate::error::Error;
8

            
9
/// Returns the stamp file path for a migrated keyring file.
10
4
pub fn stamp_path(path: &Path) -> PathBuf {
11
4
    let mut stamped = path.as_os_str().to_owned();
12
4
    stamped.push(".migrated");
13
4
    PathBuf::from(stamped)
14
}
15

            
16
/// Pending keyring migration
17
#[derive(Clone)]
18
pub enum PendingMigration {
19
    /// Legacy v0 keyring format
20
    V0 {
21
        name: String,
22
        path: PathBuf,
23
        label: String,
24
        alias: String,
25
    },
26
    /// KWallet keyring format
27
    #[cfg(feature = "kwallet_migration")]
28
    KWallet {
29
        name: String,
30
        path: PathBuf,
31
        label: String,
32
        alias: String,
33
    },
34
}
35

            
36
impl PendingMigration {
37
    /// Attempt to migrate this keyring with the provided secret
38
4
    pub async fn migrate(
39
        &self,
40
        data_dir: &PathBuf,
41
        secret: Option<&Secret>,
42
    ) -> Result<UnlockedKeyring, Error> {
43
        match self {
44
4
            Self::V0 { path, name, .. } => {
45
8
                tracing::debug!("Migrating v0 keyring: {}", name);
46

            
47
16
                let unlocked = UnlockedKeyring::open_at(data_dir, name, secret.cloned()).await?;
48

            
49
                // Write migrated keyring
50
12
                unlocked.write().await?;
51
4
                tracing::info!("Wrote migrated keyring '{}' to disk", name);
52

            
53
12
                if let Err(e) = tokio::fs::write(stamp_path(path), b"").await {
54
                    tracing::warn!("Failed to write migration stamp for {:?}: {}", path, e);
55
                }
56

            
57
8
                tracing::info!("Successfully migrated v0 keyring '{}'", name);
58
4
                Ok(unlocked)
59
            }
60
            #[cfg(feature = "kwallet_migration")]
61
            Self::KWallet { path, name, .. } => {
62
                tracing::debug!("Migrating KWallet keyring: {}", name);
63

            
64
                let secret = secret.ok_or_else(|| {
65
                    Error::IO(std::io::Error::other("KWallet migration requires a secret"))
66
                })?;
67

            
68
                // Parse KWallet file in blocking task
69
                let path_clone = path.clone();
70
                let password = secret.to_vec();
71
                let wallet = tokio::task::spawn_blocking(move || {
72
                    kwallet_parser::KWalletFile::open(&path_clone, &password)
73
                })
74
                .await
75
                .map_err(|e| {
76
                    Error::IO(std::io::Error::other(format!("Task join error: {}", e)))
77
                })??;
78

            
79
                tracing::info!("Parsed KWallet file '{}'", name);
80

            
81
                // Create new oo7 keyring
82
                let unlocked =
83
                    UnlockedKeyring::open_at(data_dir, name, Some(secret.clone())).await?;
84

            
85
                // Convert KWallet entries to oo7 items
86
                let mut items = Vec::new();
87
                for (folder_name, folder) in wallet.wallet() {
88
                    for (entry_key, entry) in folder {
89
                        match kwallet_parser::convert_entry(folder_name, entry_key, entry) {
90
                            Ok(ss_entry) => {
91
                                items.push((
92
                                    ss_entry.label().to_owned(),
93
                                    ss_entry.attributes().to_owned(),
94
                                    Secret::blob(ss_entry.secret()),
95
                                    true,
96
                                ));
97
                            }
98
                            Err(e) => {
99
                                tracing::warn!(
100
                                    "Skipping entry {}/{}: {}",
101
                                    folder_name,
102
                                    entry_key,
103
                                    e
104
                                );
105
                            }
106
                        }
107
                    }
108
                }
109
                unlocked.create_items(items).await?;
110

            
111
                tracing::info!("Migrated KWallet entries to oo7 format for '{}'", name);
112

            
113
                if let Err(e) = tokio::fs::write(stamp_path(path), b"").await {
114
                    tracing::warn!("Failed to write migration stamp for {:?}: {}", path, e);
115
                }
116

            
117
                tracing::info!("Successfully migrated KWallet keyring '{}'", name);
118
                Ok(unlocked)
119
            }
120
        }
121
    }
122

            
123
5
    pub fn name(&self) -> &str {
124
        match self {
125
4
            Self::V0 { name, .. } => name,
126
            #[cfg(feature = "kwallet_migration")]
127
            Self::KWallet { name, .. } => name,
128
        }
129
    }
130

            
131
4
    pub fn label(&self) -> &str {
132
        match self {
133
4
            Self::V0 { label, .. } => label,
134
            #[cfg(feature = "kwallet_migration")]
135
            Self::KWallet { label, .. } => label,
136
        }
137
    }
138

            
139
4
    pub fn alias(&self) -> &str {
140
        match self {
141
4
            Self::V0 { alias, .. } => alias,
142
            #[cfg(feature = "kwallet_migration")]
143
            Self::KWallet { alias, .. } => alias,
144
        }
145
    }
146

            
147
    pub fn path(&self) -> &PathBuf {
148
        match self {
149
            Self::V0 { path, .. } => path,
150
            #[cfg(feature = "kwallet_migration")]
151
            Self::KWallet { path, .. } => path,
152
        }
153
    }
154
}