1use super::Complete;
2use pijul_config as config;
3
4use libpijul::key::{PublicKey, SecretKey};
5
6use std::fs;
7use std::io::{Read, Write};
8use std::path::PathBuf;
9
10use anyhow::{bail, Context};
11use log::debug;
12use thiserror::Error;
13
14const FIRST_IDENTITY_MESSAGE: &str = "It doesn't look like you have any identities configured!
15Each author in Pijul is identified by a unique key to provide greater security & flexibility over names/emails.
16To make sure humans (including you!) can easily identify these keys, we need a few personal details.
17For more information see https://pijul.org/manual/keys.html";
18
19const MIGRATE_IDENTITY_MESSAGE: &str =
20 "It seems you have configured an identity in an older version of Pijul, which uses an older identity format!
21Please take a moment to confirm your details are correct.";
22
23const MISMATCHED_KEYS_MESSAGE: &str = "It seems the keys on your system are mismatched!
24This is most likely the result of data corruption, please check your drive and try again.";
25
26#[derive(Error, Debug)]
27pub enum IdentityParseError {
28 #[error("Mismatching keys")]
29 MismatchingKeys,
30 #[error("Could not find secret key at path {0}")]
31 NoSecretKey(PathBuf),
32 #[error(transparent)]
33 Other(#[from] anyhow::Error),
34}
35
36pub async fn fix_identities() -> Result<(), anyhow::Error> {
43 let mut dir = config::global_config_dir().unwrap();
44 dir.push("identities");
45 std::fs::create_dir_all(&dir)?;
46 dir.pop();
47
48 let identities = Complete::load_all()?;
49
50 if identities.is_empty() {
51 let extraction_result = Complete::from_old_format();
54
55 let mut stderr = std::io::stderr();
56
57 match extraction_result {
58 Ok(old_identity) => {
59 writeln!(stderr, "{MIGRATE_IDENTITY_MESSAGE}")?;
61
62 old_identity.clone().create(true).await?;
64
65 let identity_path = format!("identities/{}", &old_identity.public_key.key);
67
68 let paths_to_delete =
70 vec!["publickey.json", "secretkey.json", identity_path.as_str()];
71 for path in paths_to_delete {
72 let file_path = dir.join(path);
73 if file_path.exists() {
74 debug!("Deleting old file: {file_path:?}");
75 fs::remove_file(file_path)?;
76 } else {
77 debug!("Could not delete old file (path not found): {file_path:?}");
78 }
79 }
80 }
81 Err(e) => {
82 match e {
83 IdentityParseError::MismatchingKeys => {
84 bail!("User must repair broken keys before continuing");
85 }
86 IdentityParseError::NoSecretKey(_) => {
87 writeln!(stderr, "{FIRST_IDENTITY_MESSAGE}")?;
89 Complete::default()?.create(true).await?;
90 }
91 IdentityParseError::Other(err) => {
92 bail!(err);
93 }
94 }
95 }
96 }
97 }
98
99 for identity in Complete::load_all()? {
101 identity.valid_keys()?;
102 }
103
104 Ok(())
105}
106
107impl Complete {
108 fn valid_keys(&self) -> Result<bool, anyhow::Error> {
110 let public_key = &self.public_key;
111 let decryped_public_key = self.decrypt()?.0.public_key();
112
113 if public_key.key != decryped_public_key.key {
114 let mut stderr = std::io::stderr();
115 writeln!(stderr, "{MISMATCHED_KEYS_MESSAGE}")?;
116 writeln!(stderr, "Got the following public key signatures:")?;
117 writeln!(stderr, "Plaintext public key: {public_key:#?}")?;
118 writeln!(stderr, "Decrypted public key: {decryped_public_key:#?}")?;
119
120 return Ok(false);
121 }
122
123 Ok(true)
124 }
125
126 fn from_old_format() -> Result<Self, IdentityParseError> {
174 let config_dir = config::global_config_dir().unwrap();
175
176 let config_path = config_dir.join("config.toml");
177 let identities_path = config_dir.join("identities");
178 let public_key_path = config_dir.join("publickey.json");
179 let secret_key_path = config_dir.join("secretkey.json");
180
181 if !secret_key_path.exists() {
184 return Err(IdentityParseError::NoSecretKey(secret_key_path));
185 }
186 let mut secret_key_file =
192 fs::File::open(&secret_key_path).context("Failed to open secret key file")?;
193 let mut secret_key_text = String::new();
194 secret_key_file
195 .read_to_string(&mut secret_key_text)
196 .context("Failed to read secret key file")?;
197 let secret_key: SecretKey =
198 serde_json::from_str(&secret_key_text).context("Failed to parse secret key file")?;
199
200 let public_key: PublicKey = if public_key_path.exists() {
203 let mut public_key_file =
204 fs::File::open(&public_key_path).context("Failed to open public key file")?;
205 let mut public_key_text = String::new();
206 public_key_file
207 .read_to_string(&mut public_key_text)
208 .context("Failed to read public key file")?;
209
210 serde_json::from_str(&public_key_text).context("Failed to parse public key file")?
211 } else {
212 return Err(IdentityParseError::Other(anyhow::anyhow!(
213 "Public key does not exist!"
214 )));
215 };
216
217 let identity: Option<Complete> = if identities_path.exists() {
219 if identities_path.is_dir() {
220 let identities_iter =
221 fs::read_dir(identities_path).context("Failed to read identities directory")?;
222 let mut identities: Vec<Complete> = vec![];
223
224 for dir_entry in identities_iter {
226 let path = dir_entry.unwrap().path();
227
228 if path.is_file() {
229 let mut identity_file =
232 fs::File::open(&path).context("Failed to open identity file")?;
233 let mut identity_text = String::new();
234 identity_file
235 .read_to_string(&mut identity_text)
236 .context("Failed to read identity file")?;
237 let deserialization_result: Result<Complete, _> =
238 serde_json::from_str(&identity_text);
239
240 if deserialization_result.is_ok() {
241 identities.push(
242 deserialization_result
243 .context("Failed to deserialize identity file")?,
244 );
245 }
246 }
247 }
248
249 if identities.len() == 1 {
250 Some(identities[0].clone())
251 } else {
252 None
253 }
254 } else {
255 None
256 }
257 } else {
258 None
259 };
260
261 let config: super::Config = if config_path.exists() {
262 let mut config_file =
263 fs::File::open(&config_path).context("Failed to open config file")?;
264 let mut config_text = String::new();
265 config_file
266 .read_to_string(&mut config_text)
267 .context("Failed to read config file")?;
268
269 let config_data: config::Global =
270 toml::from_str(&config_text).context("Failed to parse config file")?;
271
272 super::Config {
273 key_path: config_data.author.key_path.clone(),
274 author: config_data.author,
275 }
276 } else {
277 let mut author = config::Author::default();
278 author.username = identity
279 .as_ref()
280 .map_or_else(String::new, |x| x.config.author.username.clone());
281
282 super::Config {
283 key_path: None,
284 author,
285 }
286 };
287
288 let identity = Self::new(
289 String::from("default"),
290 config,
291 public_key,
292 Some(super::Credentials::from(secret_key)),
293 );
294
295 if identity.valid_keys()? {
296 Ok(identity)
297 } else {
298 Err(IdentityParseError::MismatchingKeys)
299 }
300 }
301}