defmodule GitGud.Packages.Apt.Native do
@moduledoc """
The two things the apt repository needs that OTP cannot do, via the
`native/gitgud_apt` Rustler NIF.
**OpenPGP signatures.** apt requires a signed `Release`, and
`:public_key` speaks PKCS and X.509 — OpenPGP is a different packet
format with its own key material, and nothing in the Erlang
distribution implements it. This is the only format in the registry
that needed a NIF; the rest get by on `:crypto` and `:public_key`.
**xz and zstd.** A `.deb`'s control member is `control.tar.xz` on
anything since squeeze and `control.tar.zst` on Ubuntu, and
`:erl_tar` handles gzip and nothing else. The same encoder produces
the `Packages.xz` a Debian repository is expected to offer.
## Why these backends
This project ships k8s manifests, so a NIF that changes an image's
package list is a deployment change wearing a dependency's clothes.
The cost was measured by building the Dockerfile, not reasoned about:
crypto-openssl builder: libclang-dev runtime: — (libssl already named)
crypto-nettle builder: libclang-dev, runtime: — (but libnettle/libgmp
nettle-dev, are present only as a
libgmp-dev transitive dep of git)
crypto-rust builder: — runtime: — variable-time
`libclang-dev` is unavoidable for any backend that binds C:
`openssl-sys` generates its bindings with bindgen, which dlopens
libclang. Only the pure-Rust backend escapes it, and sequoia gates
that behind `allow-variable-time-crypto` — the wrong trade for code
holding a signing key.
So: OpenSSL. One package, in the build stage only, for constant-time
audited crypto. The runtime image is unchanged.
The compression crates (`lzma-rs`, `ruzstd`) are pure Rust and cost
nothing either way.
The compression crates (`lzma-rs`, `ruzstd`) are pure Rust for the
same reason: no new system libraries.
Keys and signatures cross the boundary as armored ASCII, so nothing
on the Elixir side handles OpenPGP packets.
"""
use Rustler, otp_app: :git_gud, crate: "gitgud_apt"
@doc """
Generate a signing certificate for `user_id`. Returns
`{:ok, {public_armored, secret_armored}}` — the public half goes in
a client's `/etc/apt/keyrings`, the secret half is stored encrypted
and never leaves the forge.
"""
@spec generate_key(String.t()) :: {:ok, {String.t(), String.t()}} | {:error, String.t()}
def generate_key(_user_id), do: :erlang.nif_error(:nif_not_loaded)
@doc """
Clearsign `data` — the `InRelease` shape, signature and signed text
in one file, which is what apt prefers.
"""
@spec sign_clearsign(String.t(), String.t()) :: {:ok, String.t()} | {:error, String.t()}
def sign_clearsign(_secret_armored, _data), do: :erlang.nif_error(:nif_not_loaded)
@doc """
Produce a detached armored signature — the `Release.gpg` shape,
which older apt falls back to when `InRelease` is absent.
"""
@spec sign_detached(String.t(), String.t()) :: {:ok, String.t()} | {:error, String.t()}
def sign_detached(_secret_armored, _data), do: :erlang.nif_error(:nif_not_loaded)
@doc """
Verify a detached signature. Not used to serve anything — it lets
tests assert what apt will assert.
"""
@spec verify_detached(String.t(), String.t(), String.t()) ::
{:ok, boolean()} | {:error, String.t()}
def verify_detached(_public_armored, _data, _signature_armored),
do: :erlang.nif_error(:nif_not_loaded)
@doc "Decompress an xz stream."
@spec xz_decompress(binary()) :: {:ok, binary()} | {:error, String.t()}
def xz_decompress(_data), do: :erlang.nif_error(:nif_not_loaded)
@doc "Compress to xz."
@spec xz_compress(binary()) :: {:ok, binary()} | {:error, String.t()}
def xz_compress(_data), do: :erlang.nif_error(:nif_not_loaded)
@doc "Decompress a zstd stream."
@spec zstd_decompress(binary()) :: {:ok, binary()} | {:error, String.t()}
def zstd_decompress(_data), do: :erlang.nif_error(:nif_not_loaded)
@doc """
Decompress an xz stream, or fail once the output passes `limit`
bytes — for input whose decompressed size nothing else bounds.
"""
@spec xz_decompress_capped(binary(), non_neg_integer()) ::
{:ok, binary()} | {:error, String.t()}
def xz_decompress_capped(_data, _limit), do: :erlang.nif_error(:nif_not_loaded)
@doc "As `xz_decompress_capped/2`, for zstd."
@spec zstd_decompress_capped(binary(), non_neg_integer()) ::
{:ok, binary()} | {:error, String.t()}
def zstd_decompress_capped(_data, _limit), do: :erlang.nif_error(:nif_not_loaded)
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
4.6 KiB · text
History
69f26e0