Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Build noxssh

permissions:
contents: read

on:
push:
branches: [ master, main ]
Expand Down Expand Up @@ -47,17 +50,23 @@ jobs:
mkdir -p dist/debian
cp build/*.deb dist/debian/

- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: noxssh-${{ matrix.artifact_suffix }}
path: bin/**
if-no-files-found: error
- name: Verify binaries
shell: bash
run: |
set -euo pipefail
test -f bin/noxssh
if [[ "${{ runner.os }}" == "Linux" ]]; then test -f bin/noxsshd; fi

- name: Upload Debian package
- name: CTest
if: runner.os != 'Windows'
run: cd build && ctest --output-on-failure

- name: CTest (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: cd build; ctest -C Release --output-on-failure

- name: Smoke noxsshd (Unix)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: noxssh-${{ matrix.artifact_suffix }}-deb
path: dist/debian/*.deb
if-no-files-found: error
shell: bash
run: bash scripts/ci_smoke_sshd.sh
7 changes: 7 additions & 0 deletions .github/workflows/release-on-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ jobs:
cp "${deb}" "release-assets/noxssh-${{ matrix.artifact_suffix }}-v${VERSION}.deb"
done

- name: Verify binaries
shell: bash
run: |
set -euo pipefail
test -f bin/noxssh
if [[ "${{ runner.os }}" == "Linux" ]]; then test -f bin/noxsshd; fi

- name: Create release archives
shell: bash
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "noxtls"]
path = noxtls
url = git@github.com:argenox/noxtls.git
url = https://github.com/argenox/noxtls.git
branch = master
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ add_subdirectory(noxtls EXCLUDE_FROM_ALL)
add_library(netnox_ssh
common/noxssh_common.c
)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_sources(netnox_ssh PRIVATE common/noxssh_server.c)
endif()
target_include_directories(netnox_ssh
PUBLIC
${CMAKE_SOURCE_DIR}/common
Expand All @@ -74,6 +77,15 @@ target_compile_definitions(netnox_ssh
NOXTLS_FEATURE_AES_CTR=1
)
add_subdirectory(client)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_subdirectory(server)
endif()

option(NOXSSH_BUILD_TESTS "Build noxssh unit tests" ON)
if(NOXSSH_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

install(FILES
README.md
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# NoxSSH

The opt-in Linux SSH server is documented in [docs/server.md](docs/server.md).

[![Build](https://github.com/argenox/noxssh/actions/workflows/build.yml/badge.svg)](https://github.com/argenox/noxssh/actions/workflows/build.yml)
[![Release on tag](https://github.com/argenox/noxssh/actions/workflows/release-on-tag.yml/badge.svg)](https://github.com/argenox/noxssh/actions/workflows/release-on-tag.yml)

Expand Down
10 changes: 9 additions & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ if (-not (Test-Path $BinDir)) {
Write-Host "Configuring CMake project..."
cmake -S $Root -B $BuildDir -DCMAKE_BUILD_TYPE=$Config

Write-Host "Building noxssh ($Config)..."
Write-Host "Building noxssh (client) and noxsshd (server), $Config..."
cmake --build $BuildDir --config $Config

$client = Join-Path $BinDir "noxssh.exe"
$daemon = Join-Path $BinDir "noxsshd.exe"
if (-not (Test-Path $client)) {
Write-Warning "Expected client binary: $client"
}
if (-not (Test-Path $daemon)) {
Write-Warning "Expected server binary: $daemon"
}
Write-Host "Build complete. Binaries are in: $BinDir"
8 changes: 7 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ git -C "${ROOT_DIR}" submodule update --init --recursive
cmake -S "${ROOT_DIR}" -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
cmake --build "${BUILD_DIR}"

printf 'Build complete. Binaries are in: %s\n' "${BIN_DIR}"
printf 'Build complete (noxssh client + noxsshd server). Binaries are in: %s\n' "${BIN_DIR}"
if [ ! -f "${BIN_DIR}/noxssh" ]; then
printf 'Warning: expected %s/noxssh\n' "${BIN_DIR}" >&2
fi
if [ ! -f "${BIN_DIR}/noxsshd" ]; then
printf 'Warning: expected %s/noxsshd\n' "${BIN_DIR}" >&2
fi
7 changes: 7 additions & 0 deletions client/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,13 @@ static int noxssh_load_ed25519_seed_file(const char * path, uint8_t out_seed[32]
}
raw[nread] = '\0';

/* Base64 contains hex-looking characters; detect OpenSSH keys before
* scanning for a bare hex seed. Otherwise a normal key exceeds 64 hex
* characters and is rejected before the OpenSSH parser runs. */
if(strstr(raw, "-----BEGIN OPENSSH PRIVATE KEY-----") != NULL) {
return noxssh_load_openssh_ed25519_key(raw, (uint32_t)nread, out_seed);
}

for(i = 0u; i < (uint32_t)nread; i++) {
int nib = noxssh_hex_nibble(raw[i]);
if(nib >= 0) {
Expand Down
55 changes: 55 additions & 0 deletions common/noxssh_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -2046,6 +2046,51 @@ static netnox_return_t netnox_ssh_negotiate_userauth_service(netnox_ssh_client_t
return NETNOX_RETURN_SUCCESS;
}

/**
* @brief Perform service request/accept for ssh-connection (RFC 4254).
* @internal
*/
static netnox_return_t netnox_ssh_negotiate_connection_service(netnox_ssh_client_t * client)
{
uint8_t payload[256];
uint32_t payload_len = 0u;
uint32_t offset = 1u;
const uint8_t * svc = NULL;
uint32_t svc_len = 0u;

if(client == NULL) {
return NETNOX_RETURN_BAD_PARAM;
}
SSH_DEBUG("negotiate_connection: sending service request ssh-connection");
if(netnox_ssh_send_service_request(client, NETNOX_SSH_SERVICE_CONNECTION) != NETNOX_RETURN_SUCCESS) {
SSH_DEBUG("negotiate_connection: send_service_request failed");
return NETNOX_RETURN_FAILED;
}
if(netnox_ssh_wait_for_message(client,
NETNOX_SSH_MSG_SERVICE_ACCEPT,
0xFFu,
payload,
(uint32_t)sizeof(payload),
&payload_len) != NETNOX_RETURN_SUCCESS) {
SSH_DEBUG("negotiate_connection: wait_for_message SERVICE_ACCEPT failed");
return NETNOX_RETURN_FAILED;
}
if(payload[0] != NETNOX_SSH_MSG_SERVICE_ACCEPT) {
SSH_DEBUG("negotiate_connection: unexpected message %u", (unsigned)payload[0]);
return NETNOX_RETURN_FAILED;
}
if(netnox_ssh_payload_read_string_view(payload, payload_len, &offset, &svc, &svc_len) != NETNOX_RETURN_SUCCESS) {
return NETNOX_RETURN_FAILED;
}
if(svc_len != strlen(NETNOX_SSH_SERVICE_CONNECTION) ||
memcmp(svc, NETNOX_SSH_SERVICE_CONNECTION, svc_len) != 0) {
return NETNOX_RETURN_FAILED;
}

client->connection_service_ready = 1u;
return NETNOX_RETURN_SUCCESS;
}

/**
* @brief Send password-based userauth request.
* @internal
Expand Down Expand Up @@ -2483,6 +2528,15 @@ netnox_return_t netnox_ssh_client_open_session(netnox_ssh_client_t * client)
client->local_channel_id = 0u;
client->local_window_size = NETNOX_SSH_CHANNEL_WINDOW_SIZE;

if(client->connection_service_ready == 0u) {
SSH_DEBUG("open_session: requesting ssh-connection service");
if(netnox_ssh_negotiate_connection_service(client) != NETNOX_RETURN_SUCCESS) {
SSH_DEBUG("open_session: negotiate_connection_service failed");
return NETNOX_RETURN_FAILED;
}
SSH_DEBUG("open_session: ssh-connection service accepted");
}

if(netnox_ssh_send_channel_open_session(client) != NETNOX_RETURN_SUCCESS) {
SSH_DEBUG("open_session: send CHANNEL_OPEN(session) failed");
return NETNOX_RETURN_FAILED;
Expand Down Expand Up @@ -2946,6 +3000,7 @@ netnox_return_t netnox_ssh_client_close(netnox_ssh_client_t * client)
client->key_exchange_complete = 0u;
client->pending_newkeys_len = 0u;
client->userauth_service_ready = 0u;
client->connection_service_ready = 0u;
client->authenticated = 0u;
client->channel_open = 0u;
client->local_channel_id = 0u;
Expand Down
24 changes: 22 additions & 2 deletions common/noxssh_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,24 @@ typedef struct netnox_interface_s netnox_interface_t;
#define NETNOX_SSH_DEFAULT_PORT (22u)
/** @brief Default SSH client identification string. */
#define NETNOX_SSH_DEFAULT_CLIENT_IDENT "SSH-2.0-noxssh_0.1"
/** @brief Default SSH server identification string. */
#define NETNOX_SSH_DEFAULT_SERVER_IDENT "SSH-2.0-noxsshd_0.1"

/** @brief SSH message number for disconnect. */
#define NETNOX_SSH_MSG_DISCONNECT (1u)
/** @brief SSH message number for ignore. */
#define NETNOX_SSH_MSG_IGNORE (2u)
/** @brief SSH message number for ext-info (RFC 8308). */
#define NETNOX_SSH_MSG_EXT_INFO (7u)
/** @brief SSH message number for global request. */
#define NETNOX_SSH_MSG_GLOBAL_REQUEST (80u)
/** @brief SSH message number for request success (global). */
#define NETNOX_SSH_MSG_REQUEST_SUCCESS (81u)
/** @brief SSH message number for request failure (global). */
#define NETNOX_SSH_MSG_REQUEST_FAILURE (82u)

/** @brief SSH message number for service request. */
#define NETNOX_SSH_MSG_SERVICE_REQUEST (5u)
/** @brief SSH message number for ignore/no-op packets. */
#define NETNOX_SSH_MSG_IGNORE (2u)
/** @brief SSH message number for service accept. */
#define NETNOX_SSH_MSG_SERVICE_ACCEPT (6u)
/** @brief SSH message number for key exchange initialization. */
Expand All @@ -85,6 +98,8 @@ typedef struct netnox_interface_s netnox_interface_t;
#define NETNOX_SSH_MSG_CHANNEL_OPEN_CONFIRMATION (91u)
/** @brief SSH message number for channel open failure. */
#define NETNOX_SSH_MSG_CHANNEL_OPEN_FAILURE (92u)
/** @brief SSH message number for channel window adjust. */
#define NETNOX_SSH_MSG_CHANNEL_WINDOW_ADJUST (93u)
/** @brief SSH message number for channel data. */
#define NETNOX_SSH_MSG_CHANNEL_DATA (94u)
/** @brief SSH message number for channel extended data (stderr). */
Expand Down Expand Up @@ -118,6 +133,10 @@ typedef struct netnox_interface_s netnox_interface_t;
#define NETNOX_SSH_CHANNEL_REQ_SHELL "shell"
/** @brief SHA-256 fingerprint size in bytes. */
#define NETNOX_SSH_HOST_KEY_FINGERPRINT_LEN (32u)
/** @brief Channel request type for subsystem (e.g. SFTP). */
#define NETNOX_SSH_CHANNEL_REQ_SUBSYSTEM "subsystem"
/** @brief SFTP subsystem name (RFC draft / de-facto standard). */
#define NETNOX_SSH_SUBSYSTEM_SFTP "sftp"

/** Callback to send data over the transport (socket/stream). Returns bytes sent or negative on error. */
typedef int32_t (*netnox_ssh_transport_send_t)(void * user_data, const uint8_t * data, uint32_t len);
Expand Down Expand Up @@ -145,6 +164,7 @@ typedef struct
uint8_t connected;
uint8_t kexinit_exchanged;
uint8_t userauth_service_ready;
uint8_t connection_service_ready;
uint8_t authenticated;
uint8_t channel_open;
uint8_t key_exchange_complete;
Expand Down
Loading
Loading