feat: enforce updater public key [TRI-015] (#42)

This commit is contained in:
Lucas Nogueira 2022-01-09 16:37:03 -03:00
parent b43019a2b0
commit d95cc83105
No known key found for this signature in database
GPG Key ID: 2714B66BCFB01F7F
8 changed files with 40 additions and 42 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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)

View File

@ -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")]

View File

@ -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(&['\"', '\''][..], "");

View File

@ -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> {

View File

@ -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

View File

@ -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)?;
}