I reviewed upki 1.0.0~beta.3-0ubuntu1 as checked into stonking. This shouldn't
be considered a full audit but rather a quick gauge of maintainability.
upki is a privacy-preserving certificate revocation system from the rustls
project, built on Mozilla's crlite-clubcard construction. It determines whether
an end-entity certificate is revoked by consulting a locally cached,
cryptographically-filtered dataset rather than polling a server per check (no
OCSP, no per-cert telemetry). The crate is a Rust workspace: the `upki` core
library (offline revocation index + C API), `upki-cli` (the `upki` command),
`upki-openssl` (an `SSL_verify_cb` callback), `rustls-upki` (a rustls
`ServerCertVerifier`) and `upki-mirror` (tooling to host Mozilla CRLite files).
The `upki-fetch.service` systemd unit runs `upki fetch` unprivileged as the
`upki` user on a 2-hour persistent timer; it downloads `manifest.json`, diffs it
against the local cache and atomically fetches only the changed CRLite
`.filter`/`.delta` files over TLS using the system CA store. A check hashes the
certificate's issuer SPKI and serial into a CRLite lookup key, seeks the rebuilt
`index.bin` to the covering filter and CT-timestamp window and returns one of
three outcomes: `NotRevoked`, `CertainlyRevoked`, or
`NotCoveredByRevocationData`. Consumers must already have validated the chain -
all four interfaces (CLI, C-FFI, OpenSSL callback, rustls verifier) only report
revocation status, never perform their own chain validation. Packaged as `upki`,
`libupki1` (+`-dev`), `libupki-openssl1` (+`-dev`) and `upki-mirror`
- CVE History
- No CVEs are recorded for the `upki` package itself. Transitive dependencies
are current: rustls 0.23.42, rustls-webpki 0.103.13, aws-lc-rs 1.17.1,
reqwest 0.13.0. The recent rustls-webpki advisories (RUSTSEC-2026-0049,
RUSTSEC-2026-0104) concern CRL parsing/authority semantics that the crlite
path does not exercise: upki parses the end-entity certificate with webpki
but performs revocation via `clubcard_crlite` not via webpki CRLs.
`bincode` 1.3.3 is flagged by RUSTSEC-2025-0141 (unmaintained) and is
explicitly acknowledged in `deny.toml` [advisories] -- retained only for
on-disk `index.bin` format compatibility - it is not used as a
network-facing parser.
- Build-Depends
- The build uses a native Rust toolchain (Rust 1.97) via dh-cargo with full
vendoring (`rust-vendor/`). Dependencies are appropriate:
`ca-certificates` (system CA store for the fetch path), `libssl-dev` (for
the `upki-openssl` FFI crate and the `fetch-native-tls` TLS backend),
`cargo-c` (shared-library install), `help2man`, `pkgconf`. No unusual crypto
or networking libraries beyond these. The CLI is deliberately built
`--no-default-features --features fetch-native-tls`, so fetching is done
against system OpenSSL rather than the bundled `aws-lc-rs`/rustls path; the
offline `libupki1` library links no TLS.
- pre/post inst/rm scripts
- Standard maintainer scripts generated by dh_installsysusers /
dh_installtmpfiles / dh_installsystemd. `postinst` (configure) creates the
`upki` system user (`systemd-sysusers upki.conf`), the cache directories
(`systemd-tmpfiles --create upki.conf`, provisioning `/var/cache/upki` and
`/var/cache/upki/revocation` both 0755 upki:upki), unmask+enables the timer,
reloads the daemon and starts `upki-fetch.service`/`.timer`. `prerm`
(remove) stops the service and timer. `postrm` (remove) reloads the daemon;
(purge) purges the timer. Cleanup is symmetric with installation. The cache
path is consistent: `revocation_cache_dir()` returns
`cache_dir.join("revocation")`, i.e. `/var/cache/upki/revocation`, matching
both the tmpfiles entry and the unit's `CacheDirectory=upki/revocation`. No
custom logic beyond standard debhelper generated scripts.
- init scripts
- None.
- systemd units
- Two units, both installed for the `upki` package:
- `upki-fetch.service`: `Type=oneshot`, `ExecStart=/usr/bin/upki fetch`,
running as `User=upki`/`Group=upki` (unprivileged, no root daemon).
Extensively hardened: `NoNewPrivileges=true`, `PrivateTmp=true`,
`PrivateDevices=true`, `ProtectSystem=strict`, `ProtectHome`,
`ProtectClock/Hostname/KernelLogs/ Modules/Tunables`,
`ProtectControlGroups`, `RestrictNamespaces/Realtime/ SUIDSGID`,
`MemoryDenyWriteExecute=true`, `LockPersonality`, `RemoveIPC`,
`ProcSubset=pid`, `UMask=0022`, empty
`CapabilityBoundingSet`/`AmbientCapabilities`,
`RestrictAddressFamilies=AF_INET AF_INET6`,
`SystemCallArchitectures=native`, `SystemCallFilter=@system-service` with
`@privileged` and `@resources` excluded, `StateDirectory`/`CacheDirectory`
mode 0755.
- `upki-fetch.timer`: `OnBootSec=10min`, `OnUnitActiveSec=2h`,
`Persistent=true`. The unit confines the fetcher well and exposes no
listening socket, the only networking is outbound HTTPS from an unprivileged
account.
- dbus services
- None.
- setuid binaries
- None.
- binaries in PATH
- One executable is installed under `/usr/bin`: the `upki` CLI
(`/usr/bin/upki`). The mirror helpers `mozilla-crlite` and `intermediates`
are installed under `/usr/libexec/upki-mirror/` (not in `$PATH`)
- sudo fragments
- None.
- polkit files
- None.
- udev rules
- None.
- unit tests / autopkgtests
- Package sets `Testsuite: autopkgtest` with a `python3` trigger. Rust unit
tests exist in `upki/tests`, `upki-cli/tests/integration.rs`, `upki-openssl`
and `revoke-test/tests/system_tests.rs` (including a dedicated
`Index::check` test module covering invalid/truncated/oversorted index
inputs). However, `debian/rules` overrides `override_dh_auto_test` to
comment out `cargo test`, so the Rust test suite is NOT executed during the
package build (the build log confirms no test phase). `dh-cargo-built-using`
is also stubbed with a comment that it is "horribly broken" for the embedded
`aws-lc` library. The autopkgtest `debian/tests/upki-fetch` does start a
local HTTP server, repoints `fetch-url` at it and verifies
`manifest.json`/`filter1.filter` land in the cache. Recommendation: have CI
run the upstream `cargo test` suite rather than leaving it disabled in the
build.
- cron jobs None; periodic fetching is handled by the systemd timer
(appropriate).
- Build logs
- The only lines matching `error:` are
`[eyre 0.6.12] error[E0407]/E0554: `#![feature]` may not be used on the
stable release channel`
-- this is the `eyre` build-script toolchain probe (it compiles a
`#![feature]` snippet to detect `backtrace` support) and is expected
`eyre` then compiles cleanly (42 warnings). There are routine
dependency warnings (clippy `unexpected cfg` values, elided lifetimes) and
`dpkg-gencontrol` warnings that `${cargo:Depends/Recommends/...}` are
undefined (a known `dh-cargo`/built-using limitation with the embedded
aws-lc)
- Processes spawned
- Only two hits in the whole scan, both in test code
(`upki-cli/tests/integration.rs` and `revoke-test/tests/system_tests.rs`),
each spawning `cargo` to drive the build. No process spawning in any
production path (CLI, library, mirror, callbacks). Single-process design.
- Memory management
- Rust source; no manual `malloc`/`free`/unbounded copies. The only
raw-pointer/`unsafe` work is in the C-FFI (`upki/src/ffi.rs`) and the
OpenSSL FFI (`upki-openssl/src/lib.rs`): FFI slices are reconstructed with
`slice::from_raw_parts` after null/length checks and the OpenSSL `SSL_CTX`
config is tracked via a statically-registered index
(`CRYPTO_get_ex_new_index`) with explicit free-on-override and
free-on-config-drop.
- File IO
- Config-driven paths: `/etc/upki/config.toml` (config) and
`/var/cache/upki/revocation` (cache) on Linux, with XDG/home fallbacks on
desktop platforms. Downloads are written via `atomic_write` (the `tempfile`
crate, random names, mode 0644, atomic rename) and immediately hash-checked
against the manifest SHA-256 before being kept. The `index.bin` reads use
bounds-checked `split_first_chunk`/`read_be` calls and are validated on load
magic/version, header counts bounds-checked before table allocation.
`umask` is 0022 via the unit. No predictable temp-file names. No unsafe file
permission handling.
- Logging
- Fetch progress uses `tracing` (`info!`/`debug!`); the CLI revocation status
is `println!`ed to stdout. All logging interpolates owned/format-safe values
(`format!`, `{...}`) -- no user-controlled format strings, no fixed-buffer
concatenation.
- Environment variable usage
- Effectively none at runtime on Linux. The only environment reads are
`ProgramData` (Windows-only, `#[cfg(target_os = "windows")]`) and
`CARGO_MANIFEST_DIR`/ `CARGO_PKG_*` at build/test time (`codegen.rs` tests).
No runtime path, URL, or behaviour is controlled by caller-supplied
environment variables.
- Use of privileged functions
- None. The code performs no `setuid`/`setgid`/capability manipulation, opens
no device nodes and the service runs as the unprivileged `upki` user.
Privilege is not sought or used anywhere.
- Use of cryptography / random number sources etc
- Cryptography is delegated: chain validation and hostname checking are done
by `webpki` (in `rustls-upki`) / the system OpenSSL (`upki-openssl`), TLS by
system OpenSSL (native-tls) or `aws-lc-rs`; revocation uses the
`clubcard_crlite` filter and a package-local `sha256` implementation (the
hash is only a lookup-key primitive -- the data itself is fetched over TLS
and hash-verified). No key generation and no user-facing RNG. The verifier
validates the full chain (`verify_for_usage` against the supplied roots) and
the subject name BEFORE consulting revocation data and treats the presence
of a valid CT timestamp window as required for a positive determination.
- Use of temp files
- Temp files come solely from the `tempfile` crate (`tempfile_in(dir)`), which
creates uniquely-named files in the target directory and persists them
atomically; index building appends a `.new` suffix. These live under the
systemd `CacheDirectory` (`/var/cache/upki/revocation`) with `PrivateTmp` in
effect, so names are not predictable and are confined. No hand-rolled `/tmp`
paths.
- Use of networking
- The sole network activity is the `upki fetch` subcommand, using `reqwest`
over `https://` only (the configured `fetch-url` is
`https://upki.ubuntu.com/revocation/`). `native-tls` uses the system CA
store (`ca-certificates`), with a 30 s timeout and `.error_for_status()`
(non-2xx rejected). No `http://` appears in production code (only
`127.0.0.1` test URLs). The `libupki1` library and the revocation check
itself are fully offline. The `upki-mirror` tool also fetches from Mozilla
over HTTPS. No inbound listeners, no open sockets beyond outbound TLS.
- Use of WebKit
- None.
- Use of PolicyKit
- None.
- Any significant cppcheck results
- 49 cppcheck findings, ALL located under `rust-vendor/aws-lc-sys/aws-lc/`
(the vendored AWS-LC C library). They are parser artifacts (`unknownMacro`,
`preprocessorErrorDirective` -- e.g. `OPENSSL_STATIC_ASSERT` requires
configuration that cppcheck is not given). Zero findings reference package
code. Not applicable to `upki`.
- Any significant Coverity results
- 57 Coverity defects, ALL under `rust-vendor/aws-lc-sys/` (vendored AWS-LC).
Zero findings reference package code. Not applicable to `upki`.
- Any significant shellcheck results 105 shellcheck warnings, concentrated in
`./debian/bin/dh-cargo-built-using` (a bash helper the package ships to work
around a dh-cargo limitation) plus vendored CI scripts (`aws-lc-rs`,
`tokio-native-tls`, etc.). They are style/portability items (`local` in sh
mode, `SC2155`, unquoted expansions). No injection or execution risk and none
in shipped maintainer scripts beyond the one documented helper.
- Any significant bandit results
- None
- Any significant govulncheck results
- None
- Any significant Semgrep results
- None
The design is sound: a single unprivileged timer that only fetches
hash-verified, TLS-protected data into a confined cache, plus a fully offline
revocation library and verifier. Rust eliminates the memory-safety class of bugs
that a parallel C/C++ implementation of this component could carry; index
parsing is defensive and explicitly tested against corrupt/oversized inputs; the
systemd unit is among the most hardened seen for an MIR. The only fail-open path
(`NotCoveredByRevocationData`) is deliberate browser parity (a very new
certificate is allowed when the filter cannot yet cover it) and is documented.
The main items to watch are process-level: the fetch uses system OpenSSL
(native-tls), so an OS OpenSSL vulnerability directly affects the update path,
and the upstream Rust test suite is disabled in the build so regressions rely on
separate autopkgtests.
Security team ACK for promoting upki to main. ACK, with a soft
condition:
Run the upstream `cargo test` suite in CI (currently disabled via
`override_dh_auto_test`) so revocation regressions are caught at build time.
The package is sound for promotion to main.
** Changed in: upki (Ubuntu)
Assignee: Ubuntu Security Team (ubuntu-security) => (unassigned)
** Changed in: upki (Ubuntu)
Status: New => In Progress
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2166514
Title:
[MIR] upki
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/upki/+bug/2166514/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs