using Invercargill; using InvercargillJson; namespace Binman { public errordomain ManifestError { KEY_MISMATCH, INVALID_SIGNATURE } public class ManifestFile { public File file { get; private set; } public ManifestHeader read_header() throws Error { return new JsonlInputStream(get_stream()) .as_property_groups() .try_map_with(ManifestHeader.get_mapper()) .first() .unwrap(); } public Attempts get_entries() throws Error { return new JsonlInputStream(get_stream()) .as_property_groups() .skip(1) .until(o => o.has("signature")) .try_map_with(ManifestEntry.get_mapper()); } 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(); // 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 { var decompressor = new ZlibDecompressor(ZlibCompressorFormat.GZIP); return new ConverterInputStream(file.read(), decompressor); } public ManifestFile(File file) { this.file = file; } public ManifestFile.from_name(string name, ApplicationType type, BinaryData? key = null) throws Error { this.file = File.new_build_filename("/var/binman", name, ApplicationType.get_mapper().map_from(type)); if(!this.file.query_exists()) { throw new FileError.NOENT("Could not find the specified manifest"); } if(key != null && !read_header().key.equals(key)) { throw new ManifestError.KEY_MISMATCH("The key in the manifest file does not match the key expected"); } } } public class ManifestHeader { public string name { get; set; } public string description { get; set; } public int serial { get; set; } public string post_exec { get; set; } public BinaryData key { get; set; } public Vector remotes { get; set; } public static PropertyMapper get_mapper() { return PropertyMapper.build_for(cfg => { cfg.map("name", o => o.name, (o, v) => o.name = v); cfg.map("desc", o => o.description, (o, v) => o.description = v); cfg.map("serial", o => o.serial, (o, v) => o.serial = v); cfg.map("post-exec", o => o.post_exec, (o, v) => o.post_exec = v); cfg.map("key", o => o.key.to_base64(), (o, v) => o.key = new BinaryData.from_base64(v)); cfg.map_many("remotes", o => o.remotes, (o, v) => o.remotes = v.to_vector()); cfg.set_constructor(() => new ManifestHeader()); }); } } public class ManifestEntry { public string path { get; set; } public int user { get; set; } public int group { get; set; } public Posix.mode_t mode { get; set; } public string? target { get; set; } public string? licence { get; set; } public ManifestFileDescriptor? binary { get; set; } public ManifestFileDescriptor? source { get; set; } public static PropertyMapper get_mapper() { return PropertyMapper.build_for(cfg => cfg .map("path", o => o.path, (o, v) => o.path = v) .map("uid", o => o.user, (o, v) => o.user = v) .map("gid", o => o.group, (o, v) => o.group = v) .map("mod", o => (int)o.mode, (o, v) => o.mode = v) .map("target", o => o.target, (o, v) => o.target = v, false) .map("licence", o => o.licence, (o, v) => o.licence = v, false) .map_properties_with("bin", o => o.binary, (o, v) => o.binary = v, ManifestFileDescriptor.get_mapper(), false) .map_properties_with("ccs", o => o.source, (o, v) => o.source = v, ManifestFileDescriptor.get_mapper(), false) .set_constructor(() => new ManifestEntry()) ); } } public class ManifestFileDescriptor { public BinaryData checksum { get; set; } public uint64 size { get; set; } public string remote_path { get; set; } public static PropertyMapper get_mapper() { return PropertyMapper.build_for(cfg => cfg .map("checksum", o => o.checksum.to_base64(), (o, v) => o.checksum = new BinaryData.from_base64(v)) .map("size", o => o.size, (o, v) => o.size = v) .map("path", o => o.remote_path, (o, v) => o.remote_path = v) .set_constructor(() => new ManifestFileDescriptor()) ); } } public class ManifestSignature { public BinaryData signature { get; set; } public static PropertyMapper get_mapper() { return PropertyMapper.build_for(cfg => cfg .map("signature", o => o.signature.to_base64(), (o, v) => o.signature = new BinaryData.from_base64(v)) .set_constructor(() => new ManifestSignature()) ); } } }