From 0661ded273a05ac7239d178c7f78f3a0dfb32f60 Mon Sep 17 00:00:00 2001 From: oxmc7769 Date: Tue, 10 Mar 2026 19:14:51 -0700 Subject: [PATCH] Initial idea --- .gitignore | 12 + README.md | 224 ++ daemon/CMakeLists.txt | 24 + daemon/age-verification-daemon.c | 358 +++ daemon/age-verification-daemon.service | 31 + daemon/org.freedesktop.AgeVerification1.conf | 22 + debian/age-verification-daemon.8 | 78 + debian/age-verification-services.install | 6 + debian/age-verification-services.tmpfiles | 8 + debian/age-verification-sync.1 | 77 + debian/changelog | 5 + debian/control | 43 + debian/copyright | 27 + debian/postinst | 47 + debian/postrm | 25 + debian/prerm | 23 + debian/rules | 48 + debian/source/format | 1 + debian/watch | 4 + sync-client/Cargo.lock | 2613 ++++++++++++++++++ sync-client/Cargo.toml | 47 + sync-client/src/main.rs | 327 +++ 22 files changed, 4050 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 daemon/CMakeLists.txt create mode 100644 daemon/age-verification-daemon.c create mode 100644 daemon/age-verification-daemon.service create mode 100644 daemon/org.freedesktop.AgeVerification1.conf create mode 100644 debian/age-verification-daemon.8 create mode 100644 debian/age-verification-services.install create mode 100644 debian/age-verification-services.tmpfiles create mode 100644 debian/age-verification-sync.1 create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100644 debian/prerm create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/watch create mode 100644 sync-client/Cargo.lock create mode 100644 sync-client/Cargo.toml create mode 100644 sync-client/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d607383 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +debian/build-daemon +debian/cargo-home +debian/.debhelper +debian/debhelper-build-stamp +debian/age-verification-services.substvars +debian/files +debian/age-verification-services/ +debian/tmp/ +sync-client/target/ +*.deb +*.buildinfo +*.changes diff --git a/README.md b/README.md new file mode 100644 index 0000000..637f20a --- /dev/null +++ b/README.md @@ -0,0 +1,224 @@ +# age-verification + +Implementation of `org.freedesktop.AgeVerification1` — a D-Bus interface for +California SB-976-style age bracket compliance on Linux, with an optional +privacy-preserving account sync backend. + +--- + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Linux OS (Kicksecure / Whonix / Debian / etc.) │ +│ │ +│ ┌──────────────────────────────────┐ │ +│ │ age-verification-daemon (C) │ system D-Bus │ +│ │ /var/lib/age-verification/ │ org.freedesktop. │ +│ │ ages.conf (root:root 0600) │ AgeVerification1 │ +│ └────────────────┬─────────────────┘ │ +│ │ D-Bus GetAgeBracket │ +│ ┌────────────────▼─────────────────┐ │ +│ │ age-verification-sync (Rust) │──────────────────────► │ +│ │ Layered encryption: │ HTTPS (TLS) │ +│ │ 1. TLS transport │ POST /api/v1/ │ +│ │ 2. RSA-OAEP server pubkey │ age-bracket │ +│ │ 3. Ed25519 device sig │ │ +│ │ 4. AES-256-GCM local keystore │ │ +│ └──────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────┐ + │ age-verification-server (Node) │ + │ SQLite: stores bracket (1-4) │ + │ ONLY — no raw age/dob stored │ + │ │ + │ GET /api/v1/query/bracket │◄── OS services / + │ (returns only bracket int) │ app stores + └─────────────────────────────────┘ +``` + +### Age brackets + +| Bracket | Meaning | +|---------|----------------------| +| 1 | Under 13 | +| 2 | 13 – 15 (inclusive) | +| 3 | 16 – 17 (inclusive) | +| 4 | 18 or older | + +--- + +## Components + +### 1. `daemon/` — C D-Bus daemon + +Implements `org.freedesktop.AgeVerification1` on the **system bus**. +Runs as root. Stores age brackets (not raw ages) in `/var/lib/age-verification/ages.conf`. + +**Build:** +```bash +cd daemon +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build +sudo cmake --install build +sudo systemctl daemon-reload +sudo systemctl enable --now age-verification-daemon +``` + +**Test via dbus-send:** +```bash +# Set age +sudo dbus-send --system --print-reply \ + --dest=org.freedesktop.AgeVerification1 \ + /org/freedesktop/AgeVerification1 \ + org.freedesktop.AgeVerification1.SetAge \ + string:youruser uint32:25 + +# Set date of birth +sudo dbus-send --system --print-reply \ + --dest=org.freedesktop.AgeVerification1 \ + /org/freedesktop/AgeVerification1 \ + org.freedesktop.AgeVerification1.SetDateOfBirth \ + string:youruser string:2000-06-15 + +# Get bracket +dbus-send --system --print-reply \ + --dest=org.freedesktop.AgeVerification1 \ + /org/freedesktop/AgeVerification1 \ + org.freedesktop.AgeVerification1.GetAgeBracket \ + string:youruser +``` + +--- + +### 2. `sync-client/` — Rust sync client + +Reads the bracket from the local daemon and uploads it to your account server +using layered encryption: + +1. **TLS** — all traffic over HTTPS via rustls +2. **RSA-4096 OAEP-SHA256** — bracket payload encrypted with the server's public key +3. **Ed25519 device keypair** — payload signed to prove device identity +4. **AES-256-GCM + PBKDF2** — local keystore encrypted with account passphrase + +**Build:** +```bash +cd sync-client +cargo build --release +# Binary at target/release/age-verification-sync +``` + +**First-time device registration** (do this once after account creation): +```bash +# Register device with server — you'll need to extract the Ed25519 pubkey +# from the keystore or add a `--print-pubkey` subcommand for this. +# See TODO in src/main.rs for a planned 'register-device' subcommand. +``` + +**Sync:** +```bash +age-verification-sync \ + --user alice \ + --server https://accounts.example.com \ + --token "$YOUR_ACCOUNT_JWT" +``` + +**Deploy as a systemd timer** (syncs on login or periodically): +```ini +# /etc/systemd/user/age-verification-sync.timer +[Unit] +Description=Sync age bracket + +[Timer] +OnBootSec=30s +OnUnitActiveSec=24h + +[Install] +WantedBy=timers.target +``` + +--- + +### 3. `server/` — Node.js account server + +Express + SQLite backend. Stores **only the bracket integer** per account. + +**Setup:** +```bash +cd server +npm install +npm run keygen # generates keys/server-private.pem + server-public.pem +cp .env.example .env # fill in JWT_SECRET +mkdir -p data +npm start +``` + +**Distribute the public key to OS installs:** +```bash +scp keys/server-public.pem \ + root@client:/etc/age-verification/server-pubkey.pem +``` + +**API endpoints:** + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/api/v1/auth/register` | — | Create account | +| POST | `/api/v1/auth/login` | — | Get JWT | +| POST | `/api/v1/auth/register-device` | JWT | Register device Ed25519 pubkey | +| POST | `/api/v1/age-bracket` | JWT | Upload encrypted bracket from sync client | +| GET | `/api/v1/query/bracket` | JWT | Get bracket for this account (returns `{bracket: N}` only) | + +**Production deployment:** +- Put nginx in front (TLS termination, `proxy_pass http://127.0.0.1:3000`) +- Use a process manager: `pm2 start src/index.js --name age-verif` +- Back up `data/ageverif.db` and `keys/server-private.pem` (separately, securely) + +--- + +## Privacy properties + +| Property | How it's achieved | +|----------|------------------| +| Minimum data on disk | Daemon stores bracket (1–4), not raw age | +| Minimum data on server | Server stores bracket (1–4), not raw age | +| Local file not world-readable | `/var/lib/age-verification/ages.conf` is root:root 0600 | +| Local keystore encrypted | AES-256-GCM keyed from PBKDF2(account passphrase, salt) | +| Transport encrypted | TLS (reqwest/rustls, HTTPS only) | +| Server can't be MITM'd | Payload also RSA-OAEP encrypted with server pubkey | +| Device identity verified | Ed25519 signature on every sync payload | +| Server returns minimum | `/query/bracket` returns `{bracket: N}` only | + +--- + +## D-Bus interface reference + +```xml + + + + + + + + + + + + + + + + + +``` + +**Errors:** +- `org.freedesktop.AgeVerification1.Error.NoSuchUser` +- `org.freedesktop.AgeVerification1.Error.PermissionDenied` +- `org.freedesktop.AgeVerification1.Error.InvalidDate` +- `org.freedesktop.AgeVerification1.Error.AgeUndefined` +- `org.freedesktop.AgeVerification1.Error.Internal` diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt new file mode 100644 index 0000000..59a81d0 --- /dev/null +++ b/daemon/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.14) +project(age-verification-daemon C) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(DBUS REQUIRED dbus-1) +pkg_check_modules(SYSTEMD systemd) + +add_executable(age-verification-daemon age-verification-daemon.c) +target_include_directories(age-verification-daemon PRIVATE ${DBUS_INCLUDE_DIRS}) +target_link_libraries(age-verification-daemon ${DBUS_LIBRARIES}) +target_compile_options(age-verification-daemon PRIVATE ${DBUS_CFLAGS_OTHER} -Wall -Wextra -O2) + +if(SYSTEMD_FOUND) + target_compile_definitions(age-verification-daemon PRIVATE HAVE_SYSTEMD) + target_include_directories(age-verification-daemon PRIVATE ${SYSTEMD_INCLUDE_DIRS}) + target_link_libraries(age-verification-daemon ${SYSTEMD_LIBRARIES} -lsystemd) + target_compile_options(age-verification-daemon PRIVATE ${SYSTEMD_CFLAGS_OTHER}) +endif() + +install(TARGETS age-verification-daemon DESTINATION lib/age-verification) +install(FILES age-verification-daemon.service + DESTINATION lib/systemd/system) +install(FILES org.freedesktop.AgeVerification1.conf + DESTINATION share/dbus-1/system.d) diff --git a/daemon/age-verification-daemon.c b/daemon/age-verification-daemon.c new file mode 100644 index 0000000..130c3ae --- /dev/null +++ b/daemon/age-verification-daemon.c @@ -0,0 +1,358 @@ +/* + * age-verification-daemon.c + * Implements org.freedesktop.AgeVerification1 on the system D-Bus. + * Runs as root. Stores age data in /var/lib/age-verification/ (root-owned, 0600). + * + * Dependencies: libdbus-1, libsystemd (optional, for sd_notify) + * Build: gcc -o age-verification-daemon age-verification-daemon.c \ + * $(pkg-config --cflags --libs dbus-1) -lsystemd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Optional systemd notify support */ +#ifdef HAVE_SYSTEMD +#include +#define NOTIFY_READY() sd_notify(0, "READY=1") +#else +#define NOTIFY_READY() do {} while(0) +#endif + +#define STORAGE_DIR "/var/lib/age-verification" +#define STORAGE_FILE STORAGE_DIR "/ages.conf" +#define DBUS_SERVICE "org.freedesktop.AgeVerification1" +#define DBUS_IFACE "org.freedesktop.AgeVerification1" +#define DBUS_PATH "/org/freedesktop/AgeVerification1" + +#define ERR_NO_USER "org.freedesktop.AgeVerification1.Error.NoSuchUser" +#define ERR_PERM_DENIED "org.freedesktop.AgeVerification1.Error.PermissionDenied" +#define ERR_INVALID_DATE "org.freedesktop.AgeVerification1.Error.InvalidDate" +#define ERR_AGE_UNDEF "org.freedesktop.AgeVerification1.Error.AgeUndefined" +#define ERR_INTERNAL "org.freedesktop.AgeVerification1.Error.Internal" + +/* Age brackets as per California law */ +typedef enum { + BRACKET_UNDER_13 = 1, + BRACKET_13_TO_15 = 2, + BRACKET_16_TO_17 = 3, + BRACKET_ADULT = 4 +} AgeBracket; + +/* --------------------------------------------------------------------------- + * Utility: get UID_MIN / UID_MAX from /etc/login.defs + * --------------------------------------------------------------------------- */ +static void get_uid_range(uid_t *uid_min, uid_t *uid_max) { + *uid_min = 1000; + *uid_max = 60000; + FILE *f = fopen("/etc/login.defs", "r"); + if (!f) return; + char line[256]; + while (fgets(line, sizeof(line), f)) { + unsigned long val; + if (sscanf(line, "UID_MIN %lu", &val) == 1) *uid_min = (uid_t)val; + if (sscanf(line, "UID_MAX %lu", &val) == 1) *uid_max = (uid_t)val; + } + fclose(f); +} + +/* --------------------------------------------------------------------------- + * Utility: validate that 'username' is a real, non-system UNIX user + * --------------------------------------------------------------------------- */ +static int validate_user(const char *username, uid_t *out_uid) { + uid_t uid_min, uid_max; + get_uid_range(&uid_min, &uid_max); + errno = 0; + struct passwd *pw = getpwnam(username); + if (!pw) return 0; + if (pw->pw_uid < uid_min || pw->pw_uid > uid_max) return 0; + if (out_uid) *out_uid = pw->pw_uid; + return 1; +} + +/* --------------------------------------------------------------------------- + * Utility: get the UID of the D-Bus caller + * --------------------------------------------------------------------------- */ +static uid_t get_caller_uid(DBusConnection *conn, DBusMessage *msg) { + const char *sender = dbus_message_get_sender(msg); + if (!sender) return (uid_t)-1; + DBusError err; + dbus_error_init(&err); + unsigned long uid = dbus_bus_get_unix_user(conn, sender, &err); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return (uid_t)-1; + } + return (uid_t)uid; +} + +/* --------------------------------------------------------------------------- + * Storage: read bracket for a username. Returns 0 if not found. + * File format: one "username=bracket" per line. + * --------------------------------------------------------------------------- */ +static int read_bracket(const char *username) { + FILE *f = fopen(STORAGE_FILE, "r"); + if (!f) return 0; + char line[512]; + int found = 0; + while (fgets(line, sizeof(line), f)) { + /* strip newline */ + line[strcspn(line, "\n")] = 0; + char *eq = strchr(line, '='); + if (!eq) continue; + *eq = 0; + if (strcmp(line, username) == 0) { + found = atoi(eq + 1); + break; + } + } + fclose(f); + return found; +} + +/* --------------------------------------------------------------------------- + * Storage: write or update bracket for a username + * --------------------------------------------------------------------------- */ +static int write_bracket(const char *username, int bracket) { + /* Ensure storage dir exists with correct perms */ + struct stat st; + if (stat(STORAGE_DIR, &st) != 0) { + if (mkdir(STORAGE_DIR, 0700) != 0) return 0; + } + + /* Read existing entries, skip the one we're updating */ + char tmpfile[] = STORAGE_FILE ".tmp.XXXXXX"; + int tmpfd = mkstemp(tmpfile); + if (tmpfd < 0) return 0; + fchmod(tmpfd, 0600); + FILE *out = fdopen(tmpfd, "w"); + if (!out) { close(tmpfd); return 0; } + + FILE *in = fopen(STORAGE_FILE, "r"); + if (in) { + char line[512]; + while (fgets(line, sizeof(line), in)) { + char copy[512]; + strncpy(copy, line, sizeof(copy)); + copy[strcspn(copy, "\n")] = 0; + char *eq = strchr(copy, '='); + if (eq) { *eq = 0; } + if (!eq || strcmp(copy, username) != 0) { + fputs(line, out); + } + } + fclose(in); + } + + fprintf(out, "%s=%d\n", username, bracket); + fclose(out); + rename(tmpfile, STORAGE_FILE); + chmod(STORAGE_FILE, 0600); + return 1; +} + +/* --------------------------------------------------------------------------- + * Age bracket calculation + * --------------------------------------------------------------------------- */ +static AgeBracket bracket_from_years(unsigned int years) { + if (years < 13) return BRACKET_UNDER_13; + if (years < 16) return BRACKET_13_TO_15; + if (years < 18) return BRACKET_16_TO_17; + return BRACKET_ADULT; +} + +static AgeBracket bracket_from_dob(const char *iso_date) { + /* Parse YYYY-MM-DD */ + int y, m, d; + if (sscanf(iso_date, "%d-%d-%d", &y, &m, &d) != 3) return 0; + if (m < 1 || m > 12 || d < 1 || d > 31) return 0; + + time_t now = time(NULL); + struct tm *t = gmtime(&now); + int today_y = t->tm_year + 1900; + int today_m = t->tm_mon + 1; + int today_d = t->tm_mday; + + int age = today_y - y; + if (today_m < m || (today_m == m && today_d < d)) age--; + if (age < 0) return 0; + return bracket_from_years((unsigned int)age); +} + +/* --------------------------------------------------------------------------- + * D-Bus method: SetAge + * --------------------------------------------------------------------------- */ +static DBusMessage *handle_set_age(DBusConnection *conn, DBusMessage *msg) { + const char *username; + dbus_uint32_t years; + DBusError err; + dbus_error_init(&err); + + if (!dbus_message_get_args(msg, &err, + DBUS_TYPE_STRING, &username, + DBUS_TYPE_UINT32, &years, + DBUS_TYPE_INVALID)) { + DBusMessage *r = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, err.message); + dbus_error_free(&err); + return r; + } + + uid_t target_uid; + if (!validate_user(username, &target_uid)) + return dbus_message_new_error(msg, ERR_NO_USER, "No such non-system user"); + + uid_t caller_uid = get_caller_uid(conn, msg); + if (caller_uid != 0 && caller_uid != target_uid) + return dbus_message_new_error(msg, ERR_PERM_DENIED, "Permission denied"); + + AgeBracket bracket = bracket_from_years(years); + if (!write_bracket(username, (int)bracket)) + return dbus_message_new_error(msg, ERR_INTERNAL, "Failed to write storage"); + + return dbus_message_new_method_return(msg); +} + +/* --------------------------------------------------------------------------- + * D-Bus method: SetDateOfBirth + * --------------------------------------------------------------------------- */ +static DBusMessage *handle_set_dob(DBusConnection *conn, DBusMessage *msg) { + const char *username, *date; + DBusError err; + dbus_error_init(&err); + + if (!dbus_message_get_args(msg, &err, + DBUS_TYPE_STRING, &username, + DBUS_TYPE_STRING, &date, + DBUS_TYPE_INVALID)) { + DBusMessage *r = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, err.message); + dbus_error_free(&err); + return r; + } + + uid_t target_uid; + if (!validate_user(username, &target_uid)) + return dbus_message_new_error(msg, ERR_NO_USER, "No such non-system user"); + + uid_t caller_uid = get_caller_uid(conn, msg); + if (caller_uid != 0 && caller_uid != target_uid) + return dbus_message_new_error(msg, ERR_PERM_DENIED, "Permission denied"); + + AgeBracket bracket = bracket_from_dob(date); + if (bracket == 0) + return dbus_message_new_error(msg, ERR_INVALID_DATE, "Invalid ISO8601 date"); + + if (!write_bracket(username, (int)bracket)) + return dbus_message_new_error(msg, ERR_INTERNAL, "Failed to write storage"); + + return dbus_message_new_method_return(msg); +} + +/* --------------------------------------------------------------------------- + * D-Bus method: GetAgeBracket + * --------------------------------------------------------------------------- */ +static DBusMessage *handle_get_bracket(DBusConnection *conn, DBusMessage *msg) { + const char *username; + DBusError err; + dbus_error_init(&err); + + if (!dbus_message_get_args(msg, &err, + DBUS_TYPE_STRING, &username, + DBUS_TYPE_INVALID)) { + DBusMessage *r = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, err.message); + dbus_error_free(&err); + return r; + } + + uid_t target_uid; + if (!validate_user(username, &target_uid)) + return dbus_message_new_error(msg, ERR_NO_USER, "No such non-system user"); + + uid_t caller_uid = get_caller_uid(conn, msg); + if (caller_uid != 0 && caller_uid != target_uid) + return dbus_message_new_error(msg, ERR_PERM_DENIED, "Permission denied"); + + int bracket = read_bracket(username); + if (bracket == 0) + return dbus_message_new_error(msg, ERR_AGE_UNDEF, "No age configured for user"); + + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_uint32_t val = (dbus_uint32_t)bracket; + dbus_message_append_args(reply, DBUS_TYPE_UINT32, &val, DBUS_TYPE_INVALID); + return reply; +} + +/* --------------------------------------------------------------------------- + * D-Bus message dispatch + * --------------------------------------------------------------------------- */ +static DBusHandlerResult message_handler(DBusConnection *conn, + DBusMessage *msg, void *data) { + (void)data; + + if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (!dbus_message_has_interface(msg, DBUS_IFACE)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + DBusMessage *reply = NULL; + const char *member = dbus_message_get_member(msg); + + if (strcmp(member, "SetAge") == 0) reply = handle_set_age(conn, msg); + else if (strcmp(member, "SetDateOfBirth") == 0) reply = handle_set_dob(conn, msg); + else if (strcmp(member, "GetAgeBracket") == 0) reply = handle_get_bracket(conn, msg); + else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (reply) { + dbus_connection_send(conn, reply, NULL); + dbus_message_unref(reply); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +/* --------------------------------------------------------------------------- + * Main + * --------------------------------------------------------------------------- */ +int main(void) { + DBusError err; + dbus_error_init(&err); + + DBusConnection *conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "D-Bus connection error: %s\n", err.message); + dbus_error_free(&err); + return 1; + } + + int ret = dbus_bus_request_name(conn, DBUS_SERVICE, + DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "Name request error: %s\n", err.message); + dbus_error_free(&err); + return 1; + } + if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + fprintf(stderr, "Not primary owner of %s\n", DBUS_SERVICE); + return 1; + } + + static const DBusObjectPathVTable vtable = { + .message_function = message_handler + }; + dbus_connection_register_object_path(conn, DBUS_PATH, &vtable, NULL); + + NOTIFY_READY(); + fprintf(stdout, "age-verification-daemon running on system bus\n"); + + while (dbus_connection_read_write_dispatch(conn, -1)); + return 0; +} diff --git a/daemon/age-verification-daemon.service b/daemon/age-verification-daemon.service new file mode 100644 index 0000000..dc9d2d5 --- /dev/null +++ b/daemon/age-verification-daemon.service @@ -0,0 +1,31 @@ +[Unit] +Description=Age Verification D-Bus Daemon (org.freedesktop.AgeVerification1) +Documentation=https://github.com/your-org/age-verification +After=dbus.service +Requires=dbus.service + +[Service] +Type=notify +ExecStart=/usr/lib/age-verification/age-verification-daemon +Restart=on-failure +RestartSec=5 + +# Security hardening +User=root +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +ReadWritePaths=/var/lib/age-verification +PrivateTmp=yes +PrivateDevices=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +RestrictAddressFamilies=AF_UNIX +RestrictNamespaces=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM + +[Install] +WantedBy=multi-user.target diff --git a/daemon/org.freedesktop.AgeVerification1.conf b/daemon/org.freedesktop.AgeVerification1.conf new file mode 100644 index 0000000..0e8125d --- /dev/null +++ b/daemon/org.freedesktop.AgeVerification1.conf @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/debian/age-verification-daemon.8 b/debian/age-verification-daemon.8 new file mode 100644 index 0000000..4fd7ed6 --- /dev/null +++ b/debian/age-verification-daemon.8 @@ -0,0 +1,78 @@ +.TH AGE-VERIFICATION-DAEMON 8 "2026-03-10" "age-verification-services 0.1.0" +.SH NAME +age-verification-daemon \- D-Bus daemon implementing org.freedesktop.AgeVerification1 +.SH SYNOPSIS +.B age-verification-daemon +.SH DESCRIPTION +.B age-verification-daemon +is a system D-Bus service that implements the +.B org.freedesktop.AgeVerification1 +interface, providing age bracket data to application stores and OS-level +services in compliance with California SB-976 and similar legislation. +.PP +The daemon runs as root and stores age bracket data in +.I /var/lib/age-verification/ages.conf +(owned by root, mode 0600). Only the age bracket integer (1\(en4) is +stored, never a raw age value or date of birth. +.SH AGE BRACKETS +.TS +l l. +Bracket Meaning +1 Under 13 years old +2 13 to 15 years old (inclusive) +3 16 to 17 years old (inclusive) +4 18 years old or older +.TE +.SH D-BUS INTERFACE +The service is available on the system bus at: +.PP +.RS +Service: \fBorg.freedesktop.AgeVerification1\fR +.br +Object: \fB/org/freedesktop/AgeVerification1\fR +.RE +.SS Methods +.TP +.B SetAge(s user, u years) +Set the age for +.I user +in years. The age bracket is computed and stored; the raw value is discarded. +.TP +.B SetDateOfBirth(s user, s date) +Set the age for +.I user +from an ISO 8601 date string (\fIYYYY-MM-DD\fR). +The age bracket is computed and stored; the date is discarded. +.TP +.B GetAgeBracket(s user) \(-> u bracket +Return the age bracket (1\(en4) for +.IR user . +.SS Errors +.TP +.B org.freedesktop.AgeVerification1.Error.NoSuchUser +The specified user does not exist or is a system account. +.TP +.B org.freedesktop.AgeVerification1.Error.PermissionDenied +The caller is not root and is not querying their own account. +.TP +.B org.freedesktop.AgeVerification1.Error.InvalidDate +The date string passed to SetDateOfBirth is not a valid ISO 8601 date. +.TP +.B org.freedesktop.AgeVerification1.Error.AgeUndefined +No age has been configured for the specified user. +.SH FILES +.TP +.I /var/lib/age-verification/ages.conf +Local age bracket storage. Owned by root:root, mode 0600. +.TP +.I /usr/share/dbus-1/system.d/org.freedesktop.AgeVerification1.conf +D-Bus system policy file. +.TP +.I /lib/systemd/system/age-verification-daemon.service +systemd service unit. +.SH SEE ALSO +.BR age-verification-sync (1), +.BR dbus-send (1), +.BR systemctl (1) +.SH AUTHORS +Seth Olivarez diff --git a/debian/age-verification-services.install b/debian/age-verification-services.install new file mode 100644 index 0000000..4832a52 --- /dev/null +++ b/debian/age-verification-services.install @@ -0,0 +1,6 @@ +usr/lib/age-verification/age-verification-daemon usr/lib/age-verification/ +usr/bin/age-verification-sync usr/bin/ +usr/lib/systemd/system/age-verification-daemon.service usr/lib/systemd/system/ +usr/share/dbus-1/system.d/org.freedesktop.AgeVerification1.conf usr/share/dbus-1/system.d/ +etc/age-verification etc/ +var/lib/age-verification var/lib/ diff --git a/debian/age-verification-services.tmpfiles b/debian/age-verification-services.tmpfiles new file mode 100644 index 0000000..585e5c4 --- /dev/null +++ b/debian/age-verification-services.tmpfiles @@ -0,0 +1,8 @@ +# age-verification-services tmpfiles.d +# Creates and maintains runtime directories with correct permissions. +# See tmpfiles.d(5) for format documentation. + +# Type Path Mode User Group Age Argument +d /var/lib/age-verification 0700 root root - - +d /var/lib/age-verification/device 0700 root root - - +d /etc/age-verification 0755 root root - - diff --git a/debian/age-verification-sync.1 b/debian/age-verification-sync.1 new file mode 100644 index 0000000..b949d5a --- /dev/null +++ b/debian/age-verification-sync.1 @@ -0,0 +1,77 @@ +.TH AGE-VERIFICATION-SYNC 1 "2026-03-10" "age-verification-services 0.1.0" +.SH NAME +age-verification-sync \- sync age bracket to account server +.SH SYNOPSIS +.B age-verification-sync +.B \-\-user +.I USERNAME +.B \-\-server +.I URL +.B \-\-token +.I JWT +.RB [ \-\-server\-pubkey +.IR PATH ] +.RB [ \-\-keystore +.IR PATH ] +.RB [ \-\-keystore\-pass +.IR PASS ] +.SH DESCRIPTION +.B age-verification-sync +reads the age bracket for +.I USERNAME +from the local +.BR age-verification-daemon (8) +via D-Bus, then encrypts and uploads it to the account server at +.IR URL . +.PP +The payload is protected by three layers: +.IP 1. +TLS (transport security, via rustls) +.IP 2. +RSA-4096 OAEP-SHA256 encryption with the server's public key +.IP 3. +Ed25519 signature from a per-device keypair, stored locally encrypted +with AES-256-GCM keyed from PBKDF2(account passphrase) +.SH OPTIONS +.TP +.BI \-u,\ \-\-user\ USERNAME +UNIX username whose bracket to sync. +.TP +.BI \-s,\ \-\-server\ URL +Account server base URL, e.g.\& +.IR https://accounts.example.com . +Can also be set via the +.B AGE_SERVER_URL +environment variable. +.TP +.BI \-t,\ \-\-token\ JWT +Account token (JWT) from the server's login endpoint. Can also be set via the +.B AGE_ACCOUNT_TOKEN +environment variable. +.TP +.BI \-\-server\-pubkey\ PATH +Path to the server's RSA public key in PEM (PKCS#8) format. +Default: +.IR /etc/age-verification/server-pubkey.pem . +.TP +.BI \-\-keystore\ PATH +Directory for the per-device Ed25519 keypair (AES-256-GCM encrypted). +Default: +.IR /var/lib/age-verification/device . +.TP +.BI \-\-keystore\-pass\ PASS +Passphrase protecting the local keystore. If not provided, a prompt is shown. +Can also be set via the +.B AGE_KEYSTORE_PASS +environment variable. +.SH FILES +.TP +.I /etc/age-verification/server-pubkey.pem +Server RSA public key. Distributed by the server operator. +.TP +.I /var/lib/age-verification/device/device.json +Per-device Ed25519 keypair, AES-256-GCM encrypted. +.SH SEE ALSO +.BR age-verification-daemon (8) +.SH AUTHORS +Seth Olivarez diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..6f728fc --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +age-verification-services (0.1.0-1) unstable; urgency=low + + * Initial release. + + -- Seth Olivarez Tue, 10 Mar 2026 00:00:00 +0000 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..3c249e9 --- /dev/null +++ b/debian/control @@ -0,0 +1,43 @@ +Source: age-verification-services +Section: admin +Priority: optional +Maintainer: Seth Olivarez +Build-Depends: + debhelper-compat (= 13), + cmake (>= 3.14), + ninja-build, + pkg-config, + libdbus-1-dev, + libsystemd-dev, + cargo, + rustc (>= 1.70) +Standards-Version: 4.6.2 +Rules-Requires-Root: no +Homepage: https://git.oxmc.me/VesperOS/age-verification-services +Vcs-Browser: https://git.oxmc.me/VesperOS/age-verification-services +Vcs-Git: https://git.oxmc.me/VesperOS/age-verification-services.git + +Package: age-verification-services +Architecture: any +Depends: + ${shlibs:Depends}, + ${misc:Depends}, + dbus, + adduser +Recommends: + systemd +Description: Age verification D-Bus daemon and sync client + Implements org.freedesktop.AgeVerification1 on the system D-Bus, + providing age bracket data to application stores and OS services + in compliance with California SB-976 and similar legislation. + . + This package contains: + - age-verification-daemon: D-Bus system service that stores age + bracket data locally (never raw age or date of birth) + - age-verification-sync: Rust client that encrypts and syncs the + age bracket to an account server using layered cryptography + (RSA-OAEP, Ed25519, AES-256-GCM) + . + Age data is stored as one of four brackets (under 13, 13-15, 16-17, + 18+) and never as a raw age value. The local data file is owned by + root and is not world-readable. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ceb8603 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,27 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: age-verification-services +Upstream-Contact: me@oxmc.me +Source: https://git.oxmc.me/VesperOS/age-verification-services + +Files: * +Copyright: 2026 Seth Olivarez +License: MIT + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..9132a68 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,47 @@ +#!/bin/sh +set -e + +case "$1" in + configure) + # Create storage directory with correct ownership and permissions + install -d -m 0700 -o root -g root /var/lib/age-verification + install -d -m 0700 -o root -g root /var/lib/age-verification/device + + # Ensure config dir exists + install -d -m 0755 /etc/age-verification + + # Secure the ages.conf if it already exists from a previous install + if [ -f /var/lib/age-verification/ages.conf ]; then + chown root:root /var/lib/age-verification/ages.conf + chmod 0600 /var/lib/age-verification/ages.conf + fi + + # Reload D-Bus so it picks up the new policy file + if [ -d /run/dbus ] && [ -S /run/dbus/system_bus_socket ]; then + dbus-send --system \ + --type=method_call \ + --dest=org.freedesktop.DBus \ + /org/freedesktop/DBus \ + org.freedesktop.DBus.ReloadConfig 2>/dev/null || true + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument '$1'" >&2 + exit 1 + ;; +esac + +# Enable and start the systemd service if systemd is running +if [ -d /run/systemd/system ]; then + systemctl daemon-reload >/dev/null 2>&1 || true + systemctl enable age-verification-daemon.service >/dev/null 2>&1 || true + systemctl start age-verification-daemon.service >/dev/null 2>&1 || true +fi + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..0f011f4 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + # Remove data directory only on purge, not plain remove + rm -rf /var/lib/age-verification + rm -rf /etc/age-verification + if [ -d /run/systemd/system ]; then + systemctl daemon-reload >/dev/null 2>&1 || true + fi + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument '$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 0000000..9e9121e --- /dev/null +++ b/debian/prerm @@ -0,0 +1,23 @@ +#!/bin/sh +set -e + +case "$1" in + remove|upgrade|deconfigure) + if [ -d /run/systemd/system ]; then + systemctl stop age-verification-daemon.service >/dev/null 2>&1 || true + systemctl disable age-verification-daemon.service >/dev/null 2>&1 || true + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument '$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..e2a20cd --- /dev/null +++ b/debian/rules @@ -0,0 +1,48 @@ +#!/usr/bin/make -f +# Debian rules for age-verification-services +# Builds: +# 1. age-verification-daemon (C, CMake) +# 2. age-verification-sync (Rust, Cargo) + +export DH_VERBOSE = 1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +%: + dh $@ + +# --------------------------------------------------------------------------- +# C daemon (CMake) +# --------------------------------------------------------------------------- +override_dh_auto_configure: + cmake \ + -S daemon \ + -B debian/build-daemon \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_VERBOSE_MAKEFILE=ON + +override_dh_auto_build: + cmake --build debian/build-daemon + # Build Rust sync client + cd sync-client && cargo build --release + +override_dh_auto_install: + DESTDIR=$(CURDIR)/debian/tmp \ + cmake --install debian/build-daemon + # Install Rust binary + install -Dm755 sync-client/target/release/age-verification-sync \ + $(CURDIR)/debian/tmp/usr/bin/age-verification-sync + # Install server public key placeholder dir + install -dm755 \ + $(CURDIR)/debian/tmp/etc/age-verification + # Install device keystore dir (empty, created at runtime) + install -dm700 \ + $(CURDIR)/debian/tmp/var/lib/age-verification/device + +override_dh_auto_clean: + rm -rf debian/build-daemon + cd sync-client && cargo clean || true + +override_dh_auto_test: + # No automated tests yet — skip diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..ca75f22 --- /dev/null +++ b/debian/watch @@ -0,0 +1,4 @@ +version=4 +opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/age-verification-services-$1\.tar\.gz/ \ + https://git.oxmc.me/VesperOS/age-verification-services/tags \ + .*/v?(\d\S+)\.tar\.gz diff --git a/sync-client/Cargo.lock b/sync-client/Cargo.lock new file mode 100644 index 0000000..20f0d33 --- /dev/null +++ b/sync-client/Cargo.lock @@ -0,0 +1,2613 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "age-verification-sync" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "clap", + "dirs", + "hex", + "pbkdf2", + "rand", + "reqwest", + "ring", + "rpassword", + "rsa", + "serde", + "serde_json", + "sha2", + "tokio", + "zbus", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rpassword" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.59.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rtoolbox" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/sync-client/Cargo.toml b/sync-client/Cargo.toml new file mode 100644 index 0000000..da0bf91 --- /dev/null +++ b/sync-client/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "age-verification-sync" +version = "0.1.0" +edition = "2021" +description = "Sync client for age-verification daemon — sends encrypted age bracket to account server" + +[[bin]] +name = "age-verification-sync" +path = "src/main.rs" + +[dependencies] +# D-Bus to query the local daemon +zbus = { version = "4", default-features = false, features = ["tokio"] } + +# Crypto +ring = "0.17" # HKDF, AEAD (AES-256-GCM) +rsa = { version = "0.9", features = ["sha2"] } +rand = "0.8" +sha2 = "0.10" +pbkdf2 = { version = "0.12", features = ["simple"] } +hex = "0.4" +base64 = "0.21" + +# HTTP +reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } + +# Async runtime +tokio = { version = "1", features = ["full"] } + +# Config / storage +serde = { version = "1", features = ["derive"] } +serde_json = "1" +dirs = "5" + +# Error handling +anyhow = "1" + +# Password prompt +rpassword = "7" + +# CLI +clap = { version = "4", features = ["derive", "env"] } + +[profile.release] +strip = true +opt-level = "z" +lto = true diff --git a/sync-client/src/main.rs b/sync-client/src/main.rs new file mode 100644 index 0000000..340b5f5 --- /dev/null +++ b/sync-client/src/main.rs @@ -0,0 +1,327 @@ +/// age-verification-sync +/// +/// Reads the age bracket from the local D-Bus daemon, then sends it to the +/// account server using a layered encryption scheme: +/// +/// 1. TLS (handled by reqwest/rustls) — transport security +/// 2. Server RSA-OAEP public key encryption — only the server can decrypt +/// 3. Per-device Ed25519 keypair — authenticate this device to the server +/// 4. Local AES-256-GCM encryption keyed from account credentials (PBKDF2) +/// — protects the device keystore at rest +/// +/// The payload sent to the server is: +/// { "device_id": "", "bracket": , "sig": "" } +/// +/// Where `bracket` is RSA-OAEP encrypted with the server's public key, +/// and `sig` is an Ed25519 signature over (device_id || bracket_ciphertext). + +use base64::{engine::general_purpose::STANDARD as B64, Engine}; +use clap::Parser; +use rand::rngs::OsRng; +use ring::{ + aead::{self, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, AES_256_GCM}, + error::Unspecified, + rand::{SecureRandom, SystemRandom}, +}; +use rsa::{ + pkcs8::DecodePublicKey, Oaep, RsaPublicKey, +}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use std::{ + fs, + path::PathBuf, +}; +use zbus::Connection; + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +#[derive(Parser, Debug)] +#[command(name = "age-verification-sync", about = "Sync age bracket to account server")] +struct Args { + /// UNIX username to sync + #[arg(short, long)] + user: String, + + /// Account server base URL (e.g. https://accounts.example.com) + #[arg(short, long, env = "AGE_SERVER_URL")] + server: String, + + /// Account token (JWT or opaque token from login) + #[arg(short, long, env = "AGE_ACCOUNT_TOKEN")] + token: String, + + /// Path to the server's RSA public key (PEM, PKCS#8) + #[arg(long, default_value = "/etc/age-verification/server-pubkey.pem")] + server_pubkey: PathBuf, + + /// Directory to store the per-device keypair (AES-encrypted) + #[arg(long, default_value = "/var/lib/age-verification/device")] + keystore: PathBuf, + + /// Passphrase to protect local keystore (will prompt if not set) + #[arg(long, env = "AGE_KEYSTORE_PASS", hide_env_values = true)] + keystore_pass: Option, +} + +// --------------------------------------------------------------------------- +// Nonce sequence (counter-based, single use per key) +// --------------------------------------------------------------------------- + +struct CounterNonce(u64); + +impl NonceSequence for CounterNonce { + fn advance(&mut self) -> Result { + let mut n = [0u8; 12]; + n[..8].copy_from_slice(&self.0.to_le_bytes()); + self.0 += 1; + Nonce::try_assume_unique_for_key(&n) + } +} + +// --------------------------------------------------------------------------- +// Keystore (Ed25519 device keypair, AES-256-GCM encrypted at rest) +// --------------------------------------------------------------------------- + +#[derive(Serialize, Deserialize)] +struct KeystoreFile { + /// PBKDF2 salt (hex) + salt: String, + /// AES-256-GCM nonce (hex) + nonce: String, + /// AES-256-GCM ciphertext of the raw 32-byte Ed25519 seed (hex) + ciphertext: String, + /// Device ID derived from the public key (hex, first 16 bytes) + device_id: String, +} + +fn derive_key(passphrase: &str, salt: &[u8]) -> [u8; 32] { + use pbkdf2::pbkdf2_hmac; + use sha2::Sha256; + let mut key = [0u8; 32]; + pbkdf2_hmac::(passphrase.as_bytes(), salt, 310_000, &mut key); + key +} + +fn load_or_create_device_key(keystore_dir: &PathBuf, passphrase: &str) + -> anyhow::Result<([u8; 32], String)> // (seed, device_id) +{ + use ring::signature::{Ed25519KeyPair, KeyPair}; + + fs::create_dir_all(keystore_dir)?; + let keystore_path = keystore_dir.join("device.json"); + + if keystore_path.exists() { + // Load & decrypt + let raw = fs::read_to_string(&keystore_path)?; + let ks: KeystoreFile = serde_json::from_str(&raw)?; + + let salt = hex::decode(&ks.salt)?; + let nonce_bytes = hex::decode(&ks.nonce)?; + let mut ct = hex::decode(&ks.ciphertext)?; + + let aes_key = derive_key(passphrase, &salt); + let unbound = UnboundKey::new(&AES_256_GCM, &aes_key).map_err(|_| anyhow::anyhow!("key error"))?; + let mut opening = OpeningKey::new(unbound, CounterNonce(0)); + + // Reconstruct nonce in the tag — we stored the nonce separately + // so we need to set it; re-derive via the stored nonce bytes directly. + // (For simplicity we use the stored nonce as a single-shot open) + let _ = nonce_bytes; // nonce is embedded in ciphertext prefix + opening.open_in_place(aead::Aad::empty(), &mut ct) + .map_err(|_| anyhow::anyhow!("Decryption failed — wrong passphrase?"))?; + + let mut seed = [0u8; 32]; + seed.copy_from_slice(&ct[..32]); + Ok((seed, ks.device_id)) + } else { + // Generate new keypair + let rng = SystemRandom::new(); + let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng) + .map_err(|_| anyhow::anyhow!("keygen failed"))?; + let pair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()) + .map_err(|_| anyhow::anyhow!("keypair parse failed"))?; + + // Derive device ID from public key + let pub_bytes = pair.public_key().as_ref(); + let device_id = hex::encode(&pub_bytes[..16]); + + // Extract seed (first 32 bytes of pkcs8 after header — Ed25519 pkcs8 v1) + // ring's pkcs8 is: 16-byte header + 32-byte seed + 32-byte public key + let seed_bytes = &pkcs8.as_ref()[16..48]; + let mut seed = [0u8; 32]; + seed.copy_from_slice(seed_bytes); + + // Encrypt seed + let mut salt = [0u8; 16]; + let mut nonce_arr = [0u8; 12]; + rng.fill(&mut salt).map_err(|_| anyhow::anyhow!("rng fail"))?; + rng.fill(&mut nonce_arr).map_err(|_| anyhow::anyhow!("rng fail"))?; + + let aes_key = derive_key(passphrase, &salt); + let unbound = UnboundKey::new(&AES_256_GCM, &aes_key).map_err(|_| anyhow::anyhow!("key error"))?; + let mut sealing = SealingKey::new(unbound, CounterNonce(0)); + + let mut plaintext = seed.to_vec(); + sealing.seal_in_place_append_tag(aead::Aad::empty(), &mut plaintext) + .map_err(|_| anyhow::anyhow!("encrypt fail"))?; + + let ks = KeystoreFile { + salt: hex::encode(salt), + nonce: hex::encode(nonce_arr), + ciphertext: hex::encode(&plaintext), + device_id: device_id.clone(), + }; + let json = serde_json::to_string_pretty(&ks)?; + fs::write(&keystore_path, json)?; + // Restrict permissions + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&keystore_path, fs::Permissions::from_mode(0o600))?; + } + + Ok((seed, device_id)) + } +} + +// --------------------------------------------------------------------------- +// Query local daemon via D-Bus +// --------------------------------------------------------------------------- + +async fn get_age_bracket_local(username: &str) -> anyhow::Result { + let conn = Connection::system().await?; + let reply: (u32,) = conn + .call_method( + Some("org.freedesktop.AgeVerification1"), + "/org/freedesktop/AgeVerification1", + Some("org.freedesktop.AgeVerification1"), + "GetAgeBracket", + &(username,), + ) + .await? + .body() + .deserialize()?; + Ok(reply.0) +} + +// --------------------------------------------------------------------------- +// Encrypt bracket with server RSA public key (OAEP-SHA256) +// --------------------------------------------------------------------------- + +fn encrypt_for_server(server_pubkey_pem: &str, bracket: u8) -> anyhow::Result> { + let pubkey = RsaPublicKey::from_public_key_pem(server_pubkey_pem)?; + let mut rng = OsRng; + let ciphertext = pubkey.encrypt(&mut rng, Oaep::new::(), &[bracket])?; + Ok(ciphertext) +} + +// --------------------------------------------------------------------------- +// Sign payload with Ed25519 device key +// --------------------------------------------------------------------------- + +fn sign_payload(seed: &[u8; 32], device_id: &str, bracket_ct: &[u8]) -> anyhow::Result { + use ring::signature::Ed25519KeyPair; + + // Reconstruct keypair from seed via pkcs8 + // ring requires pkcs8 — build it: ASN.1 header + seed + // https://www.rfc-editor.org/rfc/rfc8410 + let mut pkcs8 = vec![ + 0x30, 0x2e, // SEQUENCE (46 bytes) + 0x02, 0x01, 0x00, // INTEGER 0 (version) + 0x30, 0x05, // SEQUENCE + 0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112 (Ed25519) + 0x04, 0x22, // OCTET STRING (34 bytes) + 0x04, 0x20, // OCTET STRING (32 bytes) — the seed + ]; + pkcs8.extend_from_slice(seed); + + let pair = Ed25519KeyPair::from_pkcs8(&pkcs8) + .map_err(|_| anyhow::anyhow!("keypair reconstruct failed"))?; + + let mut msg = device_id.as_bytes().to_vec(); + msg.extend_from_slice(bracket_ct); + let sig = pair.sign(&msg); + Ok(B64.encode(sig.as_ref())) +} + +// --------------------------------------------------------------------------- +// Server payload & sync +// --------------------------------------------------------------------------- + +#[derive(Serialize)] +struct SyncPayload { + device_id: String, + /// RSA-OAEP ciphertext of the bracket byte, base64 encoded + bracket_enc: String, + /// Ed25519 signature over (device_id_bytes || bracket_enc_bytes), base64 + sig: String, +} + +async fn sync_to_server( + server_url: &str, + token: &str, + payload: &SyncPayload, +) -> anyhow::Result<()> { + let client = reqwest::Client::builder() + .https_only(true) + .build()?; + + let resp = client + .post(format!("{}/api/v1/age-bracket", server_url)) + .bearer_auth(token) + .json(payload) + .send() + .await?; + + if !resp.status().is_success() { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + anyhow::bail!("Server returned {}: {}", status, body); + } + + println!("Sync successful."); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + // Get passphrase + let passphrase = match args.keystore_pass { + Some(p) => p, + None => rpassword::prompt_password("Keystore passphrase: ")?, + }; + + // Load/create device keypair + let (seed, device_id) = load_or_create_device_key(&args.keystore, &passphrase)?; + println!("Device ID: {}", device_id.as_str()); + + // Query local daemon + let bracket = get_age_bracket_local(&args.user).await?; + println!("Age bracket for '{}': {}", args.user, bracket); + + // Load server public key + let server_pem = fs::read_to_string(&args.server_pubkey) + .map_err(|e| anyhow::anyhow!("Cannot read server pubkey at {:?}: {}", args.server_pubkey, e))?; + + // Encrypt bracket for server + let bracket_ct = encrypt_for_server(&server_pem, bracket as u8)?; + let bracket_enc = B64.encode(&bracket_ct); + + // Sign + let sig = sign_payload(&seed, &device_id, &bracket_ct)?; + + // Sync + let payload = SyncPayload { device_id: device_id.to_string(), bracket_enc, sig }; + sync_to_server(&args.server, &args.token, &payload).await?; + + Ok(()) +}