diff --git a/install_qube_fips_nsigner.sh b/install_qube_fips_nsigner.sh index 7c848de..10301f1 100755 --- a/install_qube_fips_nsigner.sh +++ b/install_qube_fips_nsigner.sh @@ -6,34 +6,23 @@ set -euo pipefail # # Installs into $HOME: # - nsigner -> ~/.local/bin/nsigner -# - fips/fipsctl/fipstop -> ~/.local/bin/ -# - fips config -> ~/.config/fips/fips.yaml -# - user service -> ~/.config/systemd/user/fips.service +# - startup helper -> ~/start_nsigner.sh # # Usage: # bash install_qube_fips_nsigner.sh # bash install_qube_fips_nsigner.sh --help # # Optional env vars: -# NSIGNER_VERSION=v0.0.6 -# FIPS_VERSION=v0.2.0-rel +# NSIGNER_VERSION=v0.0.7 # NSIGNER_GITEA_TOKEN= # if n_signer release assets are private # NSIGNER_BINARY_URL= -NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.6}" -FIPS_VERSION="${FIPS_VERSION:-v0.2.0-rel}" +NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.7}" NSIGNER_RELEASE_PAGE="https://git.laantungir.net/laantungir/n_signer/releases/tag/${NSIGNER_VERSION}" NSIGNER_API_TAG_URL="https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases/tags/${NSIGNER_VERSION}" -NSIGNER_GIT_URL="https://git.laantungir.net/laantungir/n_signer.git" - -FIPS_RELEASE_PAGE="https://github.com/jmcorgan/fips/releases/tag/${FIPS_VERSION}" -FIPS_ARCHIVE_URL="https://github.com/jmcorgan/fips/archive/refs/tags/${FIPS_VERSION}.tar.gz" PREFIX_BIN="${HOME}/.local/bin" -PREFIX_CFG="${HOME}/.config" -PREFIX_DATA="${HOME}/.local/share/nsigner-fips-install" -SYSTEMD_USER_DIR="${HOME}/.config/systemd/user" log() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } @@ -45,25 +34,19 @@ Usage: bash install_qube_fips_nsigner.sh [options] User-only install (Qubes AppVM friendly): - n_signer ${NSIGNER_VERSION} - - FIPS ${FIPS_VERSION} - - user-level systemd service for FIPS + - signer startup helper script Options: -h, --help Show this help and exit Optional env vars: - NSIGNER_VERSION=v0.0.6 - FIPS_VERSION=v0.2.0-rel + NSIGNER_VERSION=v0.0.7 NSIGNER_GITEA_TOKEN= # required if n_signer release assets are private NSIGNER_BINARY_URL= Install paths: ~/.local/bin/nsigner - ~/.local/bin/fips - ~/.local/bin/fipsctl - ~/.local/bin/fipstop - ~/.config/fips/fips.yaml - ~/.config/systemd/user/fips.service + ~/start_nsigner.sh EOF } @@ -74,28 +57,22 @@ require_cmd() { } } -install_build_deps() { +install_runtime_deps() { if command -v apt-get >/dev/null 2>&1; then - log "Installing build/runtime dependencies via apt" + log "Installing runtime dependencies via apt" sudo apt-get update - sudo apt-get install -y \ - ca-certificates curl jq tar gzip git python3 \ - build-essential pkg-config libssl-dev zlib1g-dev \ - rustc cargo + sudo apt-get install -y ca-certificates curl jq elif command -v dnf >/dev/null 2>&1; then - log "Installing build/runtime dependencies via dnf" - sudo dnf install -y \ - ca-certificates curl jq tar gzip git python3 \ - gcc make pkgconf-pkg-config openssl-devel zlib-devel \ - rust cargo + log "Installing runtime dependencies via dnf" + sudo dnf install -y ca-certificates curl jq else - err "Unsupported distro: need apt-get or dnf to install build dependencies" + err "Unsupported distro: need apt-get or dnf to install runtime dependencies" exit 1 fi } prepare_dirs() { - mkdir -p "${PREFIX_BIN}" "${PREFIX_CFG}/fips" "${PREFIX_DATA}" "${SYSTEMD_USER_DIR}" + mkdir -p "${PREFIX_BIN}" } download_nsigner_asset_url() { @@ -135,103 +112,47 @@ install_nsigner() { log "Installed ${PREFIX_BIN}/nsigner from release binary" } -install_fips() { - log "Installing FIPS ${FIPS_VERSION}" - log "Release page: ${FIPS_RELEASE_PAGE}" +write_signer_start_script() { + local script_path="${HOME}/start_nsigner.sh" - rm -rf "${PREFIX_DATA}/fips-src" - mkdir -p "${PREFIX_DATA}/fips-src" + cat >"${script_path}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail - curl -fL -o "${PREFIX_DATA}/fips.tar.gz" "${FIPS_ARCHIVE_URL}" - tar -xzf "${PREFIX_DATA}/fips.tar.gz" -C "${PREFIX_DATA}/fips-src" --strip-components=1 +export PATH="$HOME/.local/bin:$PATH" +LISTEN_TARGET="${NSIGNER_LISTEN_TARGET:-tcp:[::]:8080}" - ( - cd "${PREFIX_DATA}/fips-src" - cargo build --release +echo "=== n_signer startup ===" +echo "listen target: ${LISTEN_TARGET}" - install -m 0755 ./target/release/fips "${PREFIX_BIN}/fips" - install -m 0755 ./target/release/fipsctl "${PREFIX_BIN}/fipsctl" - - if [[ -x ./target/release/fipstop ]]; then - install -m 0755 ./target/release/fipstop "${PREFIX_BIN}/fipstop" - fi - ) - - log "Installed FIPS binaries into ${PREFIX_BIN}" -} - -write_fips_user_config() { - local cfg="${PREFIX_CFG}/fips/fips.yaml" - if [[ -f "${cfg}" ]]; then - warn "Keeping existing ${cfg}" - return - fi - - cat >"${cfg}" <<'EOF' -# User-local FIPS config for AppVM persistence -# Edit peers to join your mesh. - -node: - identity: - persistent: true - -# Try userspace tun name; adjust if your environment requires a different setting. -tun: - enabled: true - name: fips0 - mtu: 1280 - -dns: - enabled: true - bind_addr: "127.0.0.1" - port: 5354 - -transports: - udp: - bind_addr: "0.0.0.0:2121" - tcp: - bind_addr: "0.0.0.0:8443" - -peers: [] -EOF - - log "Wrote ${cfg}" -} - -write_fips_user_service() { - local svc="${SYSTEMD_USER_DIR}/fips.service" - cat >"${svc}" </dev/null 2>&1; then + if fipsctl show status >/dev/null 2>&1; then + STATUS_JSON="$(fipsctl show status)" + elif sudo -n fipsctl show status >/dev/null 2>&1; then + STATUS_JSON="$(sudo -n fipsctl show status)" else - warn "fips user service is not active" - warn "Check logs with: journalctl --user -u fips -n 200 --no-pager" + STATUS_JSON="" fi + + if [[ -n "${STATUS_JSON}" ]]; then + FIPS_IPV6="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"ipv6_addr": "\([^"]*\)".*/\1/p')" + FIPS_NPUB="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"npub": "\([^"]*\)".*/\1/p')" + [[ -n "${FIPS_IPV6}" ]] && echo "fips ipv6: ${FIPS_IPV6}" + [[ -n "${FIPS_NPUB}" ]] && echo "fips npub: ${FIPS_NPUB}" + else + echo "fips status: unavailable (run as user in fips group or with sudo)" + fi +fi + +echo +echo "Starting signer..." +echo "On first remote request, approve in prompt with [y] or [a]." +exec "$HOME/.local/bin/nsigner" --listen "${LISTEN_TARGET}" +EOF + + chmod 0755 "${script_path}" + log "Wrote ${script_path}" } post_checks() { @@ -239,15 +160,10 @@ post_checks() { log "Running post-install checks" require_cmd nsigner - require_cmd fips - require_cmd fipsctl - nsigner --version || true - fips --version || true log "User binaries installed in: ${PREFIX_BIN}" log "If needed, add to shell PATH: export PATH=\"${PREFIX_BIN}:\$PATH\"" - log "Edit peers in: ${PREFIX_CFG}/fips/fips.yaml" } main() { @@ -256,16 +172,20 @@ main() { exit 0 fi - install_build_deps + if [[ $# -gt 0 ]]; then + err "Unknown option: $1" + show_help + exit 1 + fi + + install_runtime_deps prepare_dirs install_nsigner - install_fips - write_fips_user_config - write_fips_user_service - start_fips_user_service + write_signer_start_script post_checks - log "Completed user-only install of n_signer + FIPS" + log "Completed user-only install of n_signer" + log "Start signer with: ~/start_nsigner.sh" } main "$@" diff --git a/src/main.c b/src/main.c index 654716e..5c89276 100644 --- a/src/main.c +++ b/src/main.c @@ -451,8 +451,8 @@ int socket_name_random(char *out, size_t out_len); /* Version information (auto-updated by build/version tooling) */ #define NSIGNER_VERSION_MAJOR 0 #define NSIGNER_VERSION_MINOR 0 -#define NSIGNER_VERSION_PATCH 7 -#define NSIGNER_VERSION "v0.0.7" +#define NSIGNER_VERSION_PATCH 8 +#define NSIGNER_VERSION "v0.0.8" /* NSIGNER_HEADERLESS_DECLS_END */ diff --git a/src/server.c b/src/server.c index e428358..dc0c48a 100644 --- a/src/server.c +++ b/src/server.c @@ -469,6 +469,157 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size); static int g_prompt_always_allow = 0; static int g_noninteractive_prompt_default = -1; +static int caller_id_extract_ipv6(const char *caller_id, char *out_ipv6, size_t out_sz) { + const char *start; + const char *end; + size_t len; + + if (caller_id == NULL || out_ipv6 == NULL || out_sz == 0) { + return -1; + } + + if (strncmp(caller_id, "tcp:[", 5) != 0) { + return -1; + } + + start = caller_id + 5; + end = strchr(start, ']'); + if (end == NULL || end[1] != ':') { + return -1; + } + + len = (size_t)(end - start); + if (len == 0 || len >= out_sz) { + return -1; + } + + memcpy(out_ipv6, start, len); + out_ipv6[len] = '\0'; + return 0; +} + +static int read_cmd_output(const char *cmd, char **out_buf) { + FILE *fp; + char chunk[512]; + char *buf = NULL; + size_t used = 0; + size_t cap = 0; + + if (cmd == NULL || out_buf == NULL) { + return -1; + } + + *out_buf = NULL; + fp = popen(cmd, "r"); + if (fp == NULL) { + return -1; + } + + while (fgets(chunk, sizeof(chunk), fp) != NULL) { + size_t n = strlen(chunk); + if (used + n + 1 > cap) { + size_t new_cap = (cap == 0) ? 2048 : cap * 2; + while (new_cap < used + n + 1) { + new_cap *= 2; + } + { + char *tmp = (char *)realloc(buf, new_cap); + if (tmp == NULL) { + free(buf); + (void)pclose(fp); + return -1; + } + buf = tmp; + cap = new_cap; + } + } + memcpy(buf + used, chunk, n); + used += n; + } + + (void)pclose(fp); + + if (buf == NULL) { + return -1; + } + + buf[used] = '\0'; + *out_buf = buf; + return 0; +} + +static int lookup_fips_peer_for_ipv6(const char *ipv6, + char *out_npub, + size_t out_npub_sz, + char *out_name, + size_t out_name_sz) { + char *json = NULL; + cJSON *root = NULL; + cJSON *peers = NULL; + int i; + + if (ipv6 == NULL || out_npub == NULL || out_npub_sz == 0 || out_name == NULL || out_name_sz == 0) { + return -1; + } + + out_npub[0] = '\0'; + out_name[0] = '\0'; + + if (read_cmd_output("fipsctl show peers 2>/dev/null", &json) != 0) { + return -1; + } + + root = cJSON_Parse(json); + free(json); + if (root == NULL) { + return -1; + } + + peers = cJSON_GetObjectItemCaseSensitive(root, "peers"); + if (!cJSON_IsArray(peers)) { + cJSON_Delete(root); + return -1; + } + + for (i = 0; i < cJSON_GetArraySize(peers); ++i) { + cJSON *peer = cJSON_GetArrayItem(peers, i); + cJSON *peer_ip; + cJSON *peer_npub; + cJSON *peer_name; + + if (!cJSON_IsObject(peer)) { + continue; + } + + peer_ip = cJSON_GetObjectItemCaseSensitive(peer, "ipv6_addr"); + if (!cJSON_IsString(peer_ip) || peer_ip->valuestring == NULL) { + continue; + } + + if (strcmp(peer_ip->valuestring, ipv6) != 0) { + continue; + } + + peer_npub = cJSON_GetObjectItemCaseSensitive(peer, "npub"); + peer_name = cJSON_GetObjectItemCaseSensitive(peer, "display_name"); + + if (cJSON_IsString(peer_npub) && peer_npub->valuestring != NULL) { + strncpy(out_npub, peer_npub->valuestring, out_npub_sz - 1); + out_npub[out_npub_sz - 1] = '\0'; + } + if (cJSON_IsString(peer_name) && peer_name->valuestring != NULL) { + strncpy(out_name, peer_name->valuestring, out_name_sz - 1); + out_name[out_name_sz - 1] = '\0'; + } + + cJSON_Delete(root); + return (out_npub[0] != '\0') ? 0 : -1; + } + + cJSON_Delete(root); + return -1; +} + void server_set_prompt_always_allow(int enabled) { g_prompt_always_allow = enabled ? 1 : 0; } @@ -501,6 +652,20 @@ static int prompt_for_policy_decision(const caller_identity_t *caller, printf("\nApproval required\n"); printf("caller: %s\n", (caller != NULL) ? caller->caller_id : "unknown"); + if (caller != NULL && caller->kind == NSIGNER_LISTEN_TCP) { + char ipv6[INET6_ADDRSTRLEN]; + char npub[128]; + char display_name[128]; + + if (caller_id_extract_ipv6(caller->caller_id, ipv6, sizeof(ipv6)) == 0 && + lookup_fips_peer_for_ipv6(ipv6, npub, sizeof(npub), display_name, sizeof(display_name)) == 0) { + if (display_name[0] != '\0') { + printf("fips peer: %s (%s)\n", npub, display_name); + } else { + printf("fips peer: %s\n", npub); + } + } + } printf("method: %s\n", (method != NULL) ? method : "unknown"); printf("role: %s\n", (role_name != NULL) ? role_name : "unknown"); printf("purpose: %s\n", (purpose != NULL) ? purpose : "unknown");