Table of Contents↗️

filepack

crates.io version build status downloads

Filepack is a content-addressed package format with self-describing metadata. A package is a directory of files and a manifest containing their BLAKE3 hashes. File and directory hashes form a Merkle tree whose root hash, the package fingerprint, uniquely identifies the package. Packages can be signed with Ed25519 and verified against a manifest, fingerprint, or signature. Packages may contain machine-readable metadata describing their content, allowing programmatic search, preview, playback, and conversion. `filepack` is a command-line tool for creating, signing, and verifying packages, and includes an HTTP server for package upload, download, and display. A demo server with a selection of example packages is available at [filepack.com](https://filepack.com). Filepack is currently experimental. The `filepack` interface and package format may change at any time. Quickstart ---------- Optionally, add metadata describing your package: ```sh echo "title: Packaging Guide" > metadata.yaml ``` Create `manifest.filepack`: ```shell filepack create ``` Verify current directory against hashes in `manifest.filepack`: ```shell filepack verify ``` Print package fingerprint: ```shell filepack fingerprint ``` Print package metadata: ```shell filepack metadata ``` Installation ------------ `filepack` is written in [Rust](https://www.rust-lang.org/) and can be built from source and installed from a checked-out copy of this repo with: ```sh cargo install --locked --path . ``` Or from [crates.io](https://crates.io/crates/filepack) with: ```sh cargo install --locked filepack ``` See [rustup.rs](https://rustup.rs/) for installation instructions for Rust. ### Pre-Built Binaries Pre-built binaries for Linux, MacOS, and Windows can be found on [the releases page](https://github.com/casey/filepack/releases). You can use the following command on Linux, MacOS, or Windows to download the latest release, just replace `DEST` with the directory where you'd like to put `filepack`: ```sh curl --proto '=https' --tlsv1.2 -sSf https://filepack.com/install.sh | bash -s -- --to DEST ``` For example, to install `filepack` to `~/bin`: ```sh # create ~/bin mkdir -p ~/bin # download and extract filepack to ~/bin/filepack curl --proto '=https' --tlsv1.2 -sSf https://filepack.com/install.sh | bash -s -- --to ~/bin # add `~/bin` to the paths that your shell searches for executables # this line should be added to your shell's initialization file, # e.g. `~/.bashrc` or `~/.zshrc` export PATH="$PATH:$HOME/bin" # filepack should now be executable filepack --help ``` Note that `install.sh` may fail on GitHub Actions or in other environments where many machines share IP addresses. `install.sh` calls GitHub APIs in order to determine the latest version of `filepack` to install, and those API calls are rate-limited on a per-IP basis. To make `install.sh` more reliable in such circumstances, pass a specific tag to install with `--tag`. Usage ----- Filepack supports a number of subcommands, including `filepack create` to create a manifest, `filepack verify` to verify a manifest, and `filepack serve` to start an HTTP package server. See `filepack help` for supported subcommands and `filepack help SUBCOMMAND` for information about a particular subcommand. ### `filepack create` Create a manifest. Recommended lints can be enabled with: ```shell filepack create --deny distribution ``` ### `filepack verify` Verify the contents of a directory against a manifest. To verify the contents of `DIR` against `DIR/manifest.filepack`: ```shell filepack verify DIR ``` If the current directory contains `manifest.filepack`, `DIR` can be omitted: ```shell filepack verify ``` `filepack verify` takes an optional `--print` flag, which prints the manifest to standard output if verification succeeds. This can be used in a pipeline to ensure that you the manifest has been verified before proceeding: ```shell filepack verify --print | jq ``` ### `filepack serve` Start HTTP server. The filepack server has limited authentication and no disk usage quota and should be considered experimental. To serve HTTP on `0.0.0.0:80`: ```shell filepack serve ``` To serve HTTPS on `0.0.0.0:443` with an ACME TLS certificate: ```shell filepack serve --https --domain filepack.example ``` See `filepack serve --help` for more details. Files and packages can be uploaded with `filepack upload` and downloaded with `filepack download`. Packages can be deleted with `filepack delete`. Data Directory -------------- Filepack stores local data, including private keys, in the filepack data directory, located in `~/.filepack`, or `$XDG_DATA_HOME/filepack` if `$XDG_DATA_HOME` is set and non-empty. Filepack stores private keys in the `keychain` subdirectory of the filepack data directory, by default in `~/.filepack/keychain`. The location of the filepack data directory used by a command can be overridden with the `--data-dir` option or the `FILEPACK_DATA_DIR` environment variable. Manifest -------- `filepack` manifests are conventionally named `manifest.filepack` and are placed alongside the files they reference. Manifests are encoded with deco, a compact binary serialization format, and may be converted to JSON for inspection or manipulation with `filepack manifest`. Manifests, when converted to JSON, are an object with three mandatory keys, `embedded`, `package`, and `signatures`. ### `embedded` The value of the mandatory `embedded` key is an object mapping BLAKE3 hashes to hex-encoded file contents. The `package` object only includes file names, hashes, and sizes, but files may be embedded in the manifest archive in the `embedded` map. This is currently only used to embed `metadata.filemeta`. ### `package` The value of the mandatory `package` key is an object mapping path components to directory entries. Directory entries may be subdirectories or files. Files are objects with two fields: `hash`, the tagged hex BLAKE3 hash of the file, and `size`, the length of the file in bytes. Path components are UTF-8 and may not be `.` or `..`, contain the path separators `/` or `\`, contain control characters, or begin with a Windows drive prefix, such as `C:`. ### `signatures` The value of the mandatory `signatures` key is an array of signatures. Signatures are tagged hex strings that include an Ed25519 key, the package fingerprint the signature is made over, an optional timestamp, and the signature itself. Public keys are Curve25519 points and signatures are Ed25519 signatures made over the hash of a serialized deco byte string containing the package fingerprint, which commits to the content of `package`, and an optional timestamp. ### Example A manifest converted to JSON over a directory containing the files `README.md` and `src/main.rs`, with a signature: ```json { "embedded": {}, "package": { "README.md": { "hash": "hash1fc253b84551ce6b00e820a826ac18054dc7f63a318ce62f3175315f5c467a62a", "size": 11883 }, "src": { "main.rs": { "hash": "hash11fa48b95ed335369d45b91af8138bdccd1413364bcdbfa6e9034e8a2cfd6e17f", "size": 33 } } }, "signatures": ["…"] } ``` The signature is elided for brevity. Signatures are tagged hex strings containing a public key and signature. Tagged Hex Strings ------------------ Tagged hex strings begin with a tag identifying the type, followed by a `1`, followed by the the encoded hex payload. The tags are: | tag | type | |---|---| | `hash1…` | hash | | `package1…` | package fingerprint | | `private1…` | private key | | `public1…` | public key | | `revision1…` | revision hash | | `signature1…` | signature | | `token1…` | authentication token | Metadata -------- Filepack packages may contain a file named `metadata.filemeta` describing the package and its contents. If present, `metadata.filemeta` is also embedded in the `manifest.filepack` archive. Metadata is authored by creating a file named `metadata.yaml` in the root of a new package. `filepack create` then loads `metadata.yaml` if present, checks for validity and unknown fields, and writes the deco serialization to `metadata.filemeta` in the package root. `metadata.yaml` is retained as a human-readable reference and for amending metadata, but `metadata.filemeta` is the authoritative source of metadata. For consumption by scripts and tools, `filepack metadata` prints the contents of `metadata.filemeta` as JSON. Filepack metadata is intended to be a broadly useful machine and human readable description of the contents of a package, covering personal, distribution, and archival use-cases. Metadata follows a fixed schema and is not user-extensible. Future versions of `filepack` may define new metadata fields, causing verification errors if those fields are present and invalid according to the new schema. Please feel free to open an issue with ideas for new metadata fields. ### Schema This schema is for the YAML authoring format and the JSON output of `filepack metadata`. The deco schema is currently undocumented. Fields are given as `NAME: TYPE`. All fields are optional. Top-level fields: - `artwork: path.{jpg,png}`: The path of a JPEG or PNG file containing artwork for the content, for example, cover art for an album or key art for a movie. - `creator: text`: The person or group who created the content. - `description: text`: A description of the content. - `homepage: url`: Primary URL for the content. Should be the official homepage of the content, if any, and not, for example, a Wikipedia or media database link. - `language: language`: The primary language of the content. - `media: object`: The media metadata. - `package: object`: The package metadata. - `publisher: text`: The person or group who published the content. - `readme: path.md`: The path of the content readme. - `time: time`: The time the content was created or released. - `title: text`: The content's human-readable title. Fields of `media` containing media-specific metadata: - `type: {audio,image,video,web}`: The media type. If the media type is `audio`, `image`, or `video`, the media object contains a field named `items`, which is a list of objects containing metadata for individual items in the package. When authoring metadata YAML, each item is an object with a `path` field containing the path to the item. For example, for an `audio` package: ```yaml media: type: audio items: - path: foo.flac - path: bar.flac ``` Additionally, `video` items have an optional `placeholder` field with a path to an image that should be used as a stand-in before the video begins playing: ```yaml media: type: video items: - path: foo.webm placeholder: foo.jpg - path: bar.webm placeholder: bar.jpg ``` `filepack create` populates binary filepack metadata with metadata extracted from each item. Fields of `package` describing the package itself, as opposed its content: - `colophon: path.md`: The path of the package colophon. - `creator: text`: The person or group who created the package. - `description: text`: A description of the package. - `homepage: url`: Primary URL for the package. - `time: time`: The time the package was created. - `title: text`: The package's title. May contain details related to the source, packager, or encoding. Types: - `language`: A string containing an ISO 639-1 two-character language code. See `filepack languages` for valid language codes. - `path`: A string containing a relative path consisting of `/`-separated components. - `path.EXTENSION`: A path that must end with `.EXTENSION`. - `text`: A plain text string. May not contain control characters, including newlines and tabs. - `time`: A string containing a time with one of two precisions, a year only, or a year, month, and day, written `Y-MM-DD`. Times use the proleptic Gregorian calendar with astronomical year numbering. - `url`: A string containing a URL with scheme `http` or `https`. Example times: ```tsv 0 -44 1929 -13787000000 0-01-01 929-01-01 1970-01-01 -44-03-15 ``` ### Example ```yaml title: Tobin's Spirit Guide creator: John Horace Tobin publisher: Miskatonic University Press artwork: cover.png time: 1929 description: A compilation of supernatural occurrences, entities, and facts. homepage: https://tobin-society.org/spirit-guide language: en readme: README.md package: title: Tobin's Spirit Guide - First Edition creator: Egon Spengler time: 1984-07-08 description: >- First edition on loan from NYPL Main Branch research stacks. Captured via Microtek MS-300A flatbed scanner. homepage: https://ghost-busters.net/~egon colophon: COLOPHON.md ``` The `homepage` URLs are of course anachronistic, as the World Wide Web was created in 1989, some years after Egon first packaged Tobin's Spirit Guide. Lints ----- `filepack create` supports optional lints to check your package for issues: ```shell filepack create --deny distribution ``` The `distribution` lint group checks for issues which can cause problems if the package is intended for distribution, such as non-portable paths that are illegal on Windows, paths which would conflict on case-insensitive file systems, and inclusion of junk files such as `.DS_Store`. Lint groups and the lints they contain may be printed with: ```shell filepack lints ``` Individual lints may be allowed with `--allow`: ```shell filepack create --deny distribution --allow windows-reserved-filename ``` Keys and Signatures ------------------- `filepack` supports the generation of [Curve25519](https://en.wikipedia.org/wiki/Curve25519) public/private keypairs, and the creation and verification of [EdDSA](https://en.wikipedia.org/wiki/EdDSA) signatures over manifests. ### Keypair Generation Keypairs are generated with: ```shell filepack keygen ``` Which creates `master.public` and `master.private` files in the `keychain` subdirectory of the filepack data directory. ### Public Key Printing Generated public keys can be printed with: ```shell filepack key ``` ### Signing Signatures are created with: ```shell filepack sign ``` Which signs the manifest in the current directory with your master key and adds the signature to the manifest's `signatures` array. Signatures are made over a fingerprint hash, recursively calculated from the contents of the manifest. ### Signature Verification Signatures embedded in a manifest are verified whenever a manifest is verified. The presence of a signature by a particular public key can be asserted with: ```sh filepack verify --key PUBLIC_KEY ``` Which will fail if a valid signature for `PUBLIC_KEY` over the manifest contents is not present. Fingerprints ------------ Filepack signatures are made over the package fingerprint, which is the hash of a deco object which commits to the files and directories contained in the manifest. Fingerprints are BLAKE3 hashes, constructed such that it is impossible to produce packages which are different but which have the same fingerprint. Fingerprints may be used as a globally unique identifier. If two packages have the same fingerprint, they have the same content. Guide ----- The `[DIRECTORY]` argument in examples can be omitted, in which case it defaults to the current directory. ### Creating a Package To create a filepack manifest: ```shell filepack create [DIRECTORY] ``` This creates `manifest.filepack` containing hashes and file sizes of all files in the `DIRECTORY` and subdirectories. To enable linting, use the `--deny` flag with a lint or lint group: ```sh filepack create --deny [DIRECTORY] ``` To view all lints: ```shell filepack lints ``` Please feel free to open issues requesting new lints. ### Creating a Package with Metadata To create a package with metadata describing the package, create a file named `metadata.yaml` in the package directory, for example: ```yaml title: The Necronomicon creator: Abdul Alhazred description: >- The Old Ones, their history, and the rites by which they may be summoned. language: la ``` Then, to create the package: ```shell filepack create [DIRECTORY] ``` Which will validate metadata if present and write it to `metadata.filemeta`, as well as include it in `manifest.filepack`. See [metadata](#metadata) for the full schema. ### Signing a Package To sign a package when creating it, first create a new public and private key pair: ```shell filepack keygen ``` This creates a new public key, `master.public`, with corresponding private key, `master.private`, in the filepack keychain directory. The `filepack info` command will print the path to the filepack data directory, keychain directory, and any public keys in the keychain directory. Your public key can be printed with: ```shell filepack key ``` To sign a new package: ```shell filepack create --sign [DIRECTORY] ``` To add a signature to an existing package: ```shell filepack sign ``` ### Getting the Package Fingerprint To print the fingerprint of a package: ```shell filepack fingerprint [DIRECTORY] ``` ### Publishing a Manifest Manifests contain only hashes and not file content, and so can be published anywhere, including places where technical or legal limitations would preclude publication of the content itself. ### Verifying a Package Package content can be verified with: ```shell filepack verify [DIRECTORY] ``` Any extra files, missing files, or modified files will produce an error. This verifies files against hashes stored in the manifest. Running `filepack verify` on its own can detect accidental corruption, but not intentional modification, since an attacker could modify package content, and then modify the manifest to make it match the modified content and thus pass verification. However, if the manifest was obtained from a trusted source, verification will catch any modifications, intentional or otherwise, even if the package content was obtained from an untrusted source. To detect intentional modification, you can either verify a package against a known-good fingerprint: ```sh filepack verify --fingerprint [DIRECTORY] ``` This verifies both the contents of the package and that the manifest has the expected fingerprint. The former protects against accidental corruption, and the latter against intentional modification. Packages can also be verified by checking for a signature by a known public key. This also protects against intentional modification, as long as the private key corresponding to the public key is protected, and only known to you, or someone you trust. To verify that a package has a signature from a particular public key: ```sh filepack verify --key [DIRECTORY] ``` The `--key` option can be repeated to require signatures from multiple public keys. ### Detecting Accidental Corruption Create a filepack manifest with: ```shell filepack create ``` This will create `/manifest.filepack` To later verify the package against the manifest: ```shell filepack verify ``` Because the manifest contains cryptographic hashes, accidental corruption to the files or manifest will always be detected by `filepack verify`. This is *not* the case with intentional, malicious corruption, since an attacker could modify the files and replace the manifest hashes with the hashes of the modified files. ### Detecting Malicious Corruption Because an attacker could modify the files and replace the manifest hashes with the hashes of the modified files, you must ensure that the manifest has not been tampered with. This can be accomplished in a number of ways, either by saving the manifest to a secure location, saving the package fingerprint, or signing the package. #### Manifest To save the manifest in a secure location, use the `--manifest` option to save the manifest somewhere other than the package: ```shell filepack create --manifest ``` Then, verify the package against the saved manifest: ```shell filepack verify --manifest ``` Because the manifest was protected, any modification to the package will be detected. This has the advantage that not only will any modifications be detected, but which files were modified can also be detected. #### Fingerprint Create the manifest in the package root with: ```shell filepack create ``` Print the package fingerprint: ```shell filepack fingerprint ``` Save the fingerprint in a secure location. Then, verify the package against the saved fingerprint: ```sh filepack verify --fingerprint ``` Because the fingerprint was protected, any modification to the package will be detected. This has the advantage that you only have to save a small text string, but the disadvantage that while any modifications will be detected, you will not be able to determine which files have changed. #### Signature Create the manifest in the package root and sign it with your `master` key: ```shell filepack create --sign ``` Then, verify the package and its signature: ```shell filepack verify --key master ``` Any modification to the package or manifest will invalidate the signature, which will be detected. This has the advantage of not needing to save the manifest or fingerprint of packages you want to verify. However, you will need to generate and secure your private key. ### Determining Authenticity To check the authenticity of a package created by someone else, get their public key and verify that the package contains a signature by that key: ```sh filepack verify --key ``` Alternatives and Prior Art -------------------------- `filepack` serves the same purpose as programs like `shasum`, which hash files and output a text file containing file hashes and paths, which can later be used with the same program to verify that the files have not changed. They output hashes and paths one per line, separated by whitespace, and mainly differ in which hash function they use. Some examples, with links to implementations and the hash functions they use: | binary | hash function | |---|---| | [`b2sum`](https://github.com/BLAKE2/BLAKE2) | [BLAKE2](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) | | [`b3sum`](https://github.com/BLAKE3-team/BLAKE3) | [BLAKE3](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3) | | [`cksfv`](https://zakalwe.fi/~shd/foss/cksfv/) | [CRC-32](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) | | [`hashdeep`](https://github.com/jessek/hashdeep) | various | | [`hashdir`](https://github.com/ultimateanu/hashdir/) | various | | [`sha3sum`](https://codeberg.org/maandree/sha3sum) | [SHA-3](https://en.wikipedia.org/wiki/SHA-3) | | [`shasum`](https://metacpan.org/pod/Digest::SHA) | [SHA-1](https://en.wikipedia.org/wiki/SHA-1) and [SHA-2](https://en.wikipedia.org/wiki/SHA-2) | CRC-32 is not a cryptographic hash function and cannot be used to detect intentional modifications. Similarly, SHA-1 was thought to be a cryptographic hash function, but is now known to be insecure. `filepack` and `b3sum` both use BLAKE3, a fast, general-purpose cryptographic hash function. `filepack` can also create and verify signatures. Other signing and verification utilities include: | binary | about | |---|---| | [`gpg`](https://gnupg.org/) | general-purpose, [OpenPGP](https://www.openpgp.org/) implementation | | [`ssh-keygen`](https://man.openbsd.org/ssh-keygen.1) | general-purpose, shipped with [OpenSSH](https://www.openssh.com/) | | [`minisign`](https://github.com/jedisct1/minisign) | general-purpose | | [`signifiy`]( https://github.com/aperezdc/signify) | general-purpose | | [`SignTool`](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool) | Windows code signing | | [`codesign`](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html) | macOS code signing | | [`jarsigner`](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html) | JDK code signing | Filepack for SFV Enjoyers ------------------------- If you package content for distribution, Filepack offers a number of benefits over simple file verification with `.sfv` files. - Filepack can detect both accidental corruption and intentional modification, whereas `.sfv` files can only detect accidental corruption. - Because `manifest.filepack` manifests contain file sizes, Filepack can tell the user not just whether a file has been modified, but also whether it is empty, truncated, or too long. - Filepack packages have a fingerprint, a short text string beginning with `package1…` which is guaranteed to be globally unique, allowing packages to be identified and referenced by fingerprint alone. - Fingerprints can be used to verify that a `manifest.filepack` manifest itself has not been tampered with, proving authenticity of a package regardless of its source. - Packages can be signed, allowing users to verify authenticity of any package from a packager by public key, a short string beginning with `public1…`. - Filepack can warn you if package filenames might cause issues on other operating systems or file systems. - Filepack packages are Merkle trees, both of files and within files. A user with a manifest, package fingerprint, or trusted public key can incrementally stream and verify files, or access and verify file content at random. Additionally, errors can be detected and recovered from by transmitting only those parts of the file which are corrupted. - Packages may contain machine readable metadata following a filepack-defined schema, allowing packages to be searched, indexed, and exposed through rich, featureful interfaces. Why is metadata important? -------------------------- Metadata is the difference between a good user experience and a bad user experience. On Netflix, movies are presented in an attractive fashion with artwork, titles, actors, and other information. All content is playable on whatever device you happen to be using, streaming starts instantly, search is useful, and a recommendation engine surfaces movies you might like. It is usable by anyone, of any age and with any degree of technical proficiency. On BitTorrent, movies are presented inscrutably in text listings with weirdly formatted titles, spotty or missing information, and no artwork. Content may or may not be playable on your device, streaming is impossible, search is unstructured, and recommendations are unavailable. It is only usable by a small and decreasing percentage of the population, and asking someone over to "BitTorrent and chill" will be met by blinking incomprehension, followed by derision as you fiddle with your torrent client like a digital steam engine operator, desperately trying to find seeds for the movie you want to watch. The difference between the two systems is not due to a difference in underlying data, every piece of content one could possibly imagine is available on BitTorrent. It is a difference in the availability of metadata. Netflix has a database of standardized, machine-readable metadata, supporting nearly every feature of the system. BitTorrent has a disparate collection of folders of files, with inconsistent and incomplete metadata, and the user experience and features of the system are a direct consequence. As polished services driven by metadata have become more popular, the user experience that systems based on folders of files can offer has become ever more unacceptable and alien, regardless of cost or underlying availability of content. Filepack seeks to rectify this by standardizing machine-readable metadata that can be included with folder-of-files content. Filepack metadata is stored a file, `metadata.filemeta`, in the root of a filepack package as well as in the `manifest.filepack` archive, and contains information related to what the package contains, which files in the package contain the package content and their file formats, and who created the package. This metadata can serve as a base for the creation of rich local and distributed applications and services, with user experiences that compete with and exceed those of closed centralized alternatives. Design ------ ### Manifest Format A filepack manifest contains all information needed to verify the contents of a directory. The `package` key of the manifest is a directory object mapping filenames to directory entries, which may themselves be directories, or files, in which case they contain the hash of the file contents, as well as the length of the file. The length of the file is not strictly necessary for verification, but is included so that truncated, empty, and overlong files can be identified, which may help in understanding verification failures. File contents may be embedded in the `manifest.filepack` archive under the `embedded` key. ### File Hashes The contents of files are hashed with [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) using the official Rust implementation. BLAKE3 was chosen both for its speed, and for the fact that it utilizes a Merkle tree construction. A Merkle tree allows for verified file streaming and subrange inclusion proofs, which both seem useful in the context of file hashing and verification. ### Signatures Filepack allows for the creation of Ed25519 signatures over the contents of a manifest, which thus commit to the contents of the directory covered by the manifest. Signatures are made over a statement containing a "fingerprint" hash of a canonical deco serialization of the manifest's `package` directory. This keeps signatures independent of the manifest format, avoids the need for a canonical serialization of the manifest as a whole, avoids hash loops due to the inclusion of signatures in the manifest itself, and allows proving the inclusion of files covered by a signature. ### Fingerprints Package fingerprints are the BLAKE3 hash of a canonical deco serialization of the manifest's `package` directory. Fingerprints are constructed to be unique, meaning that it is impossible for two different packages with different contents to have the same fingerprint. Currently, no fingerprint test vectors exist, and the best documentation is the code itself. In particular, see: - [Manifest](src/manifest.rs) - [Directory](src/directory.rs) - [Entry](src/entry.rs) - [Files](src/file.rs) - [Statement](src/statement.rs) And the [head](src/head.rs) module for the deco encoding.