Crate libapt

Source
Expand description

aptcheckr logo

§libapt

Libapt is a pure Rust apt library.

You can find the sources at Codeberg and Github.

§Architecture

This crate provides a pure Rust interface for Debian repositories.

§Debian apt repositories

The format of Debian, i.e. apt, repositories is described in the Debian wiki.

In general, apt repositories consist of distributions and packages. The path to this file on the server depends on the repository type. By default, a apt repository is structured in the following way:

Root --- /dists ------ /dist_a
      \          \---- /dist_b
       \          \--- ...
        \
         \--/pool ----- /a
                   \--- /b                  
                    \-- ...

The dists folder contains one or many distributions.

Each distribution is defined by a InRelease, or Release, index file. This index file is signed using GPG key of the distribution provider. The matching public key is typically installed on the local machine, and the client tool verifies the signature to ensure the repository was not manipulated.

The distribution index file contains relative paths and checksums to package index files. These package indices are grouped by components. The client tool downloads the wanted component indices, verifies the integrity using the checksum, and parses the packages stanzas.

The package indices provide, as part of the stanzas, paths to the packages, relative to the root of the apt repository, and checksums for each package. These checksums allow verification of the packages after download.

The packages are typically placed in a pool folder, and this folder typically uses a sub-folder structure consisting of first letter or lib plus first letter and name of the source package. This structure is not relevant for the apt repository client tooling, because it’s part of the paths provided in the component package indices.

Some tools generate flat repositories instead of default repositories. These repositories work in the same way, but use a path instead of a distribution name. The distribution InRelease file for these repositories is located at root / path / InRelease. These repositories typically also doesn’t follow the pool structure for organizing the packages.

§libapt

The goal of libapt is to provide a pure Rust library for interfacing with Debian apt repositories. It shall be possible to use libapt to parse an apt repository, get the packages, package metadata, checksums and locations, and verify the validity of the metadata with respect to signatures, compliance with the Debian repository format specification, and existence of packages indices and packages.

§Struct Distro

The struct Distro is the starting point to parse an apt repository. It groups all information which need to be provided by a user to locate and verify the InRelease distribution index.

For verifying the integrity of the distribution index, a public key needs to be provided. This pubic key can be armored public GPG key, e.g. form a URL or a local file, or a non-armored binary GPG key, e.g. form the local /etc/apt/trusted.gpg.d/. For receiving both types of keys, a download is tried if the provided string starts with ‘http’, else the string is interpreted as a local path.

If no verification is wanted, the value Key::NoSignatureCheck can be provided.

use libapt::{Distro, Key};

let key = Key::key("/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg");

// Default repository format using a public key form an URL.
let distro = Distro::repo(
    "http://archive.ubuntu.com/ubuntu",
    "jammy",
    key.clone(),
);

// Flat repository format using a public key form an URL.
let distro = Distro::flat_repo(
    "http://archive.ubuntu.com/ubuntu",
    "dists/jammy",
    key.clone(),
);

// Flat repo skipping verification.
let distro = Distro::flat_repo(
    "http://archive.ubuntu.com/ubuntu",
    "dists/jammy",
    Key::NoSignatureCheck,
);
§Struct Release

The struct Release groups all information contained in the InRelease file. When a new Release is created by running Release::from_distro, the content of the InRelease file is downloaded, the inline signature is verified using the Distro key, and the content is parsed.

use libapt::{Distro, Key, Release};

tokio_test::block_on(async {

// Ubuntu Jammy signing key.
let key = Key::key("/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg");

// Ubuntu Jammy distribution.
let distro = Distro::repo(
    "http://archive.ubuntu.com/ubuntu",
    "jammy",
    key,
);

// Parse the InRelease file.
let release = Release::from_distro(&distro).await.unwrap();

// Check for compliance with https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files.
release.check_compliance();

})

The struct Release provides also some convenience methods to access the package indices.

The method Release::get_package_links provides all available package indices. This means, for each combination of architecture and component, the function get_etag is used to check if the URL really exists, and if the URL exists, the Link is provided. The return value of this method is a Vec<(String, Architecture, Link)>, and each tuple consists of component name, architecture, and URL to the index as Link.

The method Release::get_package_index_link provides the Link to one specific package index. The parameters are the component name and the architecture, and the result is a Link.

The Link struct groups references to files, i.e. URLs, with the hash sums to verify the file, and the size of the file. The supported hash types are MD5, SHA1, SHA256 and SHA512. When the file is downloaded, the best available hash is verified to ensure the integrity.

§Struct PackageIndex

The struct PackageIndex groups all packages of one component and architecture. This structure also provides support for parsing the apt repository package index files.

use libapt::{Distro, Key, Release, PackageIndex, Architecture};

tokio_test::block_on(async {

// Ubuntu Jammy signing key.
let key = Key::key("/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg");

// Ubuntu Jammy distribution.
let distro = Distro::repo(
    "http://archive.ubuntu.com/ubuntu",
    "jammy",
    key,
);

// Parse the InRelease file.
let release = Release::from_distro(&distro).await.unwrap();

// Parse the package index of the main component for the amd64 architecture.
let main_amd64 = PackageIndex::new(&release, "main", &Architecture::Amd64).await.unwrap();

println!("Ubuntu Jammy main provides {} packages for amd64.", main_amd64.package_count());

// Get a Package from the package index.
let busybox = main_amd64.get("busybox-static", None).unwrap();

println!("Ubuntu Jammy main provides busybox-static version {:?}.", busybox.version);

})
§Struct Package

The metadata about binary packages are grouped in the struct Package. The packages are parsed from the so called stanzas of the package indices.

§Struct SourceIndex

The struct SourceIndex groups all source packages of one component. This structure also provides support for parsing the apt repository source package index files.

use libapt::{Distro, Key, Release, SourceIndex};

tokio_test::block_on(async {

// Ubuntu Jammy signing key.
let key = Key::key("/etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg");

// Ubuntu Jammy distribution.
let distro = Distro::repo(
    "http://archive.ubuntu.com/ubuntu",
    "jammy",
    key,
);

// Parse the InRelease file.
let release = Release::from_distro(&distro).await.unwrap();

// Parse the package index of the main component for the amd64 architecture.
let main_sources = SourceIndex::new(&release, "main").await.unwrap();

println!("Ubuntu Jammy main provides {} source packages.", main_sources.package_count());

// Get a Package from the package index.
let busybox = main_sources.get("busybox", None).unwrap();

println!("Ubuntu Jammy main provides busybox version {:?}.", busybox.version);

})
§Struct Source

The metadata about source packages are grouped in the struct Source. The source packages are parsed from the so called stanzas of the source package indices.

§Limitations

  • Apt repositories providing only the old Release with detached Release.gpg signature are not supported.
  • Host dependencies on Ubuntu Linux:
    • rust-lzma requires liblzma-dev and pkg-config
    • reqwest requires libssl-dev

§Examples

Libapt provides some example applications. You can run the examples using the command cargo run --example NAME, where name is the name of the example.

§The “hello” example.

The hello example runs the code given above in the architecture section. You can run it with the command cargo run --example hello.

§Test the examples

Some of the example tests are black box tests and depend on the binaries to exist. Run cargo build --examples before cargo test --examples, else these tests will fail.

§Potential future improvements

  • Optional lossy parsing, i.e. log unexpected input but not stop parsing.
  • Update metadata based on last etag.

Structs§

Distro
The Distro groups all information required to locate the distribution main index InRelease file.
Error
Libapt error type.
Link
Link represents a file referenced from InRelease.
Package
The Package struct groups all data about a package.
PackageIndex
A PackageIndex is a set of packages for a specific architecture and component.
PackageVersion
A PackageVersion describes a package version dependency.
Release
The Release struct groups all data from the InRelease file.
Source
The Source struct groups all data about a source package.
SourceIndex
A SourceIndex is a set of packages for a specific architecture and component.
Version
The Version struct groups the Debian version parts.

Enums§

Architecture
ErrorType
The ErrorTypes provide a rough classification of the errors.
Key
The enum Key is used to wrap the apt repository verification key.
LinkHash
Priority
VersionRelation
A VersionRelation describes the relation between two package versions.

Functions§

get_etag
Get the timestamp when the URL was last modified.

Type Aliases§

Result