VOA
This project provides Rust libraries, command line tools and a test suite to interact with the File Hierarchy for the Verification of OS Artifacts (VOA).
The VOA specification has been created to provide the means for a generic, OS artifact verification scheme, that can work with different technologies while relying on a unifying lookup directory to retrieve verifiers for signatures.
This project provides a reference implementation for the VOA specification as well as a canonical test suite that is usable by any other implementation.
Currently, only an OpenPGP backend is specified. However, specifications for the minisign, signify, SSH and X.509 backends are already prepared and only require some further input for finalizing. If you have expertise in one of these technologies, please get in touch on the relevant pull request for specification.
This project is currently supported by the Sovereign Tech Agency. Read the official announcement for more information.
Documentation
The latest project documentation can be found at https://voa.archlinux.page
Overview
The following mindmap attempts to provide a high-level overview of the project and put (existing and upcoming) libraries and command line tools into context.
mindmap root((π VOA specification)) β¨οΈ/ποΈ voa ποΈ voa-core ποΈ voa-minisign * ποΈ voa-openpgp ποΈ voa-signify * ποΈ voa-ssh * ποΈ voa-x509 * β¨οΈ voa-verify * ποΈ voa-test-suite *
[*] Not yet implemented, or subject to change.
Components
Currently the following components are available:
- voa: command line interface and library for interacting with VOA
- voa-core: a library for access to verifiers in VOA hierarchies
- voa-openpgp: a library for using OpenPGP verifiers in VOA
Contributing
Please refer to the contribution guidelines to learn how to contribute to this project.
Releases
Releases of components are created by the developers of this project.
OpenPGP certificates with the following OpenPGP fingerprints can be used to verify signed tags:
991F6E3F0765CF6295888586139B09DA5BF0D338
(David Runge <dvzrv@archlinux.org>)165E0FF7C48C226E1EC363A7F83424824B3E4B90
(Orhun ParmaksΔ±z <orhun@archlinux.org>)
The above are part of archlinux-keyring and certified by at least three main signing keys of the distribution.
License
This project can be used under the terms of the Apache-2.0 or MIT. Contributions to this project, unless noted otherwise, are automatically licensed under the terms of both of those licenses.
VOA
A command line interface and library for interacting with the "File Hierarchy for the Verification of OS Artifacts" (VOA).
Documentation
- https://voa.archlinux.page/rustdoc/voa/ for development version of the crate
- https://docs.rs/voa/latest/voa/ for released versions of the crate
Examples
Library
Import and search
use std::io::Write; use tempfile::{NamedTempFile, tempdir}; use voa::commands::{load_verifier, search_verifiers, write_verifier_to_hierarchy}; fn main() -> testresult::TestResult { // Write a generic OpenPGP certificate to a temporary file. let cert = r#"-----BEGIN PGP PUBLIC KEY BLOCK----- xjMEaNBDAhYJKwYBBAHaRw8BAQdAzjzrpQ/AEteCmzjd1xTdXGaHV0VKSm4HLy6l HVcmWT3NH0pvaG4gRG9lIDxqb2huLmRvZUBleGFtcGxlLm9yZz7CmgQQFggAQgUC aNBDAhYhBEauMg3lOimFWKbyoPtSEBy0DfYKAhsDAh4BBAsJCAcGFQ4KCQwIARYN JwkCCAIHAgkBCAEHAQIZAQAKCRD7UhActA32CkhIAP9bhoLJeZRCAc+q1kFEkstT uXBPlzHagF6ghuUfToMmVQD+KaakONKSekglKR4rJxzhleQJ4qsptt1gjXX13QgF Xwo= =Pkv9 -----END PGP PUBLIC KEY BLOCK-----"#; let mut temp_file = NamedTempFile::new()?; write!(temp_file, "{cert}")?; let input_path = temp_file.path(); // Load OpenPGP verifier from file. let verifier = load_verifier(Some(input_path.try_into()?), "openpgp".parse()?)?; // Prepare a temporary output directory. let temp_dir = tempdir()?; // Write a verifier to a location in a temporary VOA hierarchy. write_verifier_to_hierarchy(verifier, temp_dir, "os".parse()?, "packages".parse()?, None)?; // Search for verifiers by relevant identifier information. let verifiers = search_verifiers("os".parse()?, "packages".parse()?, None, None)?; for verifier in verifiers.keys() { println!("{verifier:?}"); } Ok(()) }
Verification
β οΈ DO NOT USE IN PRODUCTION: Artifact verification is still in an early experimental development stage.
use std::{collections::HashSet, str::FromStr}; use voa::{commands::verify, utils::RegularFile}; fn main() -> testresult::TestResult { verify( "os".parse()?, "purpose".parse()?, "context".parse()?, "openpgp".parse()?, &RegularFile::from_str("/some/path/to/an/file.tar.zst")?, HashSet::from([&RegularFile::from_str("/some/path/to/an/file.tar.zst")?]), )?; Ok(()) }
CLI
The voa
CLI offers a simple interface for dealing with data in a VOA hierarchy.
Import verifiers
Verifiers can be imported using the voa import
subcommand.
Assuming that the environment variable OPENPGP_CERT
contains the path to an OpenPGP certificate with signing capabilities, we can import it to a VOA hierarchy directory (represented by the VOA_DIR
environment variable).
The following imports the OpenPGP certificate to the directory os/packages/openpgp/
in VOA_DIR
, implying that this verifier is to be used for the verification of package files on the OS os
.
rpgp show "$OPENPGP_CERT"
voa import os packages openpgp --input "$OPENPGP_CERT" --base-path "$VOA_DIR"
# π EdDSA/Curve25519 v4 f992bda338ded64fe062302b5bd40d64577b8ea2
# β± Created 2025-09-20 06:13:33 UTC
#
# πͺͺ ID "John Doe <john.doe@example.org>"
# π CertGeneric 2025-09-20 06:13:33 UTC, by 5bd40d64577b8ea2 [EdDSALegacy, SHA256, V4]
#
tree "$VOA_DIR"
# .
# βββ os
# βββ packages
# βββ default
# βββ openpgp
# βββ f992bda338ded64fe062302b5bd40d64577b8ea2.openpgp
rpgp show "$VOA_DIR"/os/packages/default/openpgp/*.openpgp
# π EdDSA/Curve25519 v4 f992bda338ded64fe062302b5bd40d64577b8ea2
# β± Created 2025-09-20 06:13:33 UTC
#
# πͺͺ ID "John Doe <john.doe@example.org>"
# π CertGeneric 2025-09-20 06:13:33 UTC, by 5bd40d64577b8ea2 [EdDSALegacy, SHA256, V4]
#
List verifiers
Verifiers can be listed using the voa list
subcommand.
voa list os packages
# /home/user/.config/voa/os/packages/default/openpgp/f992bda338ded64fe062302b5bd40d64577b8ea2.openpgp
voa list os packages --output-format json
# [{"load_path":{"load_path":"/home/user/.config/voa","writable":true,"ephemeral":false},"verifier_path":"/home/user/.config/voa/arch/packages/default/openpgp/f992bda338ded64fe062302b5bd40d64577b8ea2.openpgp","os":{"id":"arch","version_id":null,"variant_id":null,"image_id":null,"image_version":null},"purpose":{"role":"packages","mode":""},"context":"default","technology":"openpgp"}]
Verify artifacts
β οΈ DO NOT USE IN PRODUCTION: Artifact verification is still in an early experimental development stage.
OS artifacts can be verified using the voa verify
subcommand.
Currently, only OpenPGP verification is available.
voa verify os image default /path/to/an/image-1.2.3.tar.zst /path/to/an/image-1.2.3.tar.zst.sig
# β
/path/to/an/image-1.2.3.tar.zst.sig 1734644649 f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 e242ed3bffccdf271b7fbaf34ed72d089537b42f
voa verify os image default /path/to/an/image-1.2.3.tar.zst /path/to/an/image-1.2.3.tar.zst.sig --output-format json
# [{"signature":"/path/to/an/image-1.2.3.tar.zst.sig","result":{"valid":{"signature_creation_time":1734644649,"primary_key_fingerprint":"f1d2d2f924e986ac86fdf7b36c94bcdf32beec15","verifying_component_key_fingerprint":"e242ed3bffccdf271b7fbaf34ed72d089537b42f"}}}]
Contributing
Please refer to the contribution guidelines to learn how to contribute to this project.
License
This project can be used under the terms of the Apache-2.0 or MIT. Contributions to this project, unless noted otherwise, are automatically licensed under the terms of both of those licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.2.0] - 2025-09-29
Added
- Add the
voa verify
subcommand and high-levelverify
function - Add
RegularFile
type for paths that must be a regular file - Add the
voa list
subcommand to list verifiers in VOA - Move
clap
andclap-verbosity-flag
to workspace dependencies
Other
- Add documentation examples on
voa verify
andverify
function - [breaking] Rename
Technology::OpenPGP
toTechnology::Openpgp
- (README) Add examples for
voa list
andsearch_verifiers
[0.1.0] - 2025-09-24
Added
- Add the
voa import
subcommand - Initialize bare
voa
crate
Other
- (README) Add examples for library and the
voa import
subcommand
File Hierarchy for the Verification of OS Artifacts (VOA)
VOA is mechanism for the storage and retrieval of cryptography technology-agnostic signature verifiers.
For specification see: https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/.
Documentation
- https://voa.archlinux.page/rustdoc/voa_core/ for development version of the crate
- https://docs.rs/voa-core/latest/voa_core/ for released versions of the crate
Purpose
The VOA hierarchy acts as structured storage for files that contain "signature verifiers" (such as OpenPGP certificates, aka "public keys").
Load Paths
A set of "load paths" may exist on a system, each containing different sets of verifier files. This library provides an abstract, unified view of sets of signature verifiers in these load paths.
Earlier load paths have precedence over later entries (in some technologies).
VOA operates either in "system mode" or "user mode", depending on the UID of the running process. The set of load paths differs between the two modes.
In system mode, the set of load paths (in order of descending precedence) is:
/etc/voa/
/run/voa/
/usr/local/share/voa/
/usr/share/voa/
In user mode, the analogous set of load paths (in order of descending precedence) is:
$XDG_CONFIG_HOME/voa/
- the
./voa/
directory in each directory defined in$XDG_CONFIG_DIRS
$XDG_RUNTIME_DIR/voa/
$XDG_DATA_HOME/voa/
- the
./voa/
directory in each directory defined in$XDG_DATA_DIRS
Support for different cryptographic technologies
Libraries dealing with a specific cryptographic technology can rely on this library to collect the paths of all verifier files relevant to them.
NOTE
Depending on the technology, multiple versions of the same verifier will either "shadow" one another, or get merged into one coherent view that represents the totality of available information about the verifier.
Shadowing/merging is specific to each signing technology and must be handled in the technology-specific library.
For more details see e.g. the voa-openpgp
implementation and the VOA specification.
VOA expects that filenames are a strong identifier that signals whether two verifier files contain variants of "the same" logical verifier. Verifiers from different load paths can be identified as related via their filenames.
For example, OpenPGP certificates must be stored using filenames based on their fingerprint. When the filename doesn't match the fingerprint of an OpenPGP verifier, the file is considered invalid and ignored.
Example VOA directory structure
In this example, three OpenPGP certificates are stored as verifiers.
They are stored in two system-mode load paths: /etc/voa
and /usr/local/share/voa/
:
/etc/voa/arch/packages/default/openpgp/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33.pgp
/etc/voa/arch/packages/default/openpgp/62cdb7020ff920e5aa642c3d4066950dd1f01f4d.pgp
/usr/local/share/voa/arch/packages/default/openpgp/bbe960a25ea311d21d40669e93df2003ba9b90a2.pgp
The four VOA subdirectory layers are the same for each of the verifiers: arch/packages/default/openpgp
.
These layers signify, respectively:
- the os identifier is
arch
, - the verifier purpose is
package
, - the verifier context is
default
and - the verifier technology is
openpgp
.
In a fully populated VOA structure, verifier files may be stored for different use cases, and stored in a mix of different os, purpose, context and technology paths.
Integration tests
The integration tests in this crate set up VOA file hierarchies in a container environment and consume/evaluate them with voa-core
.
They can be run with just containerized-integration-tests
.
Usage
This crate is not usually used directly in applications. Its main purpose is as a foundational building block for technology-specific VOA libraries, such as voa-openpgp
.
Technology-specific VOA implementations are, in turn, used in applications (e.g. an application that validates signatures over distribution packages before installation).
use voa_core::{ Voa, identifiers::{Context, Mode, Os, Purpose, Role, Technology}, }; fn main() -> Result<(), voa_core::Error> { // Create a new VOA instance. // Its load paths are automatically set up based on the user id of the running process. let voa = Voa::new(); // Look up OpenPGP verifiers for the Os `arch` to validate signatures on `packages`. let verifiers = voa.lookup( Os::new("arch".parse()?, None, None, None, None), Purpose::new(Role::Packages, Mode::ArtifactVerifier), Context::Default, Technology::Openpgp, ); Ok(()) }
Features
_containerized-integration-test
for integration tests that run in a dedicated container._winnow-debug
enables thewinnow/debug
feature, which shows the exact parsing process of winnow.
Contributing
Please refer to the contribution guidelines to learn how to contribute to this project.
License
This project can be used under the terms of the Apache-2.0 or MIT. Contributions to this project, unless noted otherwise, are automatically licensed under the terms of both of those licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.3.0] - 2025-09-29
Added
- (voa-core) Derive Clone for Verifier
- Add
serde
feature forserde
support on all identifiers - Move
clap
andclap-verbosity-flag
to workspace dependencies - Return
LoadPath
instead ofPath
fromVoaLocation::load_path
- Add
LoadPath::path
returning the underlying path - Add
LoadPath::ephemeral
to check whether the path is ephemeral
Other
- [breaking] Rename
Technology::OpenPGP
toTechnology::Openpgp
- (cargo) Move
serde
andserde_json
to workspace dependencies - (cargo) Move
strum
to workspace dependencies
[0.2.1] - 2025-09-24
Added
- Add
LoadPathFilter
to allow filtering aLoadPathList
- Add
LoadPathList::from_effective_user
for current user load paths - Publicly expose
LoadPath
andLoadPathList
Other
- (cargo) Move
anyhow
from dependencies to dev-dependencies - (cargo) Move
libc
to workspace dependencies - (cargo) Add common dependencies to workspace dependencies
[0.2.0] - 2025-09-10
Added
- Add
VerifierWriter
trait to support writing verifiers - [breaking] Replace the use of
TryFrom<&str> for Role
withRole::from_str
- Add
winnow
parsers andFromStr
impls for VOA path components - Add
IdentifierString
as base for components with custom strings
Other
- [breaking] Use
IdentifierString
instead ofcheck_part
utility - Add link for documentation of released crate versions
[0.1.0] - 2025-08-30
Added
- Basic implementation of voa core spec
Other
- Add containerized integration tests
VOA-OpenPGP
A library for using OpenPGP verifiers in VOA.
β οΈ DO NOT USE IN PRODUCTION: This VOA backend is still in an early experimental development stage.
Documentation
- https://voa.archlinux.page/rustdoc/voa_openpgp/ for development version of the crate
- https://docs.rs/voa-openpgp/latest/voa_openpgp/ for released versions of the crate
Examples
Import
OpenPGP certificates can be written to their dedicated directory structures in a VOA hierarchy.
It is supported to import single binary or ASCII-armored files, as well as directory structures that contain a number of OpenPGP packet files which comprise an OpenPGP certificate when concatenated (this structured form is in use by the archlinux-keyring project).
use voa_core::VerifierWriter; use voa_openpgp::OpenPgpImport; use pgp::{ composed::{KeyType, SecretKeyParamsBuilder, SignedPublicKey, SubkeyParamsBuilder}, ser::Serialize, types::Password, }; use rand::thread_rng; use tempfile::{NamedTempFile, tempdir}; fn openpgp_cert() -> testresult::TestResult<SignedPublicKey> { let mut signkey = SubkeyParamsBuilder::default(); signkey .key_type(KeyType::Ed25519Legacy) .can_sign(true) .can_encrypt(false) .can_authenticate(false); let mut key_params = SecretKeyParamsBuilder::default(); key_params .key_type(KeyType::Ed25519Legacy) .can_certify(true) .can_sign(false) .can_encrypt(false) .primary_user_id("John Doe <jdoe@example.org>".to_string()) .subkeys(vec![signkey.build()?]); let secret_key_params = key_params.build()?; let secret_key = secret_key_params.generate(thread_rng())?; // Produce binding self-signatures that link all the components together let signed = secret_key.sign(&mut thread_rng(), &Password::from(""))?; let pubkey = SignedPublicKey::from(signed); Ok(pubkey) } fn main() -> testresult::TestResult { // Write a generic OpenPGP certificate to a temporary file. let mut temp_file = NamedTempFile::new()?; openpgp_cert()?.to_writer(&mut temp_file)?; let input_path = temp_file.path(); // Import the OpenPGP certificate. let import = OpenPgpImport::from_file(input_path)?; // Prepare a temporary output directory. let temp_dir = tempdir()?; let output_dir = temp_dir.path(); // Write the OpenPGP verifier to a VOA hierarchy in the temporary output directory. // // There, the verifier is written to the configured directory, e.g. // `os/purpose/context/openpgp/f1d2d2f924e986ac86fdf7b36c94bcdf32beec15.openpgp` import.write_to_hierarchy( output_dir, "os".parse()?, "purpose".parse()?, Some("context".parse()?), )?; assert!( output_dir .join("os") .join("purpose") .join("context") .join("openpgp") .join(import.file_name()) .exists() ); Ok(()) }
Verification
β οΈ DO NOT USE IN PRODUCTION: The verification mechanism in this VOA backend is still in an early experimental development stage.
Simple verification of artifacts using one or more OpenPGP signatures and verifiers from VOA is straight forward:
use std::path::PathBuf; use voa_openpgp::{verify_from_file, OpenpgpCert, OpenpgpSignature, VoaOpenpgp}; fn main() -> testresult::TestResult { let voa = VoaOpenpgp::new(); let certs = voa.lookup("os".parse()?, "purpose".parse()?, "context".parse()?); let cert_refs: Vec<&OpenpgpCert> = certs.iter().collect(); verify_from_file( &PathBuf::from("/path/to/a/file.tar.zst"), &cert_refs, &[&OpenpgpSignature::from_file(&PathBuf::from("/path/to/a/file.tar.zst.sig"))?], )?; Ok(()) }
Contributing
Please refer to the contribution guidelines to learn how to contribute to this project.
License
This project can be used under the terms of the Apache-2.0 or MIT. Contributions to this project, unless noted otherwise, are automatically licensed under the terms of both of those licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.2.0] - 2025-09-29
Added
- (voa-openpgp) Implement verifier lookup, package validation
Other
- (README) Add example for
verify_from_file
- (voa-openpgp) Container tests for verifiers
- [breaking] Rename
Technology::OpenPGP
toTechnology::Openpgp
[0.1.1] - 2025-09-24
Added
- (cargo) Add
voa-core
andvoa-openpgp
to workspace dependencies
Other
- (cargo) Move
pgp
andrand
to workspace dependencies - (cargo) Add common dependencies to workspace dependencies
[0.1.0] - 2025-09-10
Added
- Add
import
module for import of OpenPGP verifiers - Initialize bare
voa-openpgp
crate
Other
- Upgrade voa-core crate to 0.2.0
API documentation
Local Dependency Graph
The following figure shows the dependency relationships between the crates of the VOA workspace.
Contributing
These are the contributing guidelines for the voa project.
Development takes place at https://gitlab.archlinux.org/archlinux/alpm/voa.
Writing code
This project is written in Rust and formatted using the nightly rustfmt
version.
The linking is performed via mold
.
All contributions are linted using clippy
and spell checked using codespell
.
The dependencies are linted with cargo-deny
and unused dependencies are detected using cargo-machete
.
License identifiers and copyright statements are checked using reuse
.
Toml files are formatted via [taplo-cli
].
Various just
targets are used to run checks and tests.
shellcheck
is used to check the just targets for correctness.
In order to review the snapshot changes in tests, you can use cargo-insta
.
Code examples in READMEs is tested via tangler
.
Links in markdown files or doc blocks are tested via lychee
.
To get all of the necessary tools installed on Arch Linux, run just install-pacman-dev-packages
.
To setup Rust for this project run just install-rust-dev-tools
.
Both can also be done in one fell swoop via just dev-install
.
To aide in development, it is encouraged to configure git to follow this project's guidelines:
just configure-git
This just
command takes care of a few things:
- Configure
git
to sign commits for this repository using OpenPGP. - Install the relevant git pre-commit and git pre-push hooks.
- Install the git prepare-commit-msg hook to automatically add a signoff section to the commit message.
Testing
We run nextest
for fast execution of unit and integration tests.
just test
calls cargo nextest
as well as just test-docs
, which runs the doc tests as nextest
isn't capable of doing that yet.
The just test-coverage
command creates a cobertura code coverage report and an HTML report.
Both are written to target/llvm-cov/
, which utilizes the llvm-tools-preview
component.
The just test-coverage doc
additionally includes doc tests into the coverage report.
However, this is still an unstable nightly-only feature.
The just containerized-integration-tests
recipe executes all tests that are made available by a _containerized-integration-test
feature and are located in an integration test module named containerized
.
With the help of a custom target runner, these tests are executed in a containerized environment using podman
.
The parameter --nocapture
shows output of the tests.
Passing test names as a parameter causes only that subset of tests to be run.
Writing commit messages
To ensure compatibility and automatic creation of semantic versioning compatible releases the commit message style follows conventional commits.
The commit messages are checked by just run-pre-push-hook
via the following tools: cocogitto
& committed
.
Follow these rules when writing commit messages:
-
The subject line should be capitalized and should not end with a period.
-
The total length of the line should not exceed 72 characters.
-
The commit body should be in the imperative mood.
-
Avoid using the crate name as the commit scope. (e.g.
feat(voa-core)
) The changelog entries will be generated for the associated crate accordingly usingrelease-plz
andgit-cliff
.
Here is an example of a good commit message:
feat(parser): Enhance error handling in parser
Improve error handling by adding specific error codes and messages
to make debugging easier and more informative. This update enhances
parsing accuracy and provides more context for handling parsing errors.
Signed-off-by: John Doe <john@archlinux.org>
Merging changes
Changes to the project are proposed and reviewed using merge requests and merged by the developers of this project using fast-forward merges.
Creating releases
Releases are created by the developers of this project using release-plz
by running (per package in the workspace):
just prepare-release <package>
Changed files are added in a pull request towards the default branch.
Once the changes are merged to the default branch a tag is created and pushed for the respective package:
just release <package>
The crate is afterwards automatically published on https://crates.io using a pipeline job.
License
All code contributions fall under the terms of the Apache-2.0 and MIT.
Configuration file contributions fall under the terms of the CC0-1.0.
Documentation contributions fall under the terms of the CC-BY-SA-4.0.
Specific license assignments and attribution are handled using REUSE.toml.
Individual contributors are all summarized as "VOA Contributors".
For a full list of individual contributors, refer to git log --format="%an <%aE>" | sort -u
.
Security Policy
Supported Versions
We release patches for security vulnerabilities. Currently, only the most recent release of a crate is eligible for receiving such patches.
Reporting a Vulnerability
Please report (suspected) security vulnerabilities on the issue tracker as a confidential issue with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will respond as quickly as possible after you open an issue. However, please note that this project is maintained by volunteers, so response times may vary. If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it.
This project follows a coordinated vulnerability disclosure (CVD) with a 90-day disclosure policy, meaning reported vulnerabilities will be addressed within 90 days before public disclosure.