Skip to content

Commit

Permalink
feat(cli): validate updater private key when signing (#4754)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored Jul 25, 2022
1 parent b48962e commit b2a8930
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 55 deletions.
6 changes: 6 additions & 0 deletions .changes/validate-signature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"cli.rs": patch
"cli.js": patch
---

Validate updater signature matches configured public key.
16 changes: 9 additions & 7 deletions examples/updater/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 35 additions & 3 deletions tooling/cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
app_paths::{app_dir, tauri_dir},
command_env,
config::{get as get_config, AppUrl, WindowUrl, MERGE_CONFIG_EXTENSION_NAME},
updater_signature::sign_file_from_env_variables,
updater_signature::{read_key_from_file, secret_key as updater_secret_key, sign_file},
},
interface::{AppInterface, AppSettings, Interface},
CommandExt, Result,
Expand All @@ -16,7 +16,11 @@ use anyhow::{bail, Context};
use clap::Parser;
use log::warn;
use log::{error, info};
use std::{env::set_current_dir, path::PathBuf, process::Command};
use std::{
env::{set_current_dir, var_os},
path::{Path, PathBuf},
process::Command,
};
use tauri_bundler::bundle::{bundle_project, PackageType};

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

// If updater is active
if config_.tauri.updater.active {
// if no password provided we use an empty string
let password = var_os("TAURI_KEY_PASSWORD").map(|v| v.to_str().unwrap().to_string());
// get the private key
let secret_key = if let Some(mut private_key) =
var_os("TAURI_PRIVATE_KEY").map(|v| v.to_str().unwrap().to_string())
{
// check if env var points to a file..
let pk_dir = Path::new(&private_key);
// Check if user provided a path or a key
// We validate if the path exist or not.
if pk_dir.exists() {
// read file content and use it as private key
private_key = read_key_from_file(pk_dir)?;
}
updater_secret_key(private_key, password)
} else {
Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_PRIVATE_KEY` environment variable."))
}?;

let pubkey = base64::decode(&config_.tauri.updater.pubkey)?;
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;

// make sure we have our package builts
let mut signed_paths = Vec::new();
for elem in bundles
Expand All @@ -286,7 +313,12 @@ pub fn command(mut options: Options) -> Result<()> {
// another type of updater package who require multiple file signature
for path in elem.bundle_paths.iter() {
// sign our path from environment variables
let (signature_path, _signature) = sign_file_from_env_variables(path)?;
let (signature_path, signature) = sign_file(&secret_key, path)?;
if signature.keynum() != public_key.keynum() {
return Err(anyhow::anyhow!(
"The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key defined in `tauri.conf.json > tauri > updater > pubkey`."
));
}
signed_paths.append(&mut vec![signature_path]);
}
}
Expand Down
54 changes: 13 additions & 41 deletions tooling/cli/src/helpers/updater_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

use anyhow::Context;
use base64::{decode, encode};
use minisign::{sign, KeyPair as KP, SecretKeyBox};
use minisign::{sign, KeyPair as KP, SecretKey, SecretKeyBox, SignatureBox};
use std::{
env::var_os,
fs::{self, File, OpenOptions},
io::{BufReader, BufWriter, Write},
path::{Path, PathBuf},
Expand Down Expand Up @@ -101,22 +100,11 @@ where
}

/// Sign files
pub fn sign_file<P>(
private_key: String,
password: Option<String>,
bin_path: P,
) -> crate::Result<(PathBuf, String)>
pub fn sign_file<P>(secret_key: &SecretKey, bin_path: P) -> crate::Result<(PathBuf, SignatureBox)>
where
P: AsRef<Path>,
{
let bin_path = bin_path.as_ref();
let decoded_secret = decode_key(private_key)?;
let sk_box = SecretKeyBox::from_string(&decoded_secret)
.with_context(|| "failed to load updater private key")?;
let sk = sk_box
.into_secret_key(password)
.with_context(|| "incorrect updater private key password")?;

// We need to append .sig at the end it's where the signature will be stored
let mut extension = bin_path.extension().unwrap().to_os_string();
extension.push(".sig");
Expand All @@ -134,7 +122,7 @@ where

let signature_box = sign(
None,
&sk,
secret_key,
data_reader,
Some(trusted_comment.as_str()),
Some("signature from tauri secret key"),
Expand All @@ -143,34 +131,18 @@ where
let encoded_signature = encode(&signature_box.to_string());
signature_box_writer.write_all(encoded_signature.as_bytes())?;
signature_box_writer.flush()?;
Ok((fs::canonicalize(&signature_path)?, encoded_signature))
Ok((fs::canonicalize(&signature_path)?, signature_box))
}

/// Sign files using the TAURI_KEY_PASSWORD and TAURI_PRIVATE_KEY environment variables
pub fn sign_file_from_env_variables<P>(path_to_sign: P) -> crate::Result<(PathBuf, String)>
where
P: AsRef<Path>,
{
// if no password provided we set empty string
let password_string =
var_os("TAURI_KEY_PASSWORD").map(|value| value.to_str().unwrap().to_string());
// get the private key
if let Some(private_key) = var_os("TAURI_PRIVATE_KEY") {
// check if this file exist..
let mut private_key_string = String::from(private_key.to_str().unwrap());
let pk_dir = Path::new(&private_key_string);
// Check if user provided a path or a key
// We validate if the path exist or no.
if pk_dir.exists() {
// read file content as use it as private key
private_key_string = read_key_from_file(pk_dir)?;
}
// sign our file
sign_file(private_key_string, password_string, path_to_sign)
} else {
// reject if we don't have the private key
Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_PRIVATE_KEY` environment variable."))
}
/// Gets the updater secret key from the given private key and password.
pub fn secret_key(private_key: String, password: Option<String>) -> crate::Result<SecretKey> {
let decoded_secret = decode_key(private_key)?;
let sk_box = SecretKeyBox::from_string(&decoded_secret)
.with_context(|| "failed to load updater private key")?;
let sk = sk_box
.into_secret_key(password)
.with_context(|| "incorrect updater private key password")?;
Ok(sk)
}

fn unix_timestamp() -> u64 {
Expand Down
9 changes: 5 additions & 4 deletions tooling/cli/src/signer/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::path::{Path, PathBuf};

use crate::{
helpers::updater_signature::{read_key_from_file, sign_file},
helpers::updater_signature::{read_key_from_file, secret_key, sign_file},
Result,
};
use anyhow::Context;
Expand Down Expand Up @@ -45,13 +45,14 @@ pub fn command(mut options: Options) -> Result<()> {
println!("Signing without password.");
}

let (manifest_dir, signature) = sign_file(private_key, options.password, options.file)
.with_context(|| "failed to sign file")?;
let (manifest_dir, signature) =
sign_file(&secret_key(private_key, options.password)?, options.file)
.with_context(|| "failed to sign file")?;

println!(
"\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.",
manifest_dir.display(),
signature
base64::encode(signature.to_string())
);

Ok(())
Expand Down

0 comments on commit b2a8930

Please sign in to comment.