Browse Source

Basic implementation of updater

Billy Barrow 1 month ago
parent
commit
1a3e3d185b
2 changed files with 56 additions and 54 deletions
  1. 32 4
      src/lib/Manifest.vala
  2. 24 50
      src/lib/ManifestUpdater.vala

+ 32 - 4
src/lib/Manifest.vala

@@ -27,10 +27,38 @@ namespace Binman {
                 .try_map_with<ManifestEntry>(ManifestEntry.get_mapper());
                 .try_map_with<ManifestEntry>(ManifestEntry.get_mapper());
         }
         }
 
 
-        //  public bool verify_signature() throws Error {
-        //      var stream = new DataInputStream(file.read());
-        //      var checksum = 
-        //  }
+        public bool verify_signature() throws Error {
+            var stream = new DataInputStream(get_stream());
+            var checksum = new Checksum(ChecksumType.SHA512);
+            ManifestSignature signature;
+            ManifestHeader header = null;
+
+            while (true) {
+                var line = stream.read_line();
+                var obj = new JsonElement.from_string(line).as<JsonObject>();
+
+                // Don't include signature object in checksum
+                if(obj.has("signature")) {
+                    signature = ManifestSignature.get_mapper().materialise(obj);
+                    break;
+                }
+
+                checksum.update(line.data, line.data.length);
+
+                if(header == null) {
+                    header = ManifestHeader.get_mapper().materialise(obj);
+                }
+            }
+
+            // Calculate checksum
+            var checksum_bytes = new uint8[ChecksumType.SHA512.get_length()];
+            size_t size = checksum_bytes.length;
+            checksum.get_digest(checksum_bytes, ref size);
+
+            // Verify signature
+            var signed_checksum = Sodium.Asymmetric.Signing.verify(signature.signature.to_array(), header.key.to_array());
+            return new BinaryData.from_byte_array(signed_checksum).equals(new BinaryData.from_byte_array(checksum_bytes));
+        }
 
 
         private InputStream get_stream() throws Error {
         private InputStream get_stream() throws Error {
             var decompressor = new ZlibDecompressor(ZlibCompressorFormat.GZIP);
             var decompressor = new ZlibDecompressor(ZlibCompressorFormat.GZIP);

+ 24 - 50
src/lib/ManifestUpdater.vala

@@ -20,26 +20,20 @@ namespace Binman {
                     min_serial = new ManifestFile(latest_syml).read_header().serial;
                     min_serial = new ManifestFile(latest_syml).read_header().serial;
                 }
                 }
 
 
-
-
+                download_manifest(component, min_serial, null);
             }
             }
 
 
         }
         }
 
 
+        private File download_manifest(CompositionComponent component, int64 min_serial = 0, FileProgressCallback? progress = null) throws IOError {
 
 
-        private File download_manifest(CompositionComponent component, int64 min_serial = 0) throws IOError {
-
-            var downloaded = false;
+            File result = null;
             foreach (var uri in component.uris) {
             foreach (var uri in component.uris) {
-                DataInputStream stream = null;
                 try {
                 try {
+                    // Peek at the header before downloading the whole thing
                     var remote_file = File.new_for_uri(uri);
                     var remote_file = File.new_for_uri(uri);
-                    var decompressor = new ZlibDecompressor(ZlibCompressorFormat.GZIP);
-                    stream = new DataInputStream(new ConverterInputStream(remote_file.read(), decompressor));
-                    
-                    // Read header
-                    var header_line = stream.read_line();
-                    var header = ManifestHeader.get_mapper().materialise(new JsonElement.from_string(header_line).as<JsonObject>());
+                    var remote_manifest = new ManifestFile(remote_file);
+                    var header = remote_manifest.read_header();
                     
                     
                     if(header.serial < min_serial){
                     if(header.serial < min_serial){
                         continue;
                         continue;
@@ -49,55 +43,35 @@ namespace Binman {
                         throw new ManifestError.KEY_MISMATCH("Key in manifest header does not match key in component");
                         throw new ManifestError.KEY_MISMATCH("Key in manifest header does not match key in component");
                     }
                     }
                     
                     
-                    // Download and verify file
-                    FileIOStream tmp_stream;
-                    var download_tmp = File.new_tmp(null, out tmp_stream);
-                    var checksum = new Checksum(ChecksumType.SHA512);
-                    var compressor = new ZlibCompressor(ZlibCompressorFormat.GZIP, 9);
-                    var save_stream = new DataOutputStream(new ConverterOutputStream(tmp_stream.output_stream, compressor));
-
-                    var line = header_line;
-                    ManifestSignature signature = null;
-                    while (true) {
-                        save_stream.put_string(line);
-                        
-                        var obj = new JsonElement.from_string(line).as<JsonObject>();
-                        // Don't include signature object in checksum
-                        if(obj.has("signature")) {
-                            signature = ManifestSignature.get_mapper().materialise(obj);
-                            break;
-                        }
-                        checksum.update(line.data, line.data.length);
+                    // Download the file
+                    var manifest_tmp = File.new_build_filename(Environment.get_tmp_dir(), "binman-manifest-" + Uuid.string_random());
+                    remote_file.copy(manifest_tmp, FileCopyFlags.NONE, null, progress);
+
+                    // Verify the manifest
+                    var local_manifest = new ManifestFile(manifest_tmp);
+                    var local_manifest_header = local_manifest.read_header();
+                    if(!local_manifest.verify_signature()) {
+                        throw new ManifestError.INVALID_SIGNATURE(@"Invalid signature on manifest $(local_manifest_header.name)");
                     }
                     }
 
 
-                    // Calculate checksum
-                    var checksum_bytes = new uint8[ChecksumType.SHA512.get_length()];
-                    size_t size = checksum_bytes.length;
-                    checksum.get_digest(checksum_bytes, ref size);
-
-                    // Verify signature
-                    var signed_checksum = Sodium.Asymmetric.Signing.verify(signature.signature.to_array(), component.key.to_array());
-                    if(!new BinaryData.from_byte_array(signed_checksum).equals(new BinaryData.from_byte_array(checksum_bytes))) {
-                        throw new ManifestError.INVALID_SIGNATURE("Downloaded manifest has invalid signature");
-                    }
-                    
                     // Install new manifest
                     // Install new manifest
-                    var dest = File.new_build_filename("/var/binman/", component.name, )
+                    var dest = File.new_build_filename("/var/binman/", component.name, local_manifest_header.serial);
+                    manifest_tmp.move(dest, FileCopyFlags.TARGET_DEFAULT_MODIFIED_TIME | FileCopyFlags.TARGET_DEFAULT_PERMS);
+
+                    // Update "latest" symlink
+                    Applicator.posix_to_error(Posix.symlink(dest.get_path(), Path.build_filename("/var/binman/", component.name, "latest")));
+                    result = dest;
 
 
                 }
                 }
                 catch(Error e) {
                 catch(Error e) {
-                    warning(@"Download \"$(descriptor.remote_path)\" from remote \"$remote\" failed: $(e.message)");
-                }
-                finally {
-                    if(stream != null) {
-                        stream.close();
-                    }
+                    warning(@"Download of manifest \"$(component.name)\" from $(uri) failed: $(e.message)");
                 }
                 }
             }
             }
 
 
-            if(!downloaded) {
+            if(result == null) {
                 throw new IOError.FAILED(@"Could not download manifest for component \"$(component.name)\", all remotes were tried.");
                 throw new IOError.FAILED(@"Could not download manifest for component \"$(component.name)\", all remotes were tried.");
             }
             }
+            return result;
         }
         }