Bläddra i källkod

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

Lucas Nogueira 3 år sedan
förälder
incheckning
d95cc83105

+ 6 - 0
.changes/force-updater-public-key.md

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

+ 5 - 5
core/tauri-utils/src/config.rs

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

+ 12 - 15
core/tauri/src/updater/core.rs

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

+ 2 - 2
core/tauri/src/updater/error.rs

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

+ 1 - 1
core/tauri/src/updater/mod.rs

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

+ 2 - 10
tooling/bundler/src/bundle/settings.rs

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

+ 9 - 7
tooling/cli.rs/schema.json

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

+ 3 - 2
tooling/cli.rs/src/build.rs

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