1
//! Keyring migration support for legacy formats
2

            
3
use std::path::PathBuf;
4

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

            
7
use crate::error::Error;
8

            
9
/// Pending keyring migration
10
#[derive(Clone)]
11
pub enum PendingMigration {
12
    /// Legacy v0 keyring format
13
    V0 {
14
        name: String,
15
        path: PathBuf,
16
        label: String,
17
        alias: String,
18
    },
19
    /// KWallet keyring format
20
    #[cfg(feature = "kwallet_migration")]
21
    KWallet {
22
        name: String,
23
        path: PathBuf,
24
        label: String,
25
        alias: String,
26
    },
27
}
28

            
29
impl PendingMigration {
30
    /// Attempt to migrate this keyring with the provided secret
31
4
    pub async fn migrate(
32
        &self,
33
        data_dir: &PathBuf,
34
        secret: &Secret,
35
    ) -> Result<UnlockedKeyring, Error> {
36
        match self {
37
4
            Self::V0 { path, name, .. } => {
38
8
                tracing::debug!("Migrating v0 keyring: {}", name);
39

            
40
12
                let unlocked = UnlockedKeyring::open_at(data_dir, name, secret.clone()).await?;
41

            
42
                // Write migrated keyring
43
12
                unlocked.write().await?;
44
4
                tracing::info!("Wrote migrated keyring '{}' to disk", name);
45

            
46
                // Cleanup old file
47
12
                if let Err(e) = tokio::fs::remove_file(path).await {
48
                    tracing::warn!("Failed to remove v0 keyring at {:?}: {}", path, e);
49
                } else {
50
8
                    tracing::info!("Removed v0 keyring file at {:?}", path);
51
                }
52

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

            
60
                // Parse KWallet file in blocking task
61
                let path_clone = path.clone();
62
                let password = secret.to_vec();
63
                let wallet = tokio::task::spawn_blocking(move || {
64
                    kwallet_parser::KWalletFile::open(&path_clone, &password)
65
                })
66
                .await
67
                .map_err(|e| {
68
                    Error::IO(std::io::Error::other(format!("Task join error: {}", e)))
69
                })??;
70

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

            
73
                // Create new oo7 keyring
74
                let unlocked = UnlockedKeyring::open_at(data_dir, name, secret.clone()).await?;
75

            
76
                // Convert KWallet entries to oo7 items
77
                let mut items = Vec::new();
78
                for (folder_name, folder) in wallet.wallet() {
79
                    for (entry_key, entry) in folder {
80
                        match kwallet_parser::convert_entry(folder_name, entry_key, entry) {
81
                            Ok(ss_entry) => {
82
                                items.push((
83
                                    ss_entry.label().to_owned(),
84
                                    ss_entry.attributes().to_owned(),
85
                                    Secret::blob(ss_entry.secret()),
86
                                    true,
87
                                ));
88
                            }
89
                            Err(e) => {
90
                                tracing::warn!(
91
                                    "Skipping entry {}/{}: {}",
92
                                    folder_name,
93
                                    entry_key,
94
                                    e
95
                                );
96
                            }
97
                        }
98
                    }
99
                }
100
                unlocked.create_items(items).await?;
101

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

            
104
                // Cleanup old files
105
                if let Err(e) = tokio::fs::remove_file(path).await {
106
                    tracing::warn!("Failed to remove KWallet file at {:?}: {}", path, e);
107
                } else {
108
                    tracing::info!("Removed KWallet file at {:?}", path);
109
                }
110

            
111
                // Try to remove salt file if it exists
112
                let salt_path = path.with_extension("salt");
113
                if salt_path.exists() {
114
                    if let Err(e) = tokio::fs::remove_file(&salt_path).await {
115
                        tracing::warn!(
116
                            "Failed to remove KWallet salt file at {:?}: {}",
117
                            salt_path,
118
                            e
119
                        );
120
                    } else {
121
                        tracing::info!("Removed KWallet salt file at {:?}", salt_path);
122
                    }
123
                }
124

            
125
                tracing::info!("Successfully migrated KWallet keyring '{}'", name);
126
                Ok(unlocked)
127
            }
128
        }
129
    }
130

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

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

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

            
155
    pub fn path(&self) -> &PathBuf {
156
        match self {
157
            Self::V0 { path, .. } => path,
158
            #[cfg(feature = "kwallet_migration")]
159
            Self::KWallet { path, .. } => path,
160
        }
161
    }
162
}