updater_signature.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use anyhow::Context;
  5. use base64::Engine;
  6. use minisign::{sign, KeyPair as KP, SecretKey, SecretKeyBox, SignatureBox};
  7. use std::{
  8. fs::{self, File, OpenOptions},
  9. io::{BufReader, BufWriter, Write},
  10. path::{Path, PathBuf},
  11. str,
  12. time::{SystemTime, UNIX_EPOCH},
  13. };
  14. /// A key pair (`PublicKey` and `SecretKey`).
  15. #[derive(Clone, Debug)]
  16. pub struct KeyPair {
  17. pub pk: String,
  18. pub sk: String,
  19. }
  20. fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {
  21. if let Some(parent) = path.parent() {
  22. fs::create_dir_all(parent)?;
  23. }
  24. let file = File::create(path)?;
  25. Ok(BufWriter::new(file))
  26. }
  27. /// Generate base64 encoded keypair
  28. pub fn generate_key(password: Option<String>) -> crate::Result<KeyPair> {
  29. let KP { pk, sk } = KP::generate_encrypted_keypair(password).unwrap();
  30. let pk_box_str = pk.to_box().unwrap().to_string();
  31. let sk_box_str = sk.to_box(None).unwrap().to_string();
  32. let encoded_pk = base64::engine::general_purpose::STANDARD.encode(pk_box_str);
  33. let encoded_sk = base64::engine::general_purpose::STANDARD.encode(sk_box_str);
  34. Ok(KeyPair {
  35. pk: encoded_pk,
  36. sk: encoded_sk,
  37. })
  38. }
  39. /// Transform a base64 String to readable string for the main signer
  40. pub fn decode_key(base64_key: String) -> crate::Result<String> {
  41. let decoded_str = &base64::engine::general_purpose::STANDARD.decode(base64_key)?[..];
  42. Ok(String::from(str::from_utf8(decoded_str)?))
  43. }
  44. /// Save KeyPair to disk
  45. pub fn save_keypair<P>(
  46. force: bool,
  47. sk_path: P,
  48. key: &str,
  49. pubkey: &str,
  50. ) -> crate::Result<(PathBuf, PathBuf)>
  51. where
  52. P: AsRef<Path>,
  53. {
  54. let sk_path = sk_path.as_ref();
  55. let pubkey_path = format!("{}.pub", sk_path.display());
  56. let pk_path = Path::new(&pubkey_path);
  57. if sk_path.exists() {
  58. if !force {
  59. return Err(anyhow::anyhow!(
  60. "Key generation aborted:\n{} already exists\nIf you really want to overwrite the existing key pair, add the --force switch to force this operation.",
  61. sk_path.display()
  62. ));
  63. } else {
  64. std::fs::remove_file(sk_path)?;
  65. }
  66. }
  67. if pk_path.exists() {
  68. std::fs::remove_file(pk_path)?;
  69. }
  70. let mut sk_writer = create_file(sk_path)?;
  71. write!(sk_writer, "{key:}")?;
  72. sk_writer.flush()?;
  73. let mut pk_writer = create_file(pk_path)?;
  74. write!(pk_writer, "{pubkey:}")?;
  75. pk_writer.flush()?;
  76. Ok((fs::canonicalize(sk_path)?, fs::canonicalize(pk_path)?))
  77. }
  78. /// Read key from file
  79. pub fn read_key_from_file<P>(sk_path: P) -> crate::Result<String>
  80. where
  81. P: AsRef<Path>,
  82. {
  83. Ok(fs::read_to_string(sk_path)?)
  84. }
  85. /// Sign files
  86. pub fn sign_file<P>(secret_key: &SecretKey, bin_path: P) -> crate::Result<(PathBuf, SignatureBox)>
  87. where
  88. P: AsRef<Path>,
  89. {
  90. let bin_path = bin_path.as_ref();
  91. // We need to append .sig at the end it's where the signature will be stored
  92. let mut extension = bin_path.extension().unwrap().to_os_string();
  93. extension.push(".sig");
  94. let signature_path = bin_path.with_extension(extension);
  95. let mut signature_box_writer = create_file(&signature_path)?;
  96. let trusted_comment = format!(
  97. "timestamp:{}\tfile:{}",
  98. unix_timestamp(),
  99. bin_path.file_name().unwrap().to_string_lossy()
  100. );
  101. let data_reader = open_data_file(bin_path)?;
  102. let signature_box = sign(
  103. None,
  104. secret_key,
  105. data_reader,
  106. Some(trusted_comment.as_str()),
  107. Some("signature from tauri secret key"),
  108. )?;
  109. let encoded_signature =
  110. base64::engine::general_purpose::STANDARD.encode(signature_box.to_string());
  111. signature_box_writer.write_all(encoded_signature.as_bytes())?;
  112. signature_box_writer.flush()?;
  113. Ok((fs::canonicalize(&signature_path)?, signature_box))
  114. }
  115. /// Gets the updater secret key from the given private key and password.
  116. pub fn secret_key(private_key: String, password: Option<String>) -> crate::Result<SecretKey> {
  117. let decoded_secret = decode_key(private_key)?;
  118. let sk_box = SecretKeyBox::from_string(&decoded_secret)
  119. .with_context(|| "failed to load updater private key")?;
  120. let sk = sk_box
  121. .into_secret_key(password)
  122. .with_context(|| "incorrect updater private key password")?;
  123. Ok(sk)
  124. }
  125. fn unix_timestamp() -> u64 {
  126. let start = SystemTime::now();
  127. let since_the_epoch = start
  128. .duration_since(UNIX_EPOCH)
  129. .expect("system clock is incorrect");
  130. since_the_epoch.as_secs()
  131. }
  132. fn open_data_file<P>(data_path: P) -> crate::Result<BufReader<File>>
  133. where
  134. P: AsRef<Path>,
  135. {
  136. let data_path = data_path.as_ref();
  137. let file = OpenOptions::new()
  138. .read(true)
  139. .open(data_path)
  140. .map_err(|e| minisign::PError::new(minisign::ErrorKind::Io, e))?;
  141. Ok(BufReader::new(file))
  142. }
  143. #[cfg(test)]
  144. mod tests {
  145. const PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5dkpDN09RZm5GeVAzc2RuYlNzWVVJelJRQnNIV2JUcGVXZUplWXZXYXpqUUFBQkFBQUFBQUFBQUFBQUlBQUFBQTZrN2RnWGh5dURxSzZiL1ZQSDdNcktiaHRxczQwMXdQelRHbjRNcGVlY1BLMTBxR2dpa3I3dDE1UTVDRDE4MXR4WlQwa1BQaXdxKy9UU2J2QmVSNXhOQWFDeG1GSVllbUNpTGJQRkhhTnROR3I5RmdUZi90OGtvaGhJS1ZTcjdZU0NyYzhQWlQ5cGM9Cg==";
  146. // we use minisign=0.7.3 to prevent a breaking change
  147. #[test]
  148. fn empty_password_is_valid() {
  149. let path = std::env::temp_dir().join("minisign-password-text.txt");
  150. std::fs::write(&path, b"TAURI").expect("failed to write test file");
  151. let secret_key =
  152. super::secret_key(PRIVATE_KEY.into(), Some("".into())).expect("failed to resolve secret key");
  153. super::sign_file(&secret_key, &path).expect("failed to sign file");
  154. }
  155. }