Jelajahi Sumber

Initial commit

Billy Barrow 1 bulan lalu
melakukan
4a098ccb18

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/build
+/build2

+ 52 - 0
MANIFEST.usm

@@ -0,0 +1,52 @@
+{
+  "name": "manifest-utils",
+  "version": "0.0.1",
+  "summary": "Utilities for manageing Binman manifests",
+  "licences": [ {"name": "GPLv3", "category": "libre", "text": "src/LICENSE"} ],
+  "flags": [],
+  "provides": {
+    "bin:manifest-build": "as-expected",
+    "bin:manifest-sign": "as-expected",
+    "bin:manifest-verify": "as-expected",
+  },
+  "depends": {
+    "runtime": [
+      "lib:libglib-2.0.so.0",
+      "lib:libgobject-2.0.so.0",
+      "lib:libgio-2.0.so.0",
+      "lib:libinvercargill.so",
+      "lib:libinvercargill-json.so",
+      "lib:libc.so.6",
+      "lib:libpcre2-8.so.0",
+      "lib:libffi.so.8",
+      "lib:libgmodule-2.0.so.0",
+      "lib:libz.so.1",
+      "lib:libmount.so.1",
+      "lib:libselinux.so.1",
+      "lib:libgee-0.8.so.2",
+      "lib:libjson-glib-1.0.so.0",
+      "lib:libblkid.so.1"
+    ],
+    "build": [
+      "bin:valac",
+      "bin:meson",
+      "pc:glib-2.0.pc",
+      "pc:gobject-2.0.pc",
+      "pc:gio-2.0.pc",
+      "pc:gee-0.8.pc",
+      "pc:json-glib-1.0.pc",
+      "pc:invercargill.pc",
+      "pc:invercargill-json.pc",
+      "pc:binman.pc",
+      "pc:gobject-introspection-1.0.pc"
+    ],
+    "manage": [
+      "bin:bash"
+    ]
+  },
+  "execs": {
+    "build": "scripts/build.sh",
+    "acquire": "scripts/acquire.sh",
+    "install": "scripts/install.sh"
+  }
+}

+ 22 - 0
README.md

@@ -0,0 +1,22 @@
+# Manifest Utils for Binman
+
+- `manifest-verify FILE` verifies the signature of the manifest file FILE.
+- `manifest-build DIR [--name NAME] [--key KEYFILE] [--serial SERIAL] [--description DESCRIPTION] [--remote URI] [--post-exec POSTEXEC] [--output OUTDIR] [--source-finder SOURCEXEC] [--license-finder LICENSEEXEC] [--path-scheme SCHEME]` builds a manifest based on the structure in DIR.
+  - `--name` specifies the name of the manifest, defaults to the base name of DIR.
+  - `--key` specifies a key file to sign the manifest with, otherwise one will be created.
+  - `--serial` specifies a serial number for this manifest, defaults to 0.
+  - `--description` specifies a description for this manifest, defaults to the name of the manifest.
+  - `--remote` specifies a remote mirror for this manifest (may be specified more than once).
+  - `--post-exec` specifies the post-exec program to be run after the manifest is applied.
+  - `--prefix` specifies a prefix to be prepended to all paths in the resulting manifest, defaults to root "/".
+  - `--remote-bin-prefix` specifies a prefix for all remote binary files, defaults to "binaries".
+  - `--remote-ccs-prefix` specifies a prefix for all remote source packages, defaults to "sources".
+  - `--output` specifies a directory to save the manifest, key file, and folder structure to. When this is not specified, the build is considered "in-place" and the application will create a manifest (and key file where applicable) in the current working directory, no copies of the binary files will be made.
+  - `--source-finder` specifies a program to run on each entry in the filesystem to determine the path to the source package for that file.
+  - `--license-finder` specifies a program to run on each entry in the filesystem to determine the path to the license for that file.
+  - `--path-scheme` specifies the scheme for remote paths to use:
+    - `analogue` creates an output structure that mimics the structure of the input directory. This is the default and only valid option when `--output` is not specified.
+    - `content-hash` creates a flat output structure where each file is named by the SHA512 hash of its contents.
+    - `path-hash` creates a flat output structure where each file is named by the SHA256 hash of its path name.
+- `manifest-sign FILE KEYFILE` re-signs a manifest using the keys stored in KEYFILE.
+

+ 7 - 0
scripts/acquire.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+set -e
+
+git init
+git remote add origin https://git.sr.ht/~tilo15/manifest-utils || echo "Origin already exists"
+git fetch
+git reset --hard origin/master

+ 11 - 0
scripts/build.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+
+src_dir=$(pwd)
+build_dir=$1
+
+cd ${build_dir}
+
+meson setup ${src_dir}/src --prefix=${PREFIX} --libdir=${LIBDIR} --bindir=${BINDIR} --includedir=${INCLUDEDIR}
+ninja
+

+ 10 - 0
scripts/install.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+src_dir=$(pwd)
+build_dir=$1
+install_dir=$2
+
+cd ${build_dir}
+
+meson install --destdir ${install_dir}

+ 126 - 0
src/build/Arguments.vala

@@ -0,0 +1,126 @@
+using Invercargill;
+
+private class Arguments {
+
+    public File directory { get; set; }
+    public string? name { get; set; }
+    public string? key_file { get; set;}
+    public int? serial { get; set; }
+    public string? description { get; set; }
+    public Vector<string> remotes { get; set; }
+    public string? post_exec { get; set; }
+    public string? output_path { get; set; }
+    public string? source_finder_exec { get; set; }
+    public string? license_finder_exec { get; set; }
+    public PathScheme? path_scheme { get; set; } 
+    public string? prefix { get; set; }
+    public string? remote_binary_prefix { get; set; }
+    public string? remote_source_prefix { get; set; }
+    public bool quiet { get; set; }
+
+    public Arguments(string[] args) {
+        directory = File.new_for_commandline_arg(args[1]);
+        remotes = new Vector<string> ();
+        
+        for(var i = 2; i < args.length; i++) {
+            if(args[i] == "--quiet") {
+                quiet = true;
+                continue;
+            }
+            
+            // All remaining arguments are in pairs
+            if(i + 1 >= args.length) {
+                error(@"Missing value after \"$(args[i])\"");
+            }
+            switch (args[i]) {
+                case "--name":
+                    i++;
+                    name = args[i];
+                    break;
+
+                case "--key":
+                    i++;
+                    key_file = args[i];
+                    break;
+                
+                case "--serial":
+                    i++;
+                    serial = int.parse (args[i]);
+                    break;
+
+                case "--description":
+                    i++;
+                    description = args[i];
+                    break;
+
+                case "--remote":
+                    i++;
+                    remotes.add(args[i]);
+                    break;
+
+                case "--post-exec":
+                    i++;
+                    post_exec = args[i];
+                    break;
+
+                case "--output":
+                    i++;
+                    output_path = args[i];
+                    break;
+
+                case "--prefix":
+                    i++;
+                    prefix = args[i];
+                    break;
+
+                case "--remote-bin-prefix":
+                    i++;
+                    remote_binary_prefix = args[i];
+                    break;
+
+                case "--remote-ccs-prefix":
+                    i++;
+                    remote_source_prefix = args[i];
+                    break;
+
+                case "--source-finder":
+                    i++;
+                    source_finder_exec = args[i];
+                    break;
+
+                case "--license-finder":
+                    i++;
+                    license_finder_exec = args[i];
+                    break;
+
+                case "--path-scheme":
+                    i++;
+                    try {
+                        path_scheme = PathScheme.get_mapper().materialise(args[i]);
+                    }
+                    catch {
+                        error(@"Unknown path scheme \"$(args[i])\"");
+                    }
+                    break;
+
+                default:
+                    error(@"Unknown option \"$(args[i])\"");
+            }
+        }
+    }
+
+}
+
+private enum PathScheme {
+    ANALOGUE,
+    CONTENT_HASH,
+    PATH_HASH;
+
+    public static Mapper<PathScheme, string> get_mapper() {
+        return ValueMapper.build_for<PathScheme, string>(cfg => cfg
+            .map(PathScheme.ANALOGUE, "analogue")
+            .map(PathScheme.CONTENT_HASH, "content-hash")
+            .map(PathScheme.PATH_HASH, "path-hash")
+        );
+    }
+}

+ 227 - 0
src/build/Build.vala

@@ -0,0 +1,227 @@
+using Binman;
+using Invercargill;
+using InvercargillJson;
+
+public static int main(string[] args) {
+
+    if(args.length < 2 || args[1] == "--help" || args[1] == "-?") {
+        usage();
+        return 255;
+    }
+
+    var arguments = new Arguments(args);
+
+    try {
+        var header = new ManifestHeader();
+        header.name = arguments.name ?? arguments.directory.get_basename();
+        header.description = arguments.description ?? header.name;
+        header.remotes = arguments.remotes;
+        header.post_exec = arguments.post_exec;
+        header.serial = arguments.serial ?? 0;
+
+        var output_dir = arguments.output_path ?? Environment.get_current_dir();
+        var keyfile = arguments.key_file ?? generate_keyfile(output_dir);
+        header.key = get_public_key(keyfile);
+
+        var compressor = new ZlibCompressor(ZlibCompressorFormat.GZIP, 9);
+        var manifest_file = File.new_build_filename(output_dir, @"$(header.name).manifest");
+        var manifest_stream = new JsonlOutputStream(new ConverterOutputStream(manifest_file.create(FileCreateFlags.REPLACE_DESTINATION), compressor));
+        manifest_stream.write_object(ManifestHeader.get_mapper().map_from(header));
+
+        var queue = new Fifo<File>();
+        queue.push(arguments.directory);
+        queue.unblock();
+
+        var prefix = arguments.prefix ?? "/";
+        foreach (var item in queue) {
+            // Print log
+            write_line(arguments, @"Processing file $(item.get_path())");
+
+            // Gather information
+            Posix.Stat stat_info;
+            posix_to_error(Posix.stat(item.get_path(), out stat_info));
+            var relative_path = arguments.directory.get_relative_path(item);
+
+            // Create the entry
+            var entry = new ManifestEntry();
+            entry.path = Path.build_filename(prefix, relative_path);
+            entry.user = stat_info.st_uid;
+            entry.group = stat_info.st_gid;
+            entry.mode = stat_info.st_mode;
+
+            // Handle symlinks
+            if(Posix.S_ISLNK(entry.mode)) {
+                var target = new char[uint16.MAX];
+                target.length = (int)Posix.readlink(item.get_path(), target);
+                entry.target = (string)target;
+            }
+
+            // Handle regular (non-empty) files
+            if(Posix.S_ISREG(entry.mode) && stat_info.st_size > 0) {
+                var binary = new ManifestFileDescriptor();
+                binary.size = stat_info.st_size;
+                binary.checksum = calculate_file_checksum(item.get_path());
+                binary.remote_path = generate_remote_binary_path(arguments, entry, binary.checksum);
+                entry.binary = binary;
+
+                // If we are copying files, copy the file
+                if(arguments.output_path != null) {
+                    var dest = File.new_build_filename(arguments.output_path, binary.remote_path);
+                    dest.get_parent().make_directory_with_parents();
+                    item.copy(dest, FileCopyFlags.TARGET_DEFAULT_PERMS | FileCopyFlags.TARGET_DEFAULT_MODIFIED_TIME);
+                }
+            }
+
+            // Handle directories
+            if(Posix.S_ISDIR(entry.mode)) {
+                var children = directory(item.get_path());
+                queue.push_all(children.select<File>(c => item.get_child(c)));
+            }
+
+            // Query for license information
+            if(arguments.license_finder_exec != null) {
+                var license_path = get_result_from_exec(arguments.license_finder_exec, item.get_path());
+                entry.licence = Path.build_filename(prefix, license_path);
+            }
+
+            // Query for source information TODO
+            if(arguments.source_finder_exec != null) {
+                var source_package_path = get_result_from_exec(arguments.source_finder_exec, item.get_path());
+                if(source_package_path != null && source_package_path.length > 0) {
+                    var source_package = File.new_for_path(source_package_path);
+                    var file_info = source_package.query_info("*", FileQueryInfoFlags.NONE);
+                    var source = new ManifestFileDescriptor();
+                    source.size = file_info.get_size();
+                    source.checksum = calculate_file_checksum(source_package_path);
+                    source.remote_path = generate_remote_source_path(arguments, source_package, source.checksum);
+                    entry.source = source;
+
+                    // If we are copying files, and the file hasn't been copied, copy the file
+                    var dest =  File.new_build_filename(arguments.output_path, source.remote_path);
+                    if(arguments.output_path != null && !dest.query_exists()) {
+                        dest.get_parent().make_directory_with_parents();
+                        source_package.copy(dest, FileCopyFlags.TARGET_DEFAULT_PERMS | FileCopyFlags.TARGET_DEFAULT_MODIFIED_TIME);
+                    }
+                }
+            }
+
+            // Write the entry to the manifest
+            manifest_stream.write_object(ManifestEntry.get_mapper().map_from(entry));
+        }
+
+        // Generate signature
+        write_line(arguments, "Signing manifest...");
+        manifest_stream.flush();
+        var signature = new ManifestSignature.generate(manifest_file, get_signing_key(keyfile).to_array());
+
+        // Write signature
+        manifest_stream.write_object(ManifestSignature.get_mapper().map_from(signature));
+        manifest_stream.flush();
+        manifest_stream.close();
+
+    }
+    catch(Error e) {
+        printerr(@"Failed to build manifest: $(e.message)\n");
+        return e.code;
+    }
+
+    write_line(arguments, "Complete!");
+    return 0;
+}
+
+private void usage() {
+    printerr("USAGE:\n\tmanifest-build DIR [--name NAME] [--key KEYFILE] [--serial SERIAL] [--description DESCRIPTION] [--remote URI] [--post-exec POSTEXEC] [--output OUTDIR [--path-scheme SCHEME]] [--source-finder SOURCEXEC] [--license-finder LICENSEEXEC]\n");
+}
+
+private string generate_keyfile(string output_dir) throws Error {
+    var public_key = new uint8[Sodium.Asymmetric.Signing.PUBLIC_KEY_BYTES];
+    var secret_key = new uint8[Sodium.Asymmetric.Signing.SECRET_KEY_BYTES];
+    Sodium.Asymmetric.Signing.generate_keypair(public_key, secret_key);
+
+    var keyfile_data = new BinaryData.from_byte_array(public_key).to_base64();
+    keyfile_data += ".";
+    keyfile_data += new BinaryData.from_byte_array(secret_key).to_base64();
+
+    var path = Path.build_filename(output_dir, "keyfile.secret");
+    FileUtils.set_contents(path, keyfile_data, keyfile_data.length);
+    return path;
+}
+
+private BinaryData get_public_key(string keyfile) throws Error {
+    string contents;
+    FileUtils.get_contents(keyfile, out contents);
+    return new BinaryData.from_base64(contents.split(".")[0]);
+}
+
+private BinaryData get_signing_key(string keyfile) throws Error {
+    string contents;
+    FileUtils.get_contents(keyfile, out contents);
+    return new BinaryData.from_base64(contents.split(".")[1]);
+}
+
+private string generate_remote_binary_path(Arguments arguments, ManifestEntry entry, BinaryData checksum) {
+    var scheme = arguments.path_scheme ?? PathScheme.ANALOGUE;
+    
+    // Analogue is forced if no output path is specified, because we don't actually copy anything
+    if(scheme == PathScheme.ANALOGUE || arguments.output_path == null) {
+        var prefix_path = File.new_for_path(arguments.prefix ?? "/");
+        var relative_path = prefix_path.get_relative_path(File.new_for_path(entry.path));
+        return Path.build_filename(arguments.remote_binary_prefix ?? "binaries", relative_path);
+    }
+
+    if(scheme == PathScheme.CONTENT_HASH) {
+        return checksum.to_base64().replace("/", "_");
+    }
+
+    if(scheme == PathScheme.PATH_HASH) {
+        var hasher = new Checksum(ChecksumType.SHA256);
+        hasher.update(entry.path.data, entry.path.data.length);
+        var result = new uint8[ChecksumType.SHA256.get_length()];
+        size_t size = result.length;
+        hasher.get_digest(result, ref size);
+        return new BinaryData.from_byte_array(result).to_base64().replace("/", "_");
+    }
+
+    assert_not_reached();
+}
+
+private string generate_remote_source_path(Arguments arguments, File source_package, BinaryData checksum) {
+    var scheme = arguments.path_scheme ?? PathScheme.ANALOGUE;
+    
+    // Analogue is forced if no output path is specified, because we don't actually copy anything
+    if(scheme == PathScheme.ANALOGUE || arguments.output_path == null) {
+        return Path.build_filename(arguments.remote_source_prefix ?? "sources", source_package.get_basename());
+    }
+
+    if(scheme == PathScheme.CONTENT_HASH) {
+        return checksum.to_base64().replace("/", "_");
+    }
+
+    if(scheme == PathScheme.PATH_HASH) {
+        var hasher = new Checksum(ChecksumType.SHA256);
+        var path = source_package.get_basename();
+        hasher.update(path.data, path.data.length);
+        var result = new uint8[ChecksumType.SHA256.get_length()];
+        size_t size = result.length;
+        hasher.get_digest(result, ref size);
+        return new BinaryData.from_byte_array(result).to_base64().replace("/", "_");
+    }
+
+    assert_not_reached();
+}
+
+private string get_result_from_exec(string exec, string param) throws Error {
+    var process = new Subprocess.newv(new string[] {exec, param}, GLib.SubprocessFlags.STDOUT_PIPE);
+    var stream = process.get_stdout_pipe();
+    var buffer = new uint8[uint16.MAX];
+    size_t bytes_read;
+    stream.read_all(buffer, out bytes_read);
+    return ((string)buffer).chomp().chug();
+}
+
+private void write_line(Arguments arguments, string line) {
+    if(arguments.quiet){
+        return;
+    }
+    printerr(@"$line\n");
+}

+ 5 - 0
src/build/meson.build

@@ -0,0 +1,5 @@
+
+sources = files('Build.vala')
+sources += files('Arguments.vala')
+
+executable('manifest-build', sources, dependencies: dependencies, install: true)

+ 21 - 0
src/meson.build

@@ -0,0 +1,21 @@
+project('manifest-utils', 'vala', 'c')
+vapi_dir = meson.current_source_dir() / 'vapi'
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gee-0.8'),
+    dependency('json-glib-1.0'),
+    dependency('invercargill'),
+    dependency('invercargill-json'),
+    dependency('binman'),
+    meson.get_compiler('vala').find_library('posix'),
+    meson.get_compiler('vala').find_library('linux'),
+    meson.get_compiler('vala').find_library('libsodium', dirs: vapi_dir),
+    meson.get_compiler('c').find_library('sodium'),
+]
+
+subdir('build')
+subdir('verify')
+subdir('sign')

+ 15 - 0
src/sign/Sign.vala

@@ -0,0 +1,15 @@
+
+public static int main(string[] args) {
+
+    if(args.length < 3) {
+        usage();
+        return 255;
+    }
+
+    printerr("Not yet implemented\n");
+    return 0;
+}
+
+private void usage() {
+    printerr("USAGE:\n\tmanifest-sign FILE KEYFILE\n");
+}

+ 4 - 0
src/sign/meson.build

@@ -0,0 +1,4 @@
+
+sources = files('Sign.vala')
+
+executable('manifest-sign', sources, dependencies: dependencies, install: true)

+ 237 - 0
src/vapi/libsodium.vapi

@@ -0,0 +1,237 @@
+/* Vala Bindings for LibSodium
+ * Copyright (c) 2020 Billy Barrow <billyb@pcthingz.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+ [CCode (cheader_filename = "sodium.h", lower_case_cprefix = "sodium_")]
+ namespace Sodium {
+ 
+   namespace Random {
+     [CCode (cname = "randombytes_SEEDBYTES")]
+     public const size_t SEED_BYTES;
+   
+     [CCode (cname = "randombytes_random")]
+     public uint32 random();
+   
+     [CCode (cname = "randombytes_uniform")]
+     public uint32 random_uniform(uint32 upper_bound);
+   
+     [CCode (cname = "randombytes_buf")]
+     public void random_bytes(uint8[] buffer);
+   
+     [CCode (cname = "randombytes_buf_deterministic")]
+     public void random_bytes_deterministic(uint8[] buffer, uint8[] seed);
+   }
+ 
+   namespace Symmetric {
+     [CCode (cname = "crypto_secretbox_KEYBYTES")]
+     public const size_t KEY_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_NONCEBYTES")]
+     public const size_t NONCE_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_MACBYTES")]
+     public const size_t MAC_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_keygen")]
+     private void key_gen([CCode (array_length = false)]uint8[] key);
+ 
+     public uint8[] generate_key() {
+       uint8[] key = new uint8[KEY_BYTES];
+       key_gen(key);
+       return key;
+     }
+ 
+     [CCode (cname = "crypto_secretbox_easy")]
+     private void secretbox(
+       [CCode (array_length = false)]uint8[] ciphertext,
+       uint8[] message,
+       [CCode (array_length = false)]uint8[] nonce,
+       [CCode (array_length = false)]uint8[] key
+     );
+ 
+     public uint8[] encrypt(uint8[] message, uint8[] key, uint8[] nonce)
+       requires (key.length == KEY_BYTES) 
+       requires (nonce.length == NONCE_BYTES)
+     {
+       // Initialise array for ciphertext
+       size_t ciphertext_size = MAC_BYTES + message.length;
+       uint8[] ciphertext = new uint8[ciphertext_size];
+ 
+       // Encrypt
+       secretbox(ciphertext, message, nonce, key);
+ 
+       // Return ciphertext
+       return ciphertext;
+     }
+ 
+     [CCode (cname = "crypto_secretbox_open_easy")]
+     private int secretbox_open(
+       [CCode (array_length = false)]uint8[] message,
+       uint8[] ciphertext,
+       [CCode (array_length = false)]uint8[] nonce,
+       [CCode (array_length = false)]uint8[] key
+     );
+ 
+     public uint8[]? decrypt(uint8[] ciphertext, uint8[] key, uint8[] nonce)
+       requires (ciphertext.length > MAC_BYTES)
+       requires (key.length == KEY_BYTES) 
+       requires (nonce.length == NONCE_BYTES)
+     {
+       // Initialise array for message
+       size_t message_size = ciphertext.length - MAC_BYTES;
+       uint8[] message = new uint8[message_size];
+ 
+       // Decrypt
+       int status = secretbox_open(message, ciphertext, nonce, key);
+ 
+       // Did it work?
+       if(status != 0) {
+         // No, return null
+         return null;
+       }
+ 
+       return message;
+     }
+   }
+   
+   namespace Asymmetric {
+ 
+     namespace Signing {
+ 
+         [CCode (cname = "crypto_sign_PUBLICKEYBYTES")]
+         public const size_t PUBLIC_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_sign_SECRETKEYBYTES")]
+         public const size_t SECRET_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_sign_BYTES")]
+         public const size_t MAX_HEADER_BYTES;
+ 
+         [CCode (cname = "crypto_sign_keypair")]
+         public void generate_keypair(
+             [CCode (array_length = false)]uint8[] public_key,
+             [CCode (array_length = false)]uint8[] secret_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES);
+             
+         [CCode (cname = "crypto_sign")]
+         private void sign_message(
+             [CCode (array_length = false)] uint8[] signed_message,
+             out int signature_length,
+             uint8[] message,
+             [CCode (array_length = false)] uint8[] secret_key
+         );
+ 
+         public uint8[] sign(
+             uint8[] message,
+             uint8[] secret_key)
+             requires (secret_key.length == SECRET_KEY_BYTES)
+         {
+             int signature_length;
+             uint8[] signed_message = new uint8[MAX_HEADER_BYTES + message.length];
+             sign_message(signed_message, out signature_length, message, secret_key);
+             signed_message.resize(signature_length);
+ 
+             return signed_message;
+         }
+ 
+         [CCode (cname = "crypto_sign_open")]
+         private int sign_open(
+             [CCode (array_length = false)] uint8[] message,
+             out int message_length,
+             uint8[] signed_message,
+             [CCode (array_length = false)] uint8[] public_key
+         );
+ 
+         public uint8[]? verify(
+             uint8[] signed_message,
+             uint8[] public_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+         {
+             int message_length;
+             uint8[] message = new uint8[signed_message.length];
+             if(sign_open(message, out message_length, signed_message, public_key) != 0) {
+                 return null;
+             }
+             message.resize(message_length);
+ 
+             return message;
+         }
+ 
+     }
+ 
+     namespace Sealing {
+ 
+         [CCode (cname = "crypto_box_PUBLICKEYBYTES")]
+         public const size_t PUBLIC_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_box_SECRETKEYBYTES")]
+         public const size_t SECRET_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_box_SEALBYTES")]
+         public const size_t HEADER_BYTES;
+ 
+         [CCode (cname = "crypto_box_keypair")]
+         public void generate_keypair(
+             [CCode (array_length = false)]uint8[] public_key,
+             [CCode (array_length = false)]uint8[] secret_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES);
+ 
+         [CCode (cname = "crypto_box_seal")]
+         private void seal_message(
+             [CCode (array_length = false)] uint8[] ciphertext,
+             uint8[] message,
+             [CCode (array_length = false)] uint8[] public_key
+         );
+ 
+         public uint8[] seal(uint8[] message, uint8[] public_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+         {
+             uint8[] ciphertext = new uint8[HEADER_BYTES + message.length];
+             seal_message(ciphertext, message, public_key);
+             return ciphertext;
+         }
+ 
+         [CCode (cname = "crypto_box_seal_open")]
+         private int seal_open(
+             [CCode (array_length = false)] uint8[] message,
+             uint8[] ciphertext,
+             [CCode (array_length = false)] uint8[] public_key,
+             [CCode (array_length = false)] uint8[] secret_key
+         );
+ 
+         public uint8[]? unseal(
+             uint8[] ciphertext,
+             uint8[] public_key,
+             uint8[] secret_key) 
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES)
+             requires (ciphertext.length > HEADER_BYTES)
+         {
+             uint8[] message = new uint8[ciphertext.length - HEADER_BYTES];
+             if(seal_open(message, ciphertext, public_key, secret_key) != 0){
+                 return null;
+             }
+             return message;
+         }
+         
+     }
+ 
+   }
+   
+ 
+ }

+ 28 - 0
src/verify/Verify.vala

@@ -0,0 +1,28 @@
+
+public static int main(string[] args) {
+
+    if(args.length < 2) {
+        usage();
+        return 255;
+    }
+
+    try {
+        var manifest_file = new Binman.ManifestFile(File.new_for_commandline_arg (args[1]));
+        if(manifest_file.verify_signature()) {
+            printerr("Manifest signature OK\n");
+            return 0;
+        }
+
+        printerr("Invalid manifest signature\n");
+        return -1;
+
+    }
+    catch(Error e) {
+        printerr(@"Failed to verify manifest: $(e.message)\n");
+        return e.code;
+    }
+}
+
+private void usage() {
+    printerr("USAGE:\n\tmanifest-verify FILE\n");
+}

+ 4 - 0
src/verify/meson.build

@@ -0,0 +1,4 @@
+
+sources = files('Verify.vala')
+
+executable('manifest-verify', sources, dependencies: dependencies, install: true)