Эх сурвалжийг харах

feat(updater): improve validation and error messages, closes #3761 (#3780)

Lucas Fernandes Nogueira 3 жил өмнө
parent
commit
dbc2873e82

+ 5 - 0
.changes/improve-updater-validation.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Improved the updater response validation and error messages.

+ 293 - 83
core/tauri/src/updater/core.rs

@@ -51,7 +51,7 @@ pub struct RemoteRelease {
   /// Update short description
   pub body: Option<String>,
   /// Optional signature for the current platform
-  pub signature: Option<String>,
+  pub signature: String,
   #[cfg(target_os = "windows")]
   /// Optional: Windows only try to use elevated task
   pub with_elevated_task: bool,
@@ -65,42 +65,47 @@ impl RemoteRelease {
     let version = match release.get("version") {
       Some(version) => version
         .as_str()
-        .ok_or_else(|| {
-          Error::RemoteMetadata("Unable to extract `version` from remote server".into())
-        })?
-        .trim_start_matches('v')
-        .to_string(),
-      None => release
-        .get("name")
-        .ok_or_else(|| Error::RemoteMetadata("Release missing `name` and `version`".into()))?
-        .as_str()
-        .ok_or_else(|| {
-          Error::RemoteMetadata("Unable to extract `name` from remote server`".into())
-        })?
+        .ok_or_else(|| Error::InvalidResponseType("version", "string", version.clone()))?
         .trim_start_matches('v')
         .to_string(),
+      None => {
+        let name = release
+          .get("name")
+          .ok_or(Error::MissingResponseField("version or name"))?;
+        name
+          .as_str()
+          .ok_or_else(|| Error::InvalidResponseType("name", "string", name.clone()))?
+          .trim_start_matches('v')
+          .to_string()
+      }
     };
 
     // pub_date is required default is: `N/A` if not provided by the remote JSON
-    let date = release
-      .get("pub_date")
-      .and_then(|v| v.as_str())
-      .unwrap_or("N/A")
-      .to_string();
+    let date = if let Some(date) = release.get("pub_date") {
+      date
+        .as_str()
+        .map(|d| d.to_string())
+        .ok_or_else(|| Error::InvalidResponseType("pub_date", "string", date.clone()))?
+    } else {
+      "N/A".into()
+    };
 
     // body is optional to build our update
-    let body = release
-      .get("notes")
-      .map(|notes| notes.as_str().unwrap_or("").to_string());
-
-    // signature is optional to build our update
-    let mut signature = release
-      .get("signature")
-      .map(|signature| signature.as_str().unwrap_or("").to_string());
+    let body = if let Some(notes) = release.get("notes") {
+      Some(
+        notes
+          .as_str()
+          .map(|n| n.to_string())
+          .ok_or_else(|| Error::InvalidResponseType("notes", "string", notes.clone()))?,
+      )
+    } else {
+      None
+    };
 
     let download_url;
     #[cfg(target_os = "windows")]
     let with_elevated_task;
+    let signature;
 
     match release.get("platforms") {
       //
@@ -123,38 +128,53 @@ impl RemoteRelease {
           // use provided signature if available
           signature = current_target_data
             .get("signature")
-            .map(|found_signature| found_signature.as_str().unwrap_or("").to_string());
+            .ok_or(Error::MissingResponseField("signature"))
+            .and_then(|signature| {
+              signature
+                .as_str()
+                .ok_or_else(|| Error::InvalidResponseType("signature", "string", signature.clone()))
+            })?;
           // Download URL is required
-          download_url = current_target_data
+          let url = current_target_data
             .get("url")
-            .ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))?
+            .ok_or(Error::MissingResponseField("url"))?;
+          download_url = url
             .as_str()
-            .ok_or_else(|| {
-              Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
-            })?
+            .ok_or_else(|| Error::InvalidResponseType("url", "string", url.clone()))?
             .to_string();
           #[cfg(target_os = "windows")]
           {
             with_elevated_task = current_target_data
               .get("with_elevated_task")
-              .and_then(|v| v.as_bool())
-              .unwrap_or_default();
+              .map(|v| {
+                v.as_bool().ok_or_else(|| {
+                  Error::InvalidResponseType("with_elevated_task", "boolean", v.clone())
+                })
+              })
+              .unwrap_or(Ok(false))?;
           }
         } else {
           // make sure we have an available platform from the static
-          return Err(Error::RemoteMetadata("Platform not available".into()));
+          return Err(Error::TargetNotFound(target.into()));
         }
       }
       // We don't have the `platforms` field announced, let's assume our
       // download URL is at the root of the JSON.
       None => {
-        download_url = release
+        signature = release
+          .get("signature")
+          .ok_or(Error::MissingResponseField("signature"))
+          .and_then(|signature| {
+            signature
+              .as_str()
+              .ok_or_else(|| Error::InvalidResponseType("signature", "string", signature.clone()))
+          })?;
+        let url = release
           .get("url")
-          .ok_or_else(|| Error::RemoteMetadata("Release missing `url`".into()))?
+          .ok_or(Error::MissingResponseField("url"))?;
+        download_url = url
           .as_str()
-          .ok_or_else(|| {
-            Error::RemoteMetadata("Unable to extract `url` from remote server`".into())
-          })?
+          .ok_or_else(|| Error::InvalidResponseType("url", "string", url.clone()))?
           .to_string();
         #[cfg(target_os = "windows")]
         {
@@ -171,7 +191,7 @@ impl RemoteRelease {
       date,
       download_url,
       body,
-      signature,
+      signature: signature.to_string(),
       #[cfg(target_os = "windows")]
       with_elevated_task,
     })
@@ -356,13 +376,11 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
     // Last error is cleaned on success -- shouldn't be triggered if
     // we have a successful call
     if let Some(error) = last_error {
-      return Err(Error::Network(error.to_string()));
+      return Err(error);
     }
 
     // Extracted remote metadata
-    let final_release = remote_release.ok_or_else(|| {
-      Error::RemoteMetadata("Unable to extract update metadata from the remote server.".into())
-    })?;
+    let final_release = remote_release.ok_or(Error::ReleaseNotFound)?;
 
     // did the announced version is greated than our current one?
     let should_update =
@@ -412,7 +430,7 @@ pub struct Update<R: Runtime> {
   /// Download URL announced
   download_url: String,
   /// Signature announced
-  signature: Option<String>,
+  signature: String,
   #[cfg(target_os = "windows")]
   /// Optional: Windows only try to use elevated task
   /// Default to false
@@ -521,14 +539,7 @@ impl<R: Runtime> Update<R> {
 
     // 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);
-    }
+    verify_signature(&mut archive_buffer, &self.signature, &pub_key)?;
 
     #[cfg(feature = "updater")]
     {
@@ -816,7 +827,9 @@ pub fn extract_path_from_executable(env: &Env, executable_path: &Path) -> PathBu
 // Convert base64 to string and prevent failing
 fn base64_to_string(base64_string: &str) -> Result<String> {
   let decoded_string = &decode(base64_string)?;
-  let result = from_utf8(decoded_string)?.to_string();
+  let result = from_utf8(decoded_string)
+    .map_err(|_| Error::SignatureUtf8(base64_string.into()))?
+    .to_string();
   Ok(result)
 }
 
@@ -868,19 +881,19 @@ mod test {
       "platforms": {
         "darwin-aarch64": {
           "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==",
-          "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz"
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz"
         },
         "darwin-x86_64": {
           "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJZVGdpKzJmRWZ0SkRvWS9TdFpqTU9xcm1mUmJSSG5OWVlwSklrWkN1SFpWbmh4SDlBcTU3SXpjbm0xMmRjRkphbkpVeGhGcTdrdzlrWGpGVWZQSWdzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1MDU3CWZpbGU6L1VzZXJzL3J1bm5lci9ydW5uZXJzLzIuMjYzLjAvd29yay90YXVyaS90YXVyaS90YXVyaS9leGFtcGxlcy9jb21tdW5pY2F0aW9uL3NyYy10YXVyaS90YXJnZXQvZGVidWcvYnVuZGxlL29zeC9hcHAuYXBwLnRhci5negp4ZHFlUkJTVnpGUXdDdEhydTE5TGgvRlVPeVhjTnM5RHdmaGx3c0ZPWjZXWnFwVDRNWEFSbUJTZ1ZkU1IwckJGdmlwSzJPd00zZEZFN2hJOFUvL1FDZz09Cg==",
-          "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.app.tar.gz"
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.app.tar.gz"
         },
         "linux-x86_64": {
           "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOWZSM29hTFNmUEdXMHRoOC81WDFFVVFRaXdWOUdXUUdwT0NlMldqdXkyaWVieXpoUmdZeXBJaXRqSm1YVmczNXdRL1Brc0tHb1NOTzhrL1hadFcxdmdnPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE3MzQzCWZpbGU6L2hvbWUvcnVubmVyL3dvcmsvdGF1cmkvdGF1cmkvdGF1cmkvZXhhbXBsZXMvY29tbXVuaWNhdGlvbi9zcmMtdGF1cmkvdGFyZ2V0L2RlYnVnL2J1bmRsZS9hcHBpbWFnZS9hcHAuQXBwSW1hZ2UudGFyLmd6CmRUTUM2bWxnbEtTbUhOZGtERUtaZnpUMG5qbVo5TGhtZWE1SFNWMk5OOENaVEZHcnAvVW0zc1A2ajJEbWZUbU0yalRHT0FYYjJNVTVHOHdTQlYwQkF3PT0K",
-          "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.AppImage.tar.gz"
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.AppImage.tar.gz"
         },
         "windows-x86_64": {
           "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K",
-          "url": "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip"
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.x64.msi.zip"
         }
       }
     }"#.into()
@@ -926,15 +939,6 @@ mod test {
     )
   }
 
-  fn generate_sample_bad_json() -> String {
-    r#"{
-      "version": "v0.0.3",
-      "notes": "Blablaa",
-      "date": "2020-02-20T15:41:00Z",
-      "download_link": "https://github.com/lemarier/tauri-test/releases/download/v0.0.1/update3.tar.gz"
-    }"#.into()
-  }
-
   #[test]
   fn simple_http_updater() {
     let _m = mockito::mock("GET", "/")
@@ -992,10 +996,10 @@ mod test {
 
     assert!(updater.should_update);
     assert_eq!(updater.version, "2.0.0");
-    assert_eq!(updater.signature, Some("dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K".into()));
+    assert_eq!(updater.signature, "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUldUTE5QWWxkQnlZOVJHMWlvTzRUSlQzTHJOMm5waWpic0p0VVI2R0hUNGxhQVMxdzBPRndlbGpXQXJJakpTN0toRURtVzBkcm15R0VaNTJuS1lZRWdzMzZsWlNKUVAzZGdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNTkyOTE1NTIzCWZpbGU6RDpcYVx0YXVyaVx0YXVyaVx0YXVyaVxleGFtcGxlc1xjb21tdW5pY2F0aW9uXHNyYy10YXVyaVx0YXJnZXRcZGVidWdcYXBwLng2NC5tc2kuemlwCitXa1lQc3A2MCs1KzEwZnVhOGxyZ2dGMlZqbjBaVUplWEltYUdyZ255eUF6eVF1dldWZzFObStaVEQ3QU1RS1lzcjhDVU4wWFovQ1p1QjJXbW1YZUJ3PT0K");
     assert_eq!(
       updater.download_url,
-      "https://github.com/lemarier/tauri-test/releases/download/v1.0.0/app.x64.msi.zip"
+      "https://github.com/tauri-apps/updater-test/releases/download/v1.0.0/app.x64.msi.zip"
     );
   }
 
@@ -1182,20 +1186,226 @@ mod test {
   }
 
   #[test]
-  fn http_updater_missing_remote_data() {
-    let _m = mockito::mock("GET", "/")
-      .with_status(200)
-      .with_header("content-type", "application/json")
-      .with_body(generate_sample_bad_json())
-      .create();
-
-    let app = crate::test::mock_app();
-    let check_update = block!(builder(app.handle())
-      .url(mockito::server_url())
-      .current_version("0.0.1")
-      .build());
+  fn http_updater_invalid_remote_data() {
+    let invalid_signature = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": true
+    }"#;
+    let invalid_version = r#"{
+      "version": 5,
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": "x"
+    }"#;
+    let invalid_name = r#"{
+      "name": false,
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": "x"
+    }"#;
+    let invalid_date = r#"{
+      "version": "1.0.0",
+      "notes": "Blablaa",
+      "pub_date": 345645646,
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": "x"
+    }"#;
+    let invalid_notes = r#"{
+      "version": "v0.0.3",
+      "notes": ["bla", "bla"],
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": "x"
+    }"#;
+    let invalid_url = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": ["https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz", "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"],
+      "signature": "x"
+    }"#;
+    let invalid_platform_signature = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "platforms": {
+        "test-target": {
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+          "signature": {
+            "test-target": "x"
+          }
+        }
+      }
+    }"#;
+    let invalid_platform_url = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "platforms": {
+        "test-target": {
+          "url": {
+            "first": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
+          }
+          "signature": "x"
+        }
+      }
+    }"#;
+
+    let test_cases = [
+      (
+        invalid_signature,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_version,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("version", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_name,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("name", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_date,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("pub_date", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_notes,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("notes", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_url,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_platform_signature,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("signature", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+      (
+        invalid_platform_url,
+        Box::new(|e| matches!(e, Error::InvalidResponseType("url", "string", _)))
+          as Box<dyn FnOnce(Error) -> bool>,
+      ),
+    ];
+
+    for (response, validator) in test_cases {
+      let _m = mockito::mock("GET", "/")
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_body(response)
+        .create();
+
+      let app = crate::test::mock_app();
+      let check_update = block!(builder(app.handle())
+        .url(mockito::server_url())
+        .current_version("0.0.1")
+        .target("test-target")
+        .build());
+      if let Err(e) = check_update {
+        validator(e);
+      } else {
+        panic!("unexpected Ok response");
+      }
+    }
+  }
 
-    assert!(check_update.is_err());
+  #[test]
+  fn http_updater_missing_remote_data() {
+    let missing_signature = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
+    }"#;
+    let missing_version = r#"{
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+      "signature": "x"
+    }"#;
+    let missing_url = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "signature": "x"
+    }"#;
+    let missing_target = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "platforms": {
+        "unknown-target": {
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz",
+          "signature": "x"
+        }
+      }
+    }"#;
+    let missing_platform_signature = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "platforms": {
+        "test-target": {
+          "url": "https://github.com/tauri-apps/updater-test/releases/download/v0.0.1/update3.tar.gz"
+        }
+      }
+    }"#;
+    let missing_platform_url = r#"{
+      "version": "v0.0.3",
+      "notes": "Blablaa",
+      "pub_date": "2020-02-20T15:41:00Z",
+      "platforms": {
+        "test-target": {
+          "signature": "x"
+        }
+      }
+    }"#;
+
+    let test_cases = [
+      (missing_signature, Error::MissingResponseField("signature")),
+      (
+        missing_version,
+        Error::MissingResponseField("version or name"),
+      ),
+      (missing_url, Error::MissingResponseField("url")),
+      (missing_target, Error::TargetNotFound("test-target".into())),
+      (
+        missing_platform_signature,
+        Error::MissingResponseField("signature"),
+      ),
+      (missing_platform_url, Error::MissingResponseField("url")),
+    ];
+
+    for (response, error) in test_cases {
+      let _m = mockito::mock("GET", "/")
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_body(response)
+        .create();
+
+      let app = crate::test::mock_app();
+      let check_update = block!(builder(app.handle())
+        .url(mockito::server_url())
+        .current_version("0.0.1")
+        .target("test-target")
+        .build());
+      if let Err(e) = check_update {
+        assert_eq!(e.to_string(), error.to_string());
+      } else {
+        panic!("unexpected Ok response");
+      }
+    }
   }
 
   // run complete process on mac only for now as we don't have

+ 14 - 8
core/tauri/src/updater/error.rs

@@ -24,17 +24,17 @@ pub enum Error {
   #[error("Signature decoding error: {0}")]
   Base64(#[from] base64::DecodeError),
   /// UTF8 Errors in signature.
-  #[error("Signature encoding error: {0}")]
-  Utf8(#[from] std::str::Utf8Error),
+  #[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
+  SignatureUtf8(String),
   /// Tauri utils, mainly extract and file move.
   #[error("Tauri API error: {0}")]
   TauriApi(#[from] crate::api::Error),
   /// Network error.
   #[error("Network error: {0}")]
   Network(String),
-  /// Metadata (JSON) error.
-  #[error("Remote JSON error: {0}")]
-  RemoteMetadata(String),
+  /// Could not fetch a valid response from the server.
+  #[error("Could not fetch a valid release JSON from the remote")]
+  ReleaseNotFound,
   /// Error building updater.
   #[error("Unable to prepare the updater: {0}")]
   Builder(String),
@@ -44,13 +44,19 @@ pub enum Error {
   /// Updater is not supported for current operating system or platform.
   #[error("Unsupported operating system or platform")]
   UnsupportedPlatform,
-  /// Public key found in `tauri.conf.json` but no signature announced remotely.
-  #[error("Signature not available, skipping update")]
-  MissingUpdaterSignature,
+  /// The platform was not found on the updater JSON response.
+  #[error("the platform `{0}` was not found on the response `platforms` object")]
+  TargetNotFound(String),
   /// 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")]
   UpToDate,
+  /// The server did not include the signature field.
+  #[error("the `{0}` field was not set on the updater response")]
+  MissingResponseField(&'static str),
+  /// The updater responded with an invalid signature type.
+  #[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")]
+  InvalidResponseType(&'static str, &'static str, serde_json::Value),
 }
 
 pub type Result<T = ()> = std::result::Result<T, Error>;