mirror of https://github.com/tauri-apps/tauri
feat: enforce updater public key [TRI-015] (#42)
This commit is contained in:
parent
b43019a2b0
commit
d95cc83105
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"tauri": patch
|
||||
"tauri-utils": patch
|
||||
---
|
||||
|
||||
The updater `pubkey` is now a required field for security reasons. Sign your updates with the `tauri signer` command.
|
|
@ -1373,8 +1373,8 @@ pub struct UpdaterConfig {
|
|||
pub dialog: bool,
|
||||
/// The updater endpoints.
|
||||
pub endpoints: Option<Vec<String>>,
|
||||
/// Optional pubkey.
|
||||
pub pubkey: Option<String>,
|
||||
/// Signature public key.
|
||||
pub pubkey: String,
|
||||
}
|
||||
|
||||
impl Default for UpdaterConfig {
|
||||
|
@ -1383,7 +1383,7 @@ impl Default for UpdaterConfig {
|
|||
active: false,
|
||||
dialog: default_dialog(),
|
||||
endpoints: None,
|
||||
pubkey: None,
|
||||
pubkey: "".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2028,7 +2028,7 @@ mod build {
|
|||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let active = self.active;
|
||||
let dialog = self.dialog;
|
||||
let pubkey = opt_str_lit(self.pubkey.as_ref());
|
||||
let pubkey = str_lit(&self.pubkey);
|
||||
let endpoints = opt_vec_str_lit(self.endpoints.as_ref());
|
||||
|
||||
literal_struct!(tokens, UpdaterConfig, active, dialog, pubkey, endpoints);
|
||||
|
@ -2234,7 +2234,7 @@ mod test {
|
|||
updater: UpdaterConfig {
|
||||
active: false,
|
||||
dialog: true,
|
||||
pubkey: None,
|
||||
pubkey: "".into(),
|
||||
endpoints: None,
|
||||
},
|
||||
security: SecurityConfig {
|
||||
|
|
|
@ -11,8 +11,8 @@ use crate::api::{
|
|||
use base64::decode;
|
||||
use http::StatusCode;
|
||||
use minisign_verify::{PublicKey, Signature};
|
||||
use tauri_utils::Env;
|
||||
use tauri_utils::platform::current_exe;
|
||||
use tauri_utils::Env;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -405,7 +405,7 @@ pub struct Update {
|
|||
impl Update {
|
||||
// Download and install our update
|
||||
// @todo(lemarier): Split into download and install (two step) but need to be thread safe
|
||||
pub async fn download_and_install(&self, pub_key: Option<String>) -> Result {
|
||||
pub async fn download_and_install(&self, pub_key: String) -> Result {
|
||||
// download url for selected release
|
||||
let url = self.download_url.as_str();
|
||||
// extract path
|
||||
|
@ -453,18 +453,15 @@ impl Update {
|
|||
// create memory buffer from our archive (Seek + Read)
|
||||
let mut archive_buffer = Cursor::new(resp.data);
|
||||
|
||||
// Validate signature ONLY if pubkey is available in tauri.conf.json
|
||||
if let Some(pub_key) = pub_key {
|
||||
// We need an announced signature by the server
|
||||
// if there is no signature, bail out.
|
||||
if let Some(signature) = &self.signature {
|
||||
// we make sure the archive is valid and signed with the private key linked with the publickey
|
||||
verify_signature(&mut archive_buffer, signature, &pub_key)?;
|
||||
} else {
|
||||
// We have a public key inside our source file, but not announced by the server,
|
||||
// we assume this update is NOT valid.
|
||||
return Err(Error::PubkeyButNoSignature);
|
||||
}
|
||||
// We need an announced signature by the server
|
||||
// if there is no signature, bail out.
|
||||
if let Some(signature) = &self.signature {
|
||||
// we make sure the archive is valid and signed with the private key linked with the publickey
|
||||
verify_signature(&mut archive_buffer, signature, &pub_key)?;
|
||||
} else {
|
||||
// We have a public key inside our source file, but not announced by the server,
|
||||
// we assume this update is NOT valid.
|
||||
return Err(Error::MissingUpdaterSignature);
|
||||
}
|
||||
|
||||
// we copy the files depending of the operating system
|
||||
|
@ -1174,7 +1171,7 @@ mod test {
|
|||
assert_eq!(updater.version, "2.0.1");
|
||||
|
||||
// download, install and validate signature
|
||||
let install_process = block!(updater.download_and_install(Some(pubkey)));
|
||||
let install_process = block!(updater.download_and_install(pubkey));
|
||||
assert!(install_process.is_ok());
|
||||
|
||||
// make sure the extraction went well (it should have skipped the main app.app folder)
|
||||
|
|
|
@ -45,8 +45,8 @@ pub enum Error {
|
|||
#[error("Unsuported operating system or platform")]
|
||||
UnsupportedPlatform,
|
||||
/// Public key found in `tauri.conf.json` but no signature announced remotely.
|
||||
#[error("Signature not available but public key provided, skipping update")]
|
||||
PubkeyButNoSignature,
|
||||
#[error("Signature not available, skipping update")]
|
||||
MissingUpdaterSignature,
|
||||
/// Triggered when there is NO error and the two versions are equals.
|
||||
/// On client side, it's important to catch this error.
|
||||
#[error("No updates available")]
|
||||
|
|
|
@ -528,7 +528,7 @@ async fn prompt_for_install<R: Runtime>(
|
|||
updater: &self::core::Update,
|
||||
app_name: &str,
|
||||
body: &str,
|
||||
pubkey: Option<String>,
|
||||
pubkey: String,
|
||||
) -> crate::Result<()> {
|
||||
// remove single & double quote
|
||||
let escaped_body = body.replace(&['\"', '\''][..], "");
|
||||
|
|
|
@ -113,8 +113,8 @@ pub struct UpdaterSettings {
|
|||
pub active: bool,
|
||||
/// The updater endpoints.
|
||||
pub endpoints: Option<Vec<String>>,
|
||||
/// Optional pubkey.
|
||||
pub pubkey: Option<String>,
|
||||
/// Signature public key.
|
||||
pub pubkey: String,
|
||||
/// Display built-in dialog or use event system if disabled.
|
||||
pub dialog: bool,
|
||||
}
|
||||
|
@ -680,14 +680,6 @@ impl Settings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Is pubkey provided?
|
||||
pub fn is_updater_pubkey(&self) -> bool {
|
||||
match &self.bundle_settings.updater {
|
||||
Some(val) => val.pubkey.is_some(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get pubkey (mainly for testing)
|
||||
#[cfg(test)]
|
||||
pub fn updater_pubkey(&self) -> Option<&str> {
|
||||
|
|
|
@ -159,7 +159,8 @@
|
|||
"security": {},
|
||||
"updater": {
|
||||
"active": false,
|
||||
"dialog": true
|
||||
"dialog": true,
|
||||
"pubkey": ""
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
|
@ -1493,7 +1494,8 @@
|
|||
"description": "The updater configuration.",
|
||||
"default": {
|
||||
"active": false,
|
||||
"dialog": true
|
||||
"dialog": true,
|
||||
"pubkey": ""
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -1534,6 +1536,9 @@
|
|||
"UpdaterConfig": {
|
||||
"description": "The Updater configuration object.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pubkey"
|
||||
],
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "Whether the updater is active or not.",
|
||||
|
@ -1556,11 +1561,8 @@
|
|||
}
|
||||
},
|
||||
"pubkey": {
|
||||
"description": "Optional pubkey.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
"description": "Signature public key.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
|
@ -270,8 +270,8 @@ pub fn command(options: Options) -> Result<()> {
|
|||
|
||||
let bundles = bundle_project(settings).with_context(|| "failed to bundle project")?;
|
||||
|
||||
// If updater is active and pubkey is available
|
||||
if config_.tauri.updater.active && config_.tauri.updater.pubkey.is_some() {
|
||||
// If updater is active
|
||||
if config_.tauri.updater.active {
|
||||
// make sure we have our package builts
|
||||
let mut signed_paths = Vec::new();
|
||||
for elem in bundles
|
||||
|
@ -286,6 +286,7 @@ pub fn command(options: Options) -> Result<()> {
|
|||
signed_paths.append(&mut vec![signature_path]);
|
||||
}
|
||||
}
|
||||
|
||||
if !signed_paths.is_empty() {
|
||||
print_signed_updater_archive(&signed_paths)?;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue