# Spec for kuipa: post-quantum host enrollment for FreeIPA. # # DO NOT edit kuipa.spec directly — it is generated from this file by # 'make srpm'. Edit kuipa.spec.in and run 'make srpm' to regenerate. # # Source tarball ('make tarball'): # git archive --format=tar.gz --prefix=kuipa-{version}/ HEAD \ # -o kuipa-{version}.tar.gz # # Vendor tarball ('make vendor'): # cargo-vendor-filterer with Linux-only platform filtering. The tarball is # self-sufficient — it contains every crate source, so one SRPM builds # across Fedora versions. system-crates.txt (Source10) lists the crates # Fedora ships at the exact locked version; %build turns them into crate() # BuildRequires and drops them from the bundled(crate()) Provides. # # Crates available as system packages (installed via BuildRequires): # native-ossl, rustls-native-ossl, ring-native-ossl — COPR abbra/rust-deps # synta* 0.5.x, agirru-* 0.1.x — COPR abbra/rust-deps # 40+ other crates — Fedora rust-*-devel # # [patch.crates-io] in Cargo.toml replaces 'rand' and 'ring' with the # OpenSSL-backed crates in shims/, so no RustCrypto crate is linked (the 'deps' # CI job enforces this). They are path dependencies: cargo vendor skips them # and they ship in the source tarball (Source0). # # PREREQUISITE: install the crates above into the mock chroot before rebuilding # (COPR abbra/rust-deps is configured as an Additional repo of abbra/pqc): # mock --install rust-native-ossl-devel rust-synta-devel \ # rust-synta-certificate-devel rust-synta-mtc-devel \ # rust-synta-x509-verification-devel rust-agirru-crdt-devel \ # rust-agirru-engine-devel rust-hickory-resolver-devel \ # rust-rustls-platform-verifier-devel # Snapshot release identifiers — substituted by 'make srpm' via sed. # Do not edit these lines manually; the sed pattern matches the whole line. %global snapdate 202610290228 %global snapcommit 178f4b4 %bcond check 1 # The tang-pqc Clevis pin subpackage and its dracut module. # Disable with: rpmbuild --without clevis %bcond clevis 1 # Force --locked for all cargo invocations. Without it, cargo resolves the # dependency graph from scratch in the offline build environment and fails to # locate the vendored crates. %global __cargo_common_opts %{?_smp_mflags} -Z avoid-dev-deps --locked Name: kuipa Version: 0.1.0 Release: 1.%{snapdate}.git%{snapcommit}%{?dist} Summary: Post-quantum host enrollment for FreeIPA License: GPL-3.0-or-later URL: https://github.com/abbra/kuipa Source0: kuipa-%{version}.tar.gz # Vendor tarball — generated by cargo-vendor-filterer (see 'make vendor'). Source1: kuipa-%{version}-vendor.tar.gz # Systemd units and the httpd reverse-proxy snippet (from contrib/ in the # source tree; copied into _sourcedir by 'make srpm'). Source2: kuipad.service Source3: kuipad.socket Source4: kuipa-unseal.service Source5: ipa-client-complete.service Source6: kuipa-rotate.service Source7: kuipa-rotate.timer Source8: kuipa.conf Source9: kuipad.json.example # Manifest of crates Fedora ships at the exact locked version (written by # 'make vendor' via filter-vendor-fedora.sh). %generate_buildrequires emits # crate() BuildRequires from it; %build strips those crates from the # bundled(crate()) Provides in cargo-vendor.txt. Source10: system-crates.txt ExclusiveArch: %{rust_arches} BuildRequires: cargo-rpm-macros >= 26 # System OpenSSL >= 3.5 — every cryptographic operation runs on it through # native-ossl (ML-KEM and ML-DSA are FIPS 204/ML-KEM APIs in OpenSSL 3.5). # native-ossl-sys generates its FFI bindings with bindgen, which needs # libclang; rebuild this package whenever openssl-devel is updated. BuildRequires: pkgconfig(openssl) BuildRequires: openssl-devel BuildRequires: clang-devel # Kerberos GSSAPI — kuipa-gssapi's build script probes krb5-gssapi / # mit-krb5-gssapi and statically asserts the FFI layouts against the headers. BuildRequires: pkgconfig(krb5-gssapi) BuildRequires: krb5-devel # LDAP + SASL — kuipa-ldap links libldap and libsasl2 (GSSAPI bind over # LDAPI on the server, ldapi:// or ldap:// on the client). BuildRequires: pkgconfig(ldap) BuildRequires: openldap-devel BuildRequires: cyrus-sasl-devel # FIDO2 / WebAuthn passkeys — kuipa-fido2 links libfido2. The PQC-capable # libfido2 (kryoptic provider) is in COPR abbra/pqc. BuildRequires: pkgconfig(libfido2) BuildRequires: libfido2-devel # DNS SRV/DoT/DoH discovery in kuipa-tools uses hickory-resolver's ring TLS # features; Fedora's crate maps them onto the in-tree ring shim (shims/ring), # which re-exports ring-native-ossl. BuildRequires: rust-hickory-resolver-devel BuildRequires: rust-rustls-platform-verifier-devel # synta* and agirru* are packaged in COPR abbra/rust-deps (an Additional repo # of this project); install them into the chroot before rebuilding. BuildRequires: rust-synta-devel BuildRequires: rust-synta-certificate-devel BuildRequires: rust-synta-mtc-devel BuildRequires: rust-synta-x509-verification-devel BuildRequires: rust-native-ossl-devel BuildRequires: rust-native-ossl-sys-devel BuildRequires: rust-ring-native-ossl-devel BuildRequires: rust-rustls-native-ossl-devel BuildRequires: rust-agirru-crdt-devel BuildRequires: rust-agirru-engine-devel # Man pages (docs/man/*.scd -> gzipped groff). BuildRequires: scdoc # Provides %%systemd_post / %%systemd_preun / %%systemd_postun_with_restart and # %%{?systemd_requires} used in the package headers. BuildRequires: systemd-rpm-macros %{?systemd_requires} %description kuipa enrolls hosts into a FreeIPA realm with post-quantum credentials: a device key pair using ML-DSA (FIPS 204) and ML-KEM, an attested host keytab, and sealed keytabs or files that unlock with a post-quantum Tang binding. Enrollment works online, offline with a one-time voucher, and through a passkey. This package provides the host-side tools: kuipa (enrollment, key rotation, sealing), kuipa-unseal (boot-time and gssproxy keytab loading), and the systemd units that run them. kuipa-server provides the IPA server daemon. %files %license LICENSE %license LICENSE.dependencies %license cargo-vendor.txt %{_sbindir}/kuipa %{_libexecdir}/kuipa-unseal %{_bindir}/kuipa-voucher %{_bindir}/kuipa-witness %{_bindir}/kuipa-audit %{_bindir}/kuipa-mirror %{_bindir}/kuipa-gitsign %{_mandir}/man1/kuipa.1.gz %{_mandir}/man1/kuipa-voucher.1.gz %{_mandir}/man8/kuipa-unseal.8.gz %{_unitdir}/kuipa-unseal.service %{_unitdir}/ipa-client-complete.service %{_unitdir}/kuipa-rotate.service %{_unitdir}/kuipa-rotate.timer %doc contrib/gssproxy-kuipa.conf.example # ── Subpackage: server daemon ──────────────────────────────────────────────── %package server Summary: Post-quantum enrollment daemon for IPA servers Requires: %{name} = %{version}-%{release} # Fronted by httpd on /ipa/ku/; an IPA server already has it. %{?systemd_requires} %description server kuipad is the IPA server side of kuipa. It serves the enrollment endpoints under /ipa/ku/ behind httpd (a Unix socket, forwarded by mod_proxy), stores realm key material and escrow records, talks to 389-ds over LDAPI, and holds the realm signing and Tang keys in /var/lib/ipa/ku. Install this package on IPA servers; ipa-server-install configures it. %files server %license LICENSE %{_libexecdir}/kuipad %{_mandir}/man8/kuipad.8.gz %config(noreplace) %{_sysconfdir}/ipa/kuipad.json.example %{_unitdir}/kuipad.service %{_unitdir}/kuipad.socket %{_sysconfdir}/httpd/conf.d/kuipa.conf %doc docs/DESIGN.md %doc docs/DESIGN-schema.md # ── Subpackage: post-quantum Tang bindings (library + server tool) ─────────── %package -n tang-pqc Summary: Post-quantum Tang bindings with ML-DSA and ML-KEM %description -n tang-pqc tang-pqc is the protocol behind a post-quantum Tang seal: a Tang server publishes a signed Thin Protocol key, and a client wraps its key with ML-KEM against that key and proves the opening with ML-DSA. Nothing in the exchange depends on a classical signature algorithm. This package provides the 'tang-pqc' command (server key management, the 'thp' key fingerprint, seal and unseal. The Clevis pin and its dracut module are in clevis-pin-tang-pqc. The library crate is bundled in %name. %files -n tang-pqc %license LICENSE %{_bindir}/tang-pqc %{_mandir}/man1/tang-pqc.1.gz # ── Subpackage: Clevis pin ─────────────────────────────────────────────────── %if %{with clevis} %package -n clevis-pin-tang-pqc Summary: Clevis pin that seals with a post-quantum Tang binding BuildArch: noarch Requires: tang-pqc = %{version}-%{release} Requires: clevis-luks %description -n clevis-pin-tang-pqc clevis-pin-tang-pqc adds the 'tang-pqc' pin to Clevis: the tang-pqc binary, invoked through the clevis-encrypt-tang-pqc and clevis-decrypt-tang-pqc names. A LUKS volume sealed with this pin opens with a post-quantum Tang binding, including from the initramfs (the dracut module ships here, in the directory layout clevis uses for its own pins). Disable the pin with: rpmbuild --without clevis %files -n clevis-pin-tang-pqc %{_bindir}/clevis-encrypt-tang-pqc %{_bindir}/clevis-decrypt-tang-pqc %dir %{_prefix}/lib/dracut/modules.d/60clevis-pin-tang-pqc %{_prefix}/lib/dracut/modules.d/60clevis-pin-tang-pqc/module-setup.sh %endif # ── Prep ─────────────────────────────────────────────────────────────────────── %prep # -a1 unpacks Source1 (vendor/ and Cargo.lock) inside the source directory. %autosetup -n %{name}-%{version} -p1 -a1 # Redirect every crates-io lookup into vendor/. The [patch.crates-io] entries # (rand, ring) are path dependencies inside this workspace and need no # redirect: the system hickory-resolver's dependency on 'ring' resolves to # shims/ring exactly as it does in the tree. %cargo_prep -v vendor # brp-mangle-shebangs aborts on the +x '#!/usr/bin/env python' scripts some # vendored crates ship (unicode-normalization/scripts/unicode.py). find vendor/ -type f -exec chmod -x {} \; # ── Generate BuildRequires ───────────────────────────────────────────────────── %generate_buildrequires # Unversioned crate() BuildRequires, so rust-*-devel packages are installed # when this Fedora ships that crate; the vendor copy is the fallback for the # crates it does not. Versioned requirements would drag this Fedora's # versions into older chroots. while read -r _name _ver _cksum; do echo "crate($_name)" done < %{SOURCE10} # %%cargo_generate_buildrequires is deliberately not used: it would demand the # dev-dependencies of the bench harnesses (criterion and its plotters chain), # which no Fedora repo has. # ── Build ────────────────────────────────────────────────────────────────────── %build # Every crate compiles from vendor/ exactly as Cargo.lock pins it. Fedora's # crate packages arrive through the generated crate() BuildRequires and are # then stripped from the bundled(crate()) Provides below, because they are not # bundled when present — they do not replace the vendor copies: Fedora patches # the Cargo.toml of its crate packages, and cargo --locked rejects the # resulting metadata mismatch against Cargo.lock. %cargo_build -- --workspace # Bundled-dependency license summary and manifest (Fedora policy). %{cargo_license_summary} %{cargo_license} > LICENSE.dependencies %{cargo_vendor_manifest} # Bundled(crate()) Provides describe the shipped binaries. Keep only the # crates in the linked dependency graph: the criterion bench chain, the # test-only deps (synta-krb5, the DoT/DoH test server) and the Windows / # macOS / WASM crates cargo vendor ships but this target never compiles are # dropped. Derived from the same Cargo.lock in the same buildroot, so a # dependency change cannot leave a stale hand-written list behind. The # [patch.crates-io] replacements (rand, ring) never reach the manifest: they # are path dependencies in Source0, not vendored crates. cargo tree --workspace --locked -e normal,build --prefix none \ | awk 'NF>=2 && $2 ~ /^v[0-9]/ { print $1 " " $2 }' | sort -u > cargo-linked.txt awk 'NR==FNR { keep[$1 " " $2] = 1; next } ($1 " " $2) in keep' \ cargo-linked.txt cargo-vendor.txt > cargo-vendor.txt.filtered mv -f cargo-vendor.txt.filtered cargo-vendor.txt && rm -f cargo-linked.txt # Fedora ships these at the locked version: not bundled. cargo-vendor.txt # writes versions with a 'v' prefix ("hickory-resolver v0.26.3"), so the # pattern needs it too; the registry directory is the plain "name-version". while read -r _name _ver _cksum; do if [ -d "/usr/share/cargo/registry/${_name}-${_ver}" ]; then sed -i "/^${_name} v${_ver}\$/d" cargo-vendor.txt fi done < %{SOURCE10} # Man pages: docs/man/*.scd -> groff -> gzip. for page in kuipa.1 kuipa-voucher.1 kuipa-unseal.8 kuipad.8 tang-pqc.1; do scdoc < docs/man/$page.scd | gzip -9 > $page.gz done # ── Install ──────────────────────────────────────────────────────────────────── %install # %%cargo_install is not used for workspace binaries (Fedora Rust packaging # guidelines): copy the built executables out of target/rpm/ explicitly. install -Dpm 0755 target/rpm/kuipa %{buildroot}%{_sbindir}/kuipa install -Dpm 0755 target/rpm/kuipa-unseal %{buildroot}%{_libexecdir}/kuipa-unseal install -Dpm 0755 target/rpm/kuipa-voucher %{buildroot}%{_bindir}/kuipa-voucher install -Dpm 0755 target/rpm/kuipa-witness %{buildroot}%{_bindir}/kuipa-witness install -Dpm 0755 target/rpm/kuipa-audit %{buildroot}%{_bindir}/kuipa-audit install -Dpm 0755 target/rpm/kuipa-mirror %{buildroot}%{_bindir}/kuipa-mirror install -Dpm 0755 target/rpm/kuipa-gitsign %{buildroot}%{_bindir}/kuipa-gitsign install -Dpm 0755 target/rpm/kuipad %{buildroot}%{_libexecdir}/kuipad install -Dpm 0755 target/rpm/tang-pqc %{buildroot}%{_bindir}/tang-pqc # kuipa-unseal and kuipa run from systemd units and from ipa-client-install: # both are root-only programs, hence /usr/sbin; the other tools are user # commands (/usr/bin). ExecStart in the units already names these paths. for gz in kuipa.1 kuipa-voucher.1 kuipa-unseal.8 kuipad.8 tang-pqc.1; do section=${gz##*.}; section=${section%%.*} install -Dpm 0644 $gz.gz %{buildroot}%{_mandir}/man${section}/${gz}.gz done # Systemd units (Source2..Source7, copied from contrib/ by 'make srpm'). install -Dpm 0644 %{SOURCE2} %{buildroot}%{_unitdir}/kuipad.service install -Dpm 0644 %{SOURCE3} %{buildroot}%{_unitdir}/kuipad.socket install -Dpm 0644 %{SOURCE4} %{buildroot}%{_unitdir}/kuipa-unseal.service install -Dpm 0644 %{SOURCE5} %{buildroot}%{_unitdir}/ipa-client-complete.service install -Dpm 0644 %{SOURCE6} %{buildroot}%{_unitdir}/kuipa-rotate.service install -Dpm 0644 %{SOURCE7} %{buildroot}%{_unitdir}/kuipa-rotate.timer # httpd reverse proxy for /ipa/ku/ and the daemon's configuration template. install -Dpm 0644 %{SOURCE8} %{buildroot}%{_sysconfdir}/httpd/conf.d/kuipa.conf install -Dpm 0644 %{SOURCE9} %{buildroot}%{_sysconfdir}/ipa/kuipad.json.example # The Clevis pin is the same binary, chosen by its name (as in # contrib/clevis/install.sh); the dracut module brings it into the initramfs. %if %{with clevis} ln -s tang-pqc %{buildroot}%{_bindir}/clevis-encrypt-tang-pqc ln -s tang-pqc %{buildroot}%{_bindir}/clevis-decrypt-tang-pqc install -Dpm 0755 contrib/clevis/dracut/60clevis-pin-tang-pqc/module-setup.sh \ %{buildroot}%{_prefix}/lib/dracut/modules.d/60clevis-pin-tang-pqc/module-setup.sh %endif # ── Check ────────────────────────────────────────────────────────────────────── %if %{with check} %check # Tests that need a KDC, a PKINIT CA or a kryoptic token skip themselves when # the tools are absent (tests/support/kdc.rs, src/pkcs11.rs); the CI job that # wants them runs with the tools installed and KUIPA_REQUIRE_KDC=1. %cargo_test -- --workspace %endif # ── Systemd scriptlets ───────────────────────────────────────────────────────── %post %systemd_post kuipa-unseal.service ipa-client-complete.service kuipa-rotate.timer %preun %systemd_preun kuipa-unseal.service ipa-client-complete.service kuipa-rotate.timer %postun %systemd_postun_with_restart kuipa-unseal.service %post server %systemd_post kuipad.socket kuipad.service %preun server %systemd_preun kuipad.socket kuipad.service %postun server %systemd_postun_with_restart kuipad.service # The rotate timer is not restarted on upgrade: it is a monthly calendar timer, # and restarting it would reschedule the next run. %changelog %autochangelog