Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1317de5cbe | ||
|
|
fac0ce6503 | ||
|
|
ae95ae6859 | ||
|
|
3efb0edb61 | ||
|
|
c5fdb1a207 | ||
|
|
28f50a750f | ||
|
|
1dd6630311 | ||
|
|
252d026991 |
@@ -46,7 +46,10 @@ RUN cd /tmp && \
|
||||
|
||||
# Copy and build nostr_core_lib from project resources
|
||||
COPY resources/nostr_core_lib /build/nostr_core_lib/
|
||||
RUN cd /build/nostr_core_lib && \
|
||||
RUN if [ "$(uname -m)" = "aarch64" ] && ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
|
||||
ln -s "$(command -v gcc)" /usr/local/bin/aarch64-linux-gnu-gcc; \
|
||||
fi && \
|
||||
cd /build/nostr_core_lib && \
|
||||
chmod +x ./build.sh && \
|
||||
./build.sh --nips=1,4,6,19,44
|
||||
|
||||
@@ -54,26 +57,33 @@ RUN cd /build/nostr_core_lib && \
|
||||
COPY src/ /build/src/
|
||||
|
||||
# Build nsigner as a fully static binary
|
||||
RUN gcc -static -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -s -Wall -Wextra -std=c99 \
|
||||
-I/build/nostr_core_lib \
|
||||
-I/build/nostr_core_lib/nostr_core \
|
||||
-I/build/nostr_core_lib/cjson \
|
||||
/build/src/main.c \
|
||||
/build/src/secure_mem.c \
|
||||
/build/src/mnemonic.c \
|
||||
/build/src/role_table.c \
|
||||
/build/src/selector.c \
|
||||
/build/src/enforcement.c \
|
||||
/build/src/dispatcher.c \
|
||||
/build/src/policy.c \
|
||||
/build/src/server.c \
|
||||
/build/src/transport_frame.c \
|
||||
/build/src/key_store.c \
|
||||
/build/src/socket_name.c \
|
||||
/build/nostr_core_lib/libnostr_core_x64.a \
|
||||
-o /build/nsigner_static \
|
||||
$(pkg-config --static --libs libcurl openssl) \
|
||||
-lsecp256k1 -lsqlite3 -lz -lpthread -lm
|
||||
RUN ARCH="$(uname -m)"; \
|
||||
case "$ARCH" in \
|
||||
aarch64|arm64) NOSTR_LIB="/build/nostr_core_lib/libnostr_core_arm64.a" ;; \
|
||||
x86_64|amd64) NOSTR_LIB="/build/nostr_core_lib/libnostr_core_x64.a" ;; \
|
||||
*) echo "Unsupported build arch: $ARCH"; exit 1 ;; \
|
||||
esac; \
|
||||
[ -f "$NOSTR_LIB" ] || { echo "Missing nostr core lib: $NOSTR_LIB"; ls -la /build/nostr_core_lib; exit 1; }; \
|
||||
gcc -static -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -s -Wall -Wextra -std=c99 \
|
||||
-I/build/nostr_core_lib \
|
||||
-I/build/nostr_core_lib/nostr_core \
|
||||
-I/build/nostr_core_lib/cjson \
|
||||
/build/src/main.c \
|
||||
/build/src/secure_mem.c \
|
||||
/build/src/mnemonic.c \
|
||||
/build/src/role_table.c \
|
||||
/build/src/selector.c \
|
||||
/build/src/enforcement.c \
|
||||
/build/src/dispatcher.c \
|
||||
/build/src/policy.c \
|
||||
/build/src/server.c \
|
||||
/build/src/transport_frame.c \
|
||||
/build/src/key_store.c \
|
||||
/build/src/socket_name.c \
|
||||
"$NOSTR_LIB" \
|
||||
-o /build/nsigner_static \
|
||||
$(pkg-config --static --libs libcurl openssl) \
|
||||
-lsecp256k1 -lsqlite3 -lz -lpthread -lm
|
||||
|
||||
RUN strip /build/nsigner_static || true
|
||||
RUN file /build/nsigner_static && \
|
||||
|
||||
@@ -105,12 +105,29 @@ echo "Platform: $PLATFORM"
|
||||
echo "Output: $BUILD_DIR/$OUTPUT_NAME"
|
||||
echo ""
|
||||
|
||||
if [ "$ARCH" != "$HOST_ARCH" ]; then
|
||||
echo "[0/3] Preparing buildx + QEMU for cross-architecture build"
|
||||
if ! docker buildx inspect >/dev/null 2>&1; then
|
||||
echo "ERROR: docker buildx is not available"
|
||||
exit 1
|
||||
fi
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all >/dev/null
|
||||
|
||||
if ! docker buildx inspect nsigner-builder >/dev/null 2>&1; then
|
||||
docker buildx create --name nsigner-builder --driver docker-container --use >/dev/null
|
||||
else
|
||||
docker buildx use nsigner-builder >/dev/null
|
||||
fi
|
||||
docker buildx inspect --bootstrap >/dev/null
|
||||
fi
|
||||
|
||||
echo "[1/3] Building builder stage from project root context"
|
||||
docker build \
|
||||
docker buildx build \
|
||||
--platform "$PLATFORM" \
|
||||
--target builder \
|
||||
-f "$DOCKERFILE" \
|
||||
-t "$IMAGE_TAG" \
|
||||
--load \
|
||||
"$SCRIPT_DIR"
|
||||
|
||||
echo "[2/3] Extracting static binary"
|
||||
|
||||
@@ -244,13 +244,7 @@ main() {
|
||||
check_git_repo
|
||||
|
||||
if [[ "$RELEASE_MODE" == true ]]; then
|
||||
if [[ "$VERSION_INCREMENT_EXPLICIT" == true ]]; then
|
||||
increment_version "$VERSION_INCREMENT_TYPE"
|
||||
else
|
||||
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "v0.0.1")
|
||||
NEW_VERSION="$LATEST_TAG"
|
||||
export NEW_VERSION
|
||||
fi
|
||||
increment_version "$VERSION_INCREMENT_TYPE"
|
||||
|
||||
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
|
||||
print_success "Created tag: $NEW_VERSION"
|
||||
|
||||
@@ -6,34 +6,20 @@ 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_GITEA_TOKEN=<token> # if n_signer release assets are private
|
||||
# NSIGNER_VERSION=vX.Y.Z # optional override; default is latest release tag
|
||||
# NSIGNER_GITEA_TOKEN=<token> # if n_signer release assets are private
|
||||
# NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
|
||||
|
||||
NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.6}"
|
||||
FIPS_VERSION="${FIPS_VERSION:-v0.2.0-rel}"
|
||||
|
||||
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"
|
||||
NSIGNER_VERSION="${NSIGNER_VERSION:-}"
|
||||
|
||||
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 +31,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_GITEA_TOKEN=<token> # required if n_signer release assets are private
|
||||
NSIGNER_VERSION=vX.Y.Z # optional override; default is latest release tag
|
||||
NSIGNER_GITEA_TOKEN=<token> # required if n_signer release assets are private
|
||||
NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
|
||||
|
||||
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,45 +54,70 @@ 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() {
|
||||
resolve_nsigner_version() {
|
||||
local headers=()
|
||||
local latest_tag=""
|
||||
|
||||
if [[ -n "${NSIGNER_VERSION}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then
|
||||
headers=(-H "Authorization: token ${NSIGNER_GITEA_TOKEN}")
|
||||
fi
|
||||
|
||||
curl -fsSL "${headers[@]}" "${NSIGNER_API_TAG_URL}" \
|
||||
latest_tag="$(curl -fsSL "${headers[@]}" "https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases" \
|
||||
| jq -r '.[0].tag_name // empty' || true)"
|
||||
|
||||
if [[ -z "${latest_tag}" ]]; then
|
||||
err "Could not resolve latest n_signer release tag from API."
|
||||
err "Set NSIGNER_VERSION explicitly (e.g. NSIGNER_VERSION=v0.0.11)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NSIGNER_VERSION="${latest_tag}"
|
||||
}
|
||||
|
||||
download_nsigner_asset_url() {
|
||||
local headers=()
|
||||
local api_tag_url="https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases/tags/${NSIGNER_VERSION}"
|
||||
|
||||
if [[ -n "${NSIGNER_GITEA_TOKEN:-}" ]]; then
|
||||
headers=(-H "Authorization: token ${NSIGNER_GITEA_TOKEN}")
|
||||
fi
|
||||
|
||||
curl -fsSL "${headers[@]}" "${api_tag_url}" \
|
||||
| jq -r '.assets[]?.browser_download_url // empty' \
|
||||
| grep -E 'nsigner_static_x86_64$' \
|
||||
| head -n1 || true
|
||||
}
|
||||
|
||||
install_nsigner() {
|
||||
local release_page=""
|
||||
|
||||
resolve_nsigner_version
|
||||
release_page="https://git.laantungir.net/laantungir/n_signer/releases/tag/${NSIGNER_VERSION}"
|
||||
|
||||
log "Installing n_signer ${NSIGNER_VERSION}"
|
||||
log "Release page: ${NSIGNER_RELEASE_PAGE}"
|
||||
log "Release page: ${release_page}"
|
||||
|
||||
local asset_url="${NSIGNER_BINARY_URL:-}"
|
||||
if [[ -z "${asset_url}" ]]; then
|
||||
@@ -135,103 +140,51 @@ 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}" <<EOF
|
||||
[Unit]
|
||||
Description=FIPS mesh daemon (user)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${PREFIX_BIN}/fips --config ${PREFIX_CFG}/fips/fips.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
Environment=RUST_LOG=info
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
|
||||
log "Wrote ${svc}"
|
||||
}
|
||||
|
||||
start_fips_user_service() {
|
||||
require_cmd systemctl
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now fips
|
||||
|
||||
if systemctl --user is-active --quiet fips; then
|
||||
log "fips user service: active"
|
||||
# Optional: print current FIPS identity info if fipsctl is available.
|
||||
if command -v fipsctl >/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')"
|
||||
LISTEN_PORT="$(printf '%s\n' "${LISTEN_TARGET}" | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p')"
|
||||
[[ -n "${FIPS_IPV6}" ]] && echo "fips ipv6: ${FIPS_IPV6}"
|
||||
[[ -n "${FIPS_NPUB}" ]] && echo "fips npub: ${FIPS_NPUB}"
|
||||
if [[ -n "${FIPS_NPUB}" && -n "${LISTEN_PORT}" ]]; then
|
||||
echo "fips address: http://${FIPS_NPUB}.fips:${LISTEN_PORT}"
|
||||
fi
|
||||
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 +192,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 +204,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 "$@"
|
||||
|
||||
@@ -584,7 +584,10 @@ char *dispatcher_handle_request(dispatcher_ctx_t *ctx, const char *json_request)
|
||||
|
||||
selector_request_init(&selector_req);
|
||||
|
||||
options_item = cJSON_GetArrayItem(params_item, 1);
|
||||
{
|
||||
int params_count = cJSON_GetArraySize(params_item);
|
||||
options_item = (params_count > 0) ? cJSON_GetArrayItem(params_item, params_count - 1) : NULL;
|
||||
}
|
||||
if (cJSON_IsObject(options_item)) {
|
||||
tmp = cJSON_GetObjectItemCaseSensitive(options_item, "role");
|
||||
if (tmp != NULL) {
|
||||
|
||||
188
src/main.c
188
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 14
|
||||
#define NSIGNER_VERSION "v0.0.14"
|
||||
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
@@ -746,6 +746,28 @@ static void activity_log_cb(const char *message, void *user_data) {
|
||||
g_activity_log.count++;
|
||||
}
|
||||
|
||||
static void tcp_activity_stdout_cb(const char *message, void *user_data) {
|
||||
time_t now;
|
||||
struct tm tm_now;
|
||||
char ts[32];
|
||||
|
||||
(void)user_data;
|
||||
if (message == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
now = time(NULL);
|
||||
if (localtime_r(&now, &tm_now) != NULL) {
|
||||
(void)strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tm_now);
|
||||
} else {
|
||||
strncpy(ts, "0000-00-00 00:00:00", sizeof(ts) - 1);
|
||||
ts[sizeof(ts) - 1] = '\0';
|
||||
}
|
||||
|
||||
printf("[%s] %s\n", ts, message);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void render_status(const role_table_t *role_table,
|
||||
const mnemonic_state_t *mnemonic,
|
||||
int derived_count,
|
||||
@@ -785,7 +807,7 @@ static void render_status(const role_table_t *role_table,
|
||||
}
|
||||
|
||||
printf("\nHotkeys\n-------\n");
|
||||
printf("q quit l lock/reunlock r refresh a toggle auto-approve(prompt): %s\n",
|
||||
printf("q/x quit l lock/reunlock r refresh a toggle auto-approve(prompt): %s\n",
|
||||
g_auto_approve ? "ON" : "OFF");
|
||||
fflush(stdout);
|
||||
}
|
||||
@@ -814,94 +836,104 @@ static int prompt_load_mnemonic(mnemonic_state_t *mnemonic) {
|
||||
char phrase[MNEMONIC_MAX_LEN];
|
||||
char phrase_copy[MNEMONIC_MAX_LEN];
|
||||
char mode[MNEMONIC_MAX_LEN];
|
||||
struct termios old_term;
|
||||
struct termios new_term;
|
||||
int have_term = 0;
|
||||
int invalid_attempts = 0;
|
||||
const int max_invalid_attempts = 10;
|
||||
|
||||
if (mnemonic == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Mnemonic source: [E]nter existing or [G]enerate new (default E; you can also paste mnemonic here): ");
|
||||
fflush(stdout);
|
||||
if (read_line_stdin(mode, sizeof(mode)) != 0) {
|
||||
fprintf(stderr, "Failed to read mnemonic source choice\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strchr(mode, ' ') != NULL && mode[0] != 'g' && mode[0] != 'G') {
|
||||
if (mnemonic_load(mnemonic, mode) != 0) {
|
||||
fprintf(stderr, "Invalid mnemonic (must be 12/15/18/21/24 words)\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mode[0] == 'g' || mode[0] == 'G') {
|
||||
int idx = 1;
|
||||
char *ctx = NULL;
|
||||
char *word;
|
||||
|
||||
if (mnemonic_generate(12, phrase, sizeof(phrase)) != 0) {
|
||||
fprintf(stderr, "Failed to generate mnemonic\n");
|
||||
while (invalid_attempts < max_invalid_attempts) {
|
||||
printf("Mnemonic source: [E]nter existing or [G]enerate new (default E; you can also paste mnemonic here): ");
|
||||
fflush(stdout);
|
||||
if (read_line_stdin(mode, sizeof(mode)) != 0) {
|
||||
fprintf(stderr, "Failed to read mnemonic source choice\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncpy(phrase_copy, phrase, sizeof(phrase_copy) - 1);
|
||||
phrase_copy[sizeof(phrase_copy) - 1] = '\0';
|
||||
|
||||
printf("\nGenerated mnemonic (WRITE THIS DOWN - it will not be shown again):\n");
|
||||
word = strtok_r(phrase_copy, " ", &ctx);
|
||||
while (word != NULL) {
|
||||
printf("%2d. %s\n", idx, word);
|
||||
idx++;
|
||||
word = strtok_r(NULL, " ", &ctx);
|
||||
if ((mode[0] == 'q' || mode[0] == 'Q' || mode[0] == 'x' || mode[0] == 'X') && mode[1] == '\0') {
|
||||
fprintf(stderr, "User requested exit.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mnemonic_load(mnemonic, phrase) != 0) {
|
||||
if (strchr(mode, ' ') != NULL && mode[0] != 'g' && mode[0] != 'G') {
|
||||
if (mnemonic_load(mnemonic, mode) == 0) {
|
||||
printf("Seed phrase is valid and accepted.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
invalid_attempts++;
|
||||
fprintf(stderr,
|
||||
"Invalid mnemonic (must be 12/15/18/21/24 words). Attempts: %d/%d\n",
|
||||
invalid_attempts,
|
||||
max_invalid_attempts);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode[0] == 'g' || mode[0] == 'G') {
|
||||
int idx = 1;
|
||||
char *ctx = NULL;
|
||||
char *word;
|
||||
|
||||
if (mnemonic_generate(12, phrase, sizeof(phrase)) != 0) {
|
||||
fprintf(stderr, "Failed to generate mnemonic\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncpy(phrase_copy, phrase, sizeof(phrase_copy) - 1);
|
||||
phrase_copy[sizeof(phrase_copy) - 1] = '\0';
|
||||
|
||||
printf("\nGenerated mnemonic (WRITE THIS DOWN - it will not be shown again):\n");
|
||||
word = strtok_r(phrase_copy, " ", &ctx);
|
||||
while (word != NULL) {
|
||||
printf("%2d. %s\n", idx, word);
|
||||
idx++;
|
||||
word = strtok_r(NULL, " ", &ctx);
|
||||
}
|
||||
|
||||
if (mnemonic_load(mnemonic, phrase) != 0) {
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
memset(phrase_copy, 0, sizeof(phrase_copy));
|
||||
fprintf(stderr, "Failed to load generated mnemonic\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Seed phrase is valid and accepted.\n");
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
memset(phrase_copy, 0, sizeof(phrase_copy));
|
||||
fprintf(stderr, "Failed to load generated mnemonic\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Enter mnemonic (12/15/18/21/24 words): ");
|
||||
fflush(stdout);
|
||||
|
||||
if (read_line_stdin(phrase, sizeof(phrase)) != 0) {
|
||||
fprintf(stderr, "Failed to read mnemonic\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
memset(phrase_copy, 0, sizeof(phrase_copy));
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Enter mnemonic (12/15/18/21/24 words): ");
|
||||
fflush(stdout);
|
||||
|
||||
if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &old_term) == 0) {
|
||||
new_term = old_term;
|
||||
new_term.c_lflag &= (tcflag_t)~ECHO;
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &new_term) == 0) {
|
||||
have_term = 1;
|
||||
if ((phrase[0] == 'q' || phrase[0] == 'Q' || phrase[0] == 'x' || phrase[0] == 'X') && phrase[1] == '\0') {
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
fprintf(stderr, "User requested exit.\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (read_line_stdin(phrase, sizeof(phrase)) != 0) {
|
||||
if (have_term) {
|
||||
(void)tcsetattr(STDIN_FILENO, TCSANOW, &old_term);
|
||||
if (mnemonic_load(mnemonic, phrase) == 0) {
|
||||
printf("Seed phrase is valid and accepted.\n");
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Failed to read mnemonic\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (have_term) {
|
||||
(void)tcsetattr(STDIN_FILENO, TCSANOW, &old_term);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (mnemonic_load(mnemonic, phrase) != 0) {
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
fprintf(stderr, "Invalid mnemonic (must be 12/15/18/21/24 words)\n");
|
||||
return -1;
|
||||
invalid_attempts++;
|
||||
fprintf(stderr,
|
||||
"Invalid mnemonic (must be 12/15/18/21/24 words). Attempts: %d/%d\n",
|
||||
invalid_attempts,
|
||||
max_invalid_attempts);
|
||||
}
|
||||
|
||||
memset(phrase, 0, sizeof(phrase));
|
||||
return 0;
|
||||
fprintf(stderr, "Too many invalid mnemonic attempts (%d). Exiting.\n", max_invalid_attempts);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void apply_test_overrides(policy_table_t *policy) {
|
||||
@@ -1033,6 +1065,9 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("nsigner %s\n", NSIGNER_VERSION);
|
||||
fflush(stdout);
|
||||
|
||||
mnemonic_init(&mnemonic);
|
||||
if (prompt_load_mnemonic(&mnemonic) != 0) {
|
||||
mnemonic_unload(&mnemonic);
|
||||
@@ -1119,6 +1154,17 @@ int main(int argc, char *argv[]) {
|
||||
(void)signal(SIGINT, handle_signal);
|
||||
(void)signal(SIGTERM, handle_signal);
|
||||
|
||||
if (listen_mode == NSIGNER_LISTEN_UNIX) {
|
||||
printf("System is ready and waiting for connections on @%s.\n", socket_name);
|
||||
} else if (listen_mode == NSIGNER_LISTEN_TCP) {
|
||||
printf("System is ready and waiting for connections on %s.\n", listen_target);
|
||||
} else if (listen_mode == NSIGNER_LISTEN_QREXEC) {
|
||||
printf("System is ready and waiting for a qrexec request.\n");
|
||||
} else {
|
||||
printf("System is ready and waiting for a stdio request.\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
if (listen_mode == NSIGNER_LISTEN_STDIO || listen_mode == NSIGNER_LISTEN_QREXEC) {
|
||||
int hrc = server_handle_one(&server, NULL, NULL);
|
||||
server_stop(&server);
|
||||
@@ -1141,7 +1187,7 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
if (prc > 0 && (pfds[0].revents & POLLIN)) {
|
||||
if (server_handle_one(&server, NULL, NULL) < 0) {
|
||||
if (server_handle_one(&server, tcp_activity_stdout_cb, NULL) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1185,7 +1231,7 @@ int main(int argc, char *argv[]) {
|
||||
char ch = '\0';
|
||||
if (read(STDIN_FILENO, &ch, 1) > 0) {
|
||||
ch = (char)tolower((unsigned char)ch);
|
||||
if (ch == 'q') {
|
||||
if (ch == 'q' || ch == 'x') {
|
||||
g_running = 0;
|
||||
} else if (ch == 'r') {
|
||||
render_status(&role_table, &mnemonic, derived_count, socket_name);
|
||||
|
||||
170
src/server.c
170
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");
|
||||
@@ -666,7 +831,10 @@ static int extract_method_and_selector(const char *json,
|
||||
|
||||
params_item = cJSON_GetObjectItemCaseSensitive(root, "params");
|
||||
if (cJSON_IsArray(params_item)) {
|
||||
options_item = cJSON_GetArrayItem(params_item, 1);
|
||||
{
|
||||
int params_count = cJSON_GetArraySize(params_item);
|
||||
options_item = (params_count > 0) ? cJSON_GetArrayItem(params_item, params_count - 1) : NULL;
|
||||
}
|
||||
if (cJSON_IsObject(options_item)) {
|
||||
tmp = cJSON_GetObjectItemCaseSensitive(options_item, "role");
|
||||
if (cJSON_IsString(tmp) && tmp->valuestring != NULL) {
|
||||
|
||||
Reference in New Issue
Block a user