Skip to content

Commit b2a8930

Browse files
authored
feat(cli): validate updater private key when signing (#4754)
1 parent b48962e commit b2a8930

File tree

5 files changed

+68
-55
lines changed

5 files changed

+68
-55
lines changed

.changes/validate-signature.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"cli.rs": patch
3+
"cli.js": patch
4+
---
5+
6+
Validate updater signature matches configured public key.

examples/updater/src-tauri/Cargo.lock

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/cli/src/build.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
app_paths::{app_dir, tauri_dir},
88
command_env,
99
config::{get as get_config, AppUrl, WindowUrl, MERGE_CONFIG_EXTENSION_NAME},
10-
updater_signature::sign_file_from_env_variables,
10+
updater_signature::{read_key_from_file, secret_key as updater_secret_key, sign_file},
1111
},
1212
interface::{AppInterface, AppSettings, Interface},
1313
CommandExt, Result,
@@ -16,7 +16,11 @@ use anyhow::{bail, Context};
1616
use clap::Parser;
1717
use log::warn;
1818
use log::{error, info};
19-
use std::{env::set_current_dir, path::PathBuf, process::Command};
19+
use std::{
20+
env::{set_current_dir, var_os},
21+
path::{Path, PathBuf},
22+
process::Command,
23+
};
2024
use tauri_bundler::bundle::{bundle_project, PackageType};
2125

2226
#[derive(Debug, Clone, Parser)]
@@ -276,6 +280,29 @@ pub fn command(mut options: Options) -> Result<()> {
276280

277281
// If updater is active
278282
if config_.tauri.updater.active {
283+
// if no password provided we use an empty string
284+
let password = var_os("TAURI_KEY_PASSWORD").map(|v| v.to_str().unwrap().to_string());
285+
// get the private key
286+
let secret_key = if let Some(mut private_key) =
287+
var_os("TAURI_PRIVATE_KEY").map(|v| v.to_str().unwrap().to_string())
288+
{
289+
// check if env var points to a file..
290+
let pk_dir = Path::new(&private_key);
291+
// Check if user provided a path or a key
292+
// We validate if the path exist or not.
293+
if pk_dir.exists() {
294+
// read file content and use it as private key
295+
private_key = read_key_from_file(pk_dir)?;
296+
}
297+
updater_secret_key(private_key, password)
298+
} else {
299+
Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_PRIVATE_KEY` environment variable."))
300+
}?;
301+
302+
let pubkey = base64::decode(&config_.tauri.updater.pubkey)?;
303+
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
304+
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
305+
279306
// make sure we have our package builts
280307
let mut signed_paths = Vec::new();
281308
for elem in bundles
@@ -286,7 +313,12 @@ pub fn command(mut options: Options) -> Result<()> {
286313
// another type of updater package who require multiple file signature
287314
for path in elem.bundle_paths.iter() {
288315
// sign our path from environment variables
289-
let (signature_path, _signature) = sign_file_from_env_variables(path)?;
316+
let (signature_path, signature) = sign_file(&secret_key, path)?;
317+
if signature.keynum() != public_key.keynum() {
318+
return Err(anyhow::anyhow!(
319+
"The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key defined in `tauri.conf.json > tauri > updater > pubkey`."
320+
));
321+
}
290322
signed_paths.append(&mut vec![signature_path]);
291323
}
292324
}

tooling/cli/src/helpers/updater_signature.rs

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
use anyhow::Context;
66
use base64::{decode, encode};
7-
use minisign::{sign, KeyPair as KP, SecretKeyBox};
7+
use minisign::{sign, KeyPair as KP, SecretKey, SecretKeyBox, SignatureBox};
88
use std::{
9-
env::var_os,
109
fs::{self, File, OpenOptions},
1110
io::{BufReader, BufWriter, Write},
1211
path::{Path, PathBuf},
@@ -101,22 +100,11 @@ where
101100
}
102101

103102
/// Sign files
104-
pub fn sign_file<P>(
105-
private_key: String,
106-
password: Option<String>,
107-
bin_path: P,
108-
) -> crate::Result<(PathBuf, String)>
103+
pub fn sign_file<P>(secret_key: &SecretKey, bin_path: P) -> crate::Result<(PathBuf, SignatureBox)>
109104
where
110105
P: AsRef<Path>,
111106
{
112107
let bin_path = bin_path.as_ref();
113-
let decoded_secret = decode_key(private_key)?;
114-
let sk_box = SecretKeyBox::from_string(&decoded_secret)
115-
.with_context(|| "failed to load updater private key")?;
116-
let sk = sk_box
117-
.into_secret_key(password)
118-
.with_context(|| "incorrect updater private key password")?;
119-
120108
// We need to append .sig at the end it's where the signature will be stored
121109
let mut extension = bin_path.extension().unwrap().to_os_string();
122110
extension.push(".sig");
@@ -134,7 +122,7 @@ where
134122

135123
let signature_box = sign(
136124
None,
137-
&sk,
125+
secret_key,
138126
data_reader,
139127
Some(trusted_comment.as_str()),
140128
Some("signature from tauri secret key"),
@@ -143,34 +131,18 @@ where
143131
let encoded_signature = encode(&signature_box.to_string());
144132
signature_box_writer.write_all(encoded_signature.as_bytes())?;
145133
signature_box_writer.flush()?;
146-
Ok((fs::canonicalize(&signature_path)?, encoded_signature))
134+
Ok((fs::canonicalize(&signature_path)?, signature_box))
147135
}
148136

149-
/// Sign files using the TAURI_KEY_PASSWORD and TAURI_PRIVATE_KEY environment variables
150-
pub fn sign_file_from_env_variables<P>(path_to_sign: P) -> crate::Result<(PathBuf, String)>
151-
where
152-
P: AsRef<Path>,
153-
{
154-
// if no password provided we set empty string
155-
let password_string =
156-
var_os("TAURI_KEY_PASSWORD").map(|value| value.to_str().unwrap().to_string());
157-
// get the private key
158-
if let Some(private_key) = var_os("TAURI_PRIVATE_KEY") {
159-
// check if this file exist..
160-
let mut private_key_string = String::from(private_key.to_str().unwrap());
161-
let pk_dir = Path::new(&private_key_string);
162-
// Check if user provided a path or a key
163-
// We validate if the path exist or no.
164-
if pk_dir.exists() {
165-
// read file content as use it as private key
166-
private_key_string = read_key_from_file(pk_dir)?;
167-
}
168-
// sign our file
169-
sign_file(private_key_string, password_string, path_to_sign)
170-
} else {
171-
// reject if we don't have the private key
172-
Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_PRIVATE_KEY` environment variable."))
173-
}
137+
/// Gets the updater secret key from the given private key and password.
138+
pub fn secret_key(private_key: String, password: Option<String>) -> crate::Result<SecretKey> {
139+
let decoded_secret = decode_key(private_key)?;
140+
let sk_box = SecretKeyBox::from_string(&decoded_secret)
141+
.with_context(|| "failed to load updater private key")?;
142+
let sk = sk_box
143+
.into_secret_key(password)
144+
.with_context(|| "incorrect updater private key password")?;
145+
Ok(sk)
174146
}
175147

176148
fn unix_timestamp() -> u64 {

tooling/cli/src/signer/sign.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::path::{Path, PathBuf};
66

77
use crate::{
8-
helpers::updater_signature::{read_key_from_file, sign_file},
8+
helpers::updater_signature::{read_key_from_file, secret_key, sign_file},
99
Result,
1010
};
1111
use anyhow::Context;
@@ -45,13 +45,14 @@ pub fn command(mut options: Options) -> Result<()> {
4545
println!("Signing without password.");
4646
}
4747

48-
let (manifest_dir, signature) = sign_file(private_key, options.password, options.file)
49-
.with_context(|| "failed to sign file")?;
48+
let (manifest_dir, signature) =
49+
sign_file(&secret_key(private_key, options.password)?, options.file)
50+
.with_context(|| "failed to sign file")?;
5051

5152
println!(
5253
"\nYour file was signed successfully, You can find the signature here:\n{}\n\nPublic signature:\n{}\n\nMake sure to include this into the signature field of your update server.",
5354
manifest_dir.display(),
54-
signature
55+
base64::encode(signature.to_string())
5556
);
5657

5758
Ok(())

0 commit comments

Comments
 (0)