Harden SSH Admission And Credential Input

This commit is contained in:
2026-09-15 20:49:04 +02:00
parent 436c27adb1
commit 751dfb9ddb
32 changed files with 1751 additions and 39 deletions
+35
View File
@@ -0,0 +1,35 @@
# SSH authentication transport host regression
Run from the repository root:
```sh
CCACHE_DISABLE=1 python3 tests/ssh_auth_transport/run.py
```
Requires Python 3 and a C11 host compiler (`cc`, or `CC` override). The runner also sets `CCACHE_DISABLE=1` for its subprocesses. Compilation uses `-Wall -Wextra -Werror`; compilation and execution have timeouts. Generated C and executable live in a temporary directory, not the source tree. Assertion or compiler failures propagate as a nonzero exit.
## Production code under test
Following `tests/ssh_management/runtime.py`, `run.py` extracts named function definitions **verbatim** from `src/ssh_transport.c`. It extracts the actual slot definition, transport constants, session/route enums and counters declaration too, and links the actual `src/ssh_auth_policy.c` with its header. Missing/ambiguous function definitions fail extraction rather than silently falling back to fixture implementations.
The extracted set covers authentication callbacks and helpers, counter saturation/clear, socket acceptance and free-slot selection, start/stop, retirement, admin RX, serial RX and shared TX flushing. The fixture supplies only synchronous boundary doubles for clock, database, socket/library allocation and I/O, broker, console, publication and context/listener construction. No credential verifier, admission algorithm or callback logic is reimplemented. `boot()` resets test state between independent scenarios; the restart tests use actual production start/stop without resetting the policy.
## Coverage
- Password success, invalid password, backend failure and rejected password change; admissions versus completed attempts, principal promotion and pending-principal clearing.
- Signed key authorization denial/backend error, bad signature result, stale principal, currentness backend failure and success. Admission precedes authorization, authorized signed keys await completion, and duplicate/unexpected callbacks cannot create a promotion or count again. Verification-throttled signed requests do not reach authorization or acquire a pending signature-result marker.
- Close/state/pending-result fences, unsupported method handling, keyboard callback rejection with the entire prompt structure cleared and no repeated method count after close.
- Unsigned authorized/rejected probes share a 12-token pool across both slots/reconnects, refill one token at five seconds and never become completed attempts. Password/signed verification shares six tokens, refilling one per ten seconds. Three completed failed password or signed-key attempts close the slot.
- Actual acceptance loop: two-slot capacity, six handshake admissions, allocation failures consume admission, throttle denial before allocation, and one-token/ten-second refill.
- Actual stop/start and counter clear preserve all three exhausted pools. Counter clear initialization guard, 64-bit saturating addition and per-slot 8-bit saturation.
- Partial admin RX consumption (including `false` with consumed bytes), zero-consumption rejection and full drain wipe only consumed spans. WANT_READ, WANT_WRITE, rekey, window/channel retry, zero and error paths preserve pending TX; positive partial sends wipe only accepted admin spans. Binary serial RX/TX remains unchanged, including embedded NUL/0xff and partial broker acceptance.
- Whole-slot retirement wipe including padding and buffers, exact retained generation, `socket_fd == -1` sentinel, broker-disconnect failure retaining a closing slot, and repeated cleanup without double free/close. The old live fd is closed, not retained.
- Source checks for context registration of authentication, type advertisement, result and rejecting keyboard callbacks, plus per-session auth/result/keyboard contexts.
## Limits and deferred validation
The wolfSSH types in `fixture.h` are narrow host shapes, not ABI validation. Library return codes model distinct outcomes; this does not compile wolfSSH or perform cryptographic signature work. Denied authorization is verified to return a non-success result and leave no pending completion; proving the parser actually skips signature processing belongs to the separately delegated real wolfSSH parser-order regression. Do not treat these tests as its replacement.
Context construction is stubbed; callback registration is checked in production source rather than executed. Console/broker/database behavior and real socket scheduling are outside this suite. A fixture principal uses synthetic bytes to inspect lifecycle clearing; there are no real credentials.
No firmware build, device execution, upload or erase is performed. All device validation remains deferred to whole Phase 9, including real clients, timing, concurrency, recovery and physical UART/broker behavior.
+142
View File
@@ -0,0 +1,142 @@
/* Host-only boundary doubles. No authentication/policy/transport logic here. */
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "ssh_auth_policy.h"
typedef unsigned char byte;
typedef uint32_t word32;
typedef int esp_err_t;
typedef int WOLFSSH;
typedef int WOLFSSH_CTX;
typedef unsigned session_broker_client_id_t;
typedef struct { uint32_t id; unsigned char secret[28]; } user_principal_t;
typedef struct { unsigned slot; } admin_ssh_console_token_t;
typedef struct {
byte isCert, hasSignature;
const byte *publicKeyType, *publicKey;
word32 publicKeyTypeSz, publicKeySz;
} WS_UserAuthData_PublicKey;
typedef struct {
byte type;
const byte *username;
word32 usernameSz;
union {
struct { byte hasNewPassword; const byte *password; word32 passwordSz; } password;
WS_UserAuthData_PublicKey publicKey;
} sf;
} WS_UserAuthData;
typedef struct { unsigned promptCount; void *prompts; byte storage[32]; } WS_UserAuthData_Keyboard;
enum { ESP_OK, ESP_FAIL, ESP_ERR_INVALID_STATE, ESP_ERR_TIMEOUT, ESP_ERR_NOT_FOUND };
enum { WOLFSSH_USERAUTH_PASSWORD=1, WOLFSSH_USERAUTH_PUBLICKEY=2,
WOLFSSH_USERAUTH_SUCCESS=0, WOLFSSH_USERAUTH_REJECTED=3,
WOLFSSH_USERAUTH_INVALID_AUTHTYPE, WOLFSSH_USERAUTH_INVALID_PASSWORD,
WOLFSSH_USERAUTH_FAILURE, WOLFSSH_USERAUTH_INVALID_PUBLICKEY };
enum { WS_SUCCESS=0, WS_ERROR=-1, WS_WANT_READ=-2, WS_WANT_WRITE=-3,
WS_REKEYING=-4, WS_WINDOW_FULL=-5, WS_CHAN_RXD=-6 };
#define SESSION_BROKER_NO_CLIENT 0U
/* PRODUCTION TYPES */
static ssh_slot_t s_slots[SSH_TRANSPORT_MAX_SESSIONS];
static ssh_transport_counters_t s_counters;
static ssh_auth_policy_t s_auth_policy;
static WOLFSSH_CTX *s_context;
static int s_listen_fd=-1, s_lock;
static bool s_initialized;
static unsigned lock_depth, shutdown_calls, close_calls, password_calls, key_calls;
static unsigned current_calls, allocations, frees, context_frees;
static int64_t now_us;
static bool db_accept, db_current, feed_ok, accepts_input=true, allocation_fail;
static esp_err_t db_error, current_error, broker_error;
static size_t feed_consumed, broker_accepted;
static int send_result, read_result, ssh_error, accepts_remaining;
static const byte payload[] = {0x00, 0xff, 'p', 'a', 's', 's', '\r', '\n'};
static const void *wipe_address;
static size_t wipe_size;
#define taskENTER_CRITICAL(p) do { (void)(p); assert(lock_depth++ == 0); } while (0)
#define taskEXIT_CRITICAL(p) do { (void)(p); assert(--lock_depth == 0); } while (0)
#define pdMS_TO_TICKS(n) (n)
static void secure_wipe(void *p, size_t n) {
wipe_address=p; wipe_size=n;
volatile byte *b=p; while(n--) *b++=0;
}
static int64_t esp_timer_get_time(void) { return now_us; }
static int fake_shutdown(int fd, int how) { assert(fd>=0 && how==SHUT_RDWR); ++shutdown_calls; return 0; }
static int fake_close(int fd) { assert(fd>=0); ++close_calls; return 0; }
#define shutdown fake_shutdown
#define close fake_close
static esp_err_t user_database_authenticate_password(const byte *u, word32 un,
const byte *p, word32 pn, user_principal_t *out, bool *ok) {
assert(u && un==1 && p && pn==sizeof(payload)); ++password_calls;
memset(out, 0x5a, sizeof(*out)); *ok=db_accept; return db_error;
}
static esp_err_t user_database_authorize_ssh_public_key(const byte *u, word32 un,
const byte *t, word32 tn, const byte *k, word32 kn, user_principal_t *out, bool *ok) {
assert(u && un==1 && t && tn==1 && k && kn==sizeof(payload)); ++key_calls;
memset(out, 0x5a, sizeof(*out)); *ok=db_accept; return db_error;
}
static esp_err_t user_database_principal_is_current(const user_principal_t *p, bool *ok) {
assert(p->id==0x5a5a5a5a); ++current_calls; *ok=db_current; return current_error;
}
static admin_ssh_console_token_t admin_console_token(ssh_slot_t *s, size_t i) {
assert(s==&s_slots[i]); return (admin_ssh_console_token_t){(unsigned)i};
}
static void admin_ssh_console_close(const admin_ssh_console_token_t *t) { assert(t->slot<2); }
static int wolfSSH_shutdown(WOLFSSH *s) { assert(s); return 0; }
static void wolfSSH_free(WOLFSSH *s) { assert(s); ++frees; }
static esp_err_t session_broker_disconnect(session_broker_client_id_t id) { assert(id); return broker_error; }
static void publish_slot(ssh_slot_t *s, size_t i) { assert(s==&s_slots[i]); }
static void vTaskDelay(unsigned n) { (void)n; }
static esp_err_t create_context(void) { static WOLFSSH_CTX ctx; s_context=&ctx; return ESP_OK; }
static esp_err_t create_listener(void) { s_listen_fd=10; return ESP_OK; }
static void wolfSSH_CTX_free(WOLFSSH_CTX *c) { assert(c); ++context_frees; }
static int fake_accept(int fd, struct sockaddr *p, socklen_t *n) {
assert(fd==10); memset(p,0,*n);
if(accepts_remaining>0) { --accepts_remaining; return 20+accepts_remaining; }
errno=EAGAIN; return -1;
}
#define accept fake_accept
static int fake_setsockopt(int f,int l,int o,const void *v,socklen_t n) {
(void)f;(void)l;(void)o;(void)v;(void)n; return 0;
}
#define setsockopt fake_setsockopt
static esp_err_t set_nonblocking(int fd) { assert(fd>=0); return ESP_OK; }
static void format_peer(const struct sockaddr_storage *p,char *out,size_t n) {
(void)p; assert(n>4); strcpy(out,"host");
}
static WOLFSSH *wolfSSH_new(WOLFSSH_CTX *c) { static WOLFSSH ssh; assert(c); ++allocations; return allocation_fail ? NULL : &ssh; }
static int wolfSSH_set_fd(WOLFSSH *s,int fd) { assert(s && fd>=0); return WS_SUCCESS; }
#define CONTEXT_SETTER(name) static void name(WOLFSSH *s,void *p) { assert(s && p); }
CONTEXT_SETTER(wolfSSH_SetIOReadCtx)
CONTEXT_SETTER(wolfSSH_SetUserAuthCtx)
CONTEXT_SETTER(wolfSSH_SetUserAuthResultCtx)
CONTEXT_SETTER(wolfSSH_SetKeyboardAuthCtx)
CONTEXT_SETTER(wolfSSH_SetChannelReqCtx)
static int wolfSSH_get_error(WOLFSSH *s) { assert(s); return ssh_error; }
static int wolfSSH_stream_send(WOLFSSH *s,const byte *p,word32 n) {
assert(s && n && (send_result<=0 || (unsigned)send_result<=n));
assert(memcmp(p,payload+sizeof(payload)-n,n)==0); return send_result;
}
static int wolfSSH_stream_read(WOLFSSH *s,byte *p,word32 n) {
assert(s && n>=sizeof(payload));
if(read_result>0) { assert(read_result==(int)sizeof(payload)); memcpy(p,payload,sizeof(payload)); }
return read_result;
}
static bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *t,
const byte *p,size_t n,size_t *consumed) {
assert(t->slot<2 && feed_consumed<=n);
assert(memcmp(p,payload+sizeof(payload)-n,n)==0);
*consumed=feed_consumed; return feed_ok;
}
static bool admin_ssh_console_accepts_input(const admin_ssh_console_token_t *t) { assert(t->slot<2); return accepts_input; }
static esp_err_t session_broker_write(session_broker_client_id_t id,const byte *p,
size_t n,size_t *accepted) {
(void)id; assert(broker_accepted<=n);
assert(memcmp(p,payload+sizeof(payload)-n,n)==0);
*accepted=broker_accepted; return broker_error;
}
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Compile verbatim transport functions with narrow host boundary doubles."""
import os
from pathlib import Path
import re
import shlex
import subprocess
import tempfile
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
SOURCE = (ROOT / 'src/ssh_transport.c').read_text()
HEADER = (ROOT / 'src/ssh_transport.h').read_text()
def function(name):
matches = list(re.finditer(r'^(?:static )?[^\n;{}]+\b' + re.escape(name)
+ r'\([^;]*?\n\{.*?^\}', SOURCE, re.M | re.S))
assert len(matches) == 1, f'expected exactly one production definition: {name}'
return matches[0].group() + '\n'
def declaration(text, name):
match = re.search(r'^typedef (?:struct|enum) \{[^}]*\} ' + name + ';', text, re.M)
assert match, name
return match.group() + '\n'
for setter, callback in (
('wolfSSH_SetUserAuth', 'authenticate_user'),
('wolfSSH_SetUserAuthTypes', 'allowed_auth_types'),
('wolfSSH_SetUserAuthResult', 'authentication_result'),
('wolfSSH_SetKeyboardAuthPrompts', 'reject_keyboard_auth'),
):
assert re.search(r'\b' + setter + r'\(context,\s*' + callback + r'\)',
function('create_context')), setter
for setter in ('wolfSSH_SetUserAuthCtx', 'wolfSSH_SetUserAuthResultCtx',
'wolfSSH_SetKeyboardAuthCtx'):
assert re.search(r'\b' + setter + r'\(slot->ssh,\s*slot\)',
function('accept_connections')), setter
assert re.search(r'^static ssh_auth_policy_t s_auth_policy;', SOURCE, re.M)
names = '''add_counter make_session_id allowed_auth_types clear_pending_principal
close_authentication admit_authentication reject_keyboard_auth
complete_authentication_attempt authenticate_password authenticate_public_key
authenticate_user authentication_result wolfssh_would_block close_socket
cleanup_slot request_slot_close start_runtime stop_runtime find_free_slot
accept_connections flush_client_input receive_client_input flush_client_output
flush_admin_input receive_admin_input ssh_transport_clear_counters'''.split()
constants = '\n'.join(line for text in (HEADER, SOURCE) for line in text.splitlines()
if line.startswith('#define SSH_TRANSPORT_')) + '\n'
types = ''.join(declaration(HEADER, n) for n in (
'ssh_transport_session_state_t', 'ssh_transport_session_route_t',
'ssh_transport_counters_t')) + declaration(SOURCE, 'ssh_slot_t')
fixture = (HERE / 'fixture.h').read_text()
unit = fixture.replace('/* PRODUCTION TYPES */', constants + types)
unit += '\n'.join(function(n) for n in names)
unit += (HERE / 'test.c').read_text()
env = dict(os.environ, CCACHE_DISABLE='1')
with tempfile.TemporaryDirectory(prefix='ssh-auth-transport-') as directory:
out = Path(directory)
(out / 'test.c').write_text(unit)
subprocess.run(shlex.split(os.environ.get('CC', 'cc')) + [
'-std=c11', '-Wall', '-Wextra', '-Werror', '-g',
'-I', str(ROOT / 'src'), str(out / 'test.c'),
str(ROOT / 'src/ssh_auth_policy.c'), '-o', str(out / 'test')],
env=env, check=True, timeout=30)
subprocess.run([str(out / 'test')], env=env, check=True, timeout=10)
print('PASS production callback registration and per-slot context source checks')
+249
View File
@@ -0,0 +1,249 @@
/* Included after verbatim production definitions by run.py. */
static void zero_bytes(const void *p,size_t n) {
const byte *b=p; for(size_t i=0;i<n;++i) assert(b[i]==0);
}
static ssh_slot_t *fresh(unsigned i) {
assert(i<2); memset(&s_slots[i],0,sizeof(s_slots[i]));
s_slots[i].state=SSH_TRANSPORT_SESSION_HANDSHAKE;
s_slots[i].socket_fd=30+(int)i; s_slots[i].ssh=(WOLFSSH *)&s_lock;
return &s_slots[i];
}
static void boot(void) {
memset(s_slots,0,sizeof(s_slots)); memset(&s_counters,0,sizeof(s_counters));
memset(&s_auth_policy,0,sizeof(s_auth_policy));
s_context=NULL; s_listen_fd=-1; s_initialized=true; now_us=0;
password_calls=key_calls=current_calls=shutdown_calls=close_calls=0;
allocations=frees=context_frees=0; db_error=current_error=broker_error=ESP_OK;
db_accept=db_current=true; allocation_fail=false;
}
static WS_UserAuthData password(void) {
WS_UserAuthData a={.type=WOLFSSH_USERAUTH_PASSWORD,.username=(const byte *)"u",.usernameSz=1};
a.sf.password.password=payload; a.sf.password.passwordSz=sizeof(payload); return a;
}
static WS_UserAuthData key(bool signed_key) {
WS_UserAuthData a={.type=WOLFSSH_USERAUTH_PUBLICKEY,.username=(const byte *)"u",.usernameSz=1};
a.sf.publicKey=(WS_UserAuthData_PublicKey){.hasSignature=signed_key,
.publicKeyType=(const byte *)"k",.publicKeyTypeSz=1,
.publicKey=payload,.publicKeySz=sizeof(payload)}; return a;
}
static int auth(ssh_slot_t *s,WS_UserAuthData *a) { return authenticate_user(a->type,a,s); }
static void passwords(void) {
for(unsigned mode=0;mode<4;++mode) {
boot(); ssh_slot_t *s=fresh(0); WS_UserAuthData a=password();
db_accept=mode==0; db_error=mode==2 ? ESP_FAIL : ESP_OK;
a.sf.password.hasNewPassword=mode==3;
const int expected[]={WOLFSSH_USERAUTH_SUCCESS,WOLFSSH_USERAUTH_INVALID_PASSWORD,
WOLFSSH_USERAUTH_FAILURE,WOLFSSH_USERAUTH_INVALID_AUTHTYPE};
assert(auth(s,&a)==expected[mode]);
assert(password_calls==(mode==3 ? 0U : 1U));
assert(s->authenticated==(mode==0) && s->principal_valid==(mode==0));
assert(s->authentication_attempts==1 && s_counters.authentication_attempts==1);
assert(s_counters.authentication_failures==(mode!=0));
assert(s_counters.authentication_backend_errors==(mode==2));
assert(s_counters.authentication_admissions==1);
zero_bytes(&s->pending_principal,sizeof(s->pending_principal));
}
boot(); db_accept=false; WS_UserAuthData a=password();
for(unsigned i=0;i<2;++i) {
ssh_slot_t *s=fresh(i);
for(unsigned j=0;j<3;++j) {
assert(auth(s,&a)==(j==2 ? WOLFSSH_USERAUTH_REJECTED : WOLFSSH_USERAUTH_INVALID_PASSWORD));
assert(s->close_requested==(j==2));
}
}
assert(s_counters.authentication_limit_disconnects==2 && shutdown_calls==2);
ssh_slot_t *s=fresh(0);
assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED && password_calls==6);
assert(s->authentication_attempts==0 && s_counters.authentication_throttle_rejections==1);
WS_UserAuthData signed_key=key(true); s=fresh(1);
assert(auth(s,&signed_key)==WOLFSSH_USERAUTH_REJECTED && key_calls==0);
assert(!s->awaiting_auth_result && !s->pending_principal_valid);
assert(authentication_result(WOLFSSH_USERAUTH_SUCCESS,&signed_key,s)==WS_ERROR);
assert(s_counters.authentication_attempts==6 && current_calls==0 && !s->authenticated);
now_us=9999999; s=fresh(1); assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED);
now_us=10000000; s=fresh(0); assert(auth(s,&a)==WOLFSSH_USERAUTH_INVALID_PASSWORD);
s=fresh(1); assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED && password_calls==7);
puts("PASS password outcomes, shared verification budget/refill, reconnect and three-failure closure");
}
static void signed_keys(void) {
for(unsigned mode=0;mode<6;++mode) {
boot(); ssh_slot_t *s=fresh(0); WS_UserAuthData a=key(true);
db_accept=mode!=0; db_error=mode==1 ? ESP_FAIL : ESP_OK;
int result=auth(s,&a); assert(key_calls==1);
if(mode<2) {
assert(result==(mode==0 ? WOLFSSH_USERAUTH_INVALID_PUBLICKEY : WOLFSSH_USERAUTH_FAILURE));
assert(!s->awaiting_auth_result && !s->pending_principal_valid);
assert(s_counters.authentication_attempts==1);
/* Denied authorization never grants permission for signature work. */
assert(authentication_result(WOLFSSH_USERAUTH_SUCCESS,&a,s)==WS_ERROR);
} else {
assert(result==WOLFSSH_USERAUTH_SUCCESS && s->awaiting_auth_result);
assert(!s->authenticated && s_counters.authentication_attempts==0);
db_current=mode!=3; current_error=mode==4 ? ESP_FAIL : ESP_OK;
assert(authentication_result(mode==2 ? WOLFSSH_USERAUTH_FAILURE : WOLFSSH_USERAUTH_SUCCESS,
&a,s)==((mode==3 || mode==4) ? WS_ERROR : WS_SUCCESS));
assert(s->authenticated==(mode==5));
assert(current_calls==(mode==2 ? 0U : 1U));
assert(s_counters.authentication_attempts==1);
assert(s_counters.authentication_admissions==1);
assert(s_counters.authentication_failures==(mode!=5));
assert(s_counters.authentication_backend_errors==(mode==4));
bool before=s->authenticated;
assert(authentication_result(WOLFSSH_USERAUTH_SUCCESS,&a,s)==WS_ERROR);
assert(s->authenticated==before);
}
assert(s_counters.authentication_attempts==1 && s->close_requested);
assert(!s->awaiting_auth_result && !s->pending_principal_valid);
zero_bytes(&s->pending_principal,sizeof(s->pending_principal));
}
boot(); WS_UserAuthData a=key(true); ssh_slot_t *s=fresh(0);
for(unsigned i=0;i<3;++i) {
assert(auth(s,&a)==WOLFSSH_USERAUTH_SUCCESS);
assert(authentication_result(WOLFSSH_USERAUTH_FAILURE,&a,s)==WS_SUCCESS);
assert(s->close_requested==(i==2));
}
assert(s_counters.authentication_limit_disconnects==1);
puts("PASS signed authorization, result/currentness failures, success, exactly-once completion and limits");
}
static void fences_and_probes(void) {
boot(); WS_UserAuthData a=key(false);
for(unsigned i=0;i<12;++i) {
ssh_slot_t *s=fresh(i%2); db_accept=i%2==0;
assert(auth(s,&a)==(db_accept ? WOLFSSH_USERAUTH_SUCCESS : WOLFSSH_USERAUTH_INVALID_PUBLICKEY));
assert(!s->awaiting_auth_result && !s->authenticated && !s->authentication_attempts);
}
ssh_slot_t *s=fresh(0); assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED);
assert(key_calls==12 && s_counters.authentication_attempts==0);
assert(s_counters.authentication_probe_admissions==12 && s_counters.authentication_probe_rejections==1);
now_us=4999999; s=fresh(1); assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED);
now_us=5000000; db_accept=true; s=fresh(0); assert(auth(s,&a)==WOLFSSH_USERAUTH_SUCCESS);
s=fresh(1); assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED && key_calls==13);
a=key(true); s=fresh(0); assert(auth(s,&a)==WOLFSSH_USERAUTH_SUCCESS);
assert(s_counters.authentication_admissions==1); /* Probe pool independent. */
for(unsigned mode=0;mode<7;++mode) {
boot(); s=fresh(0); a=key(true);
if(mode<3) assert(auth(s,&a)==WOLFSSH_USERAUTH_SUCCESS);
if(mode==0) s->close_requested=true;
if(mode==1) s->state=SSH_TRANSPORT_SESSION_CLOSING;
if(mode==2) { assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED); }
if(mode==4) a.sf.publicKey.hasSignature=0;
if(mode==5) a=password();
assert(authentication_result(WOLFSSH_USERAUTH_SUCCESS,mode==6 ? NULL : &a,s)==WS_ERROR);
assert(!s->authenticated && !s->principal_valid && !s->awaiting_auth_result);
assert(s_counters.authentication_attempts==0 && current_calls==0);
assert(auth(s,&a)==WOLFSSH_USERAUTH_REJECTED);
}
boot(); s=fresh(0); a=password();
assert(authenticate_user(99,&a,s)==WOLFSSH_USERAUTH_REJECTED);
assert(s_counters.authentication_method_rejections==1 && !password_calls);
assert(allowed_auth_types(NULL,NULL)==(WOLFSSH_USERAUTH_PASSWORD|WOLFSSH_USERAUTH_PUBLICKEY));
boot(); s=fresh(0); a=key(true); assert(auth(s,&a)==WOLFSSH_USERAUTH_SUCCESS);
WS_UserAuthData_Keyboard keyboard; memset(&keyboard,0xa5,sizeof(keyboard));
assert(reject_keyboard_auth(&keyboard,s)==WS_ERROR); zero_bytes(&keyboard,sizeof(keyboard));
assert(s->close_requested && !s->pending_principal_valid && !s->awaiting_auth_result);
assert(s_counters.authentication_method_rejections==1 && !s_counters.authentication_attempts);
assert(reject_keyboard_auth(NULL,s)==WS_ERROR && s_counters.authentication_method_rejections==1);
assert(reject_keyboard_auth(NULL,NULL)==WS_ERROR);
puts("PASS bounded unsigned probes, independent pools, result/close fences and keyboard decline without prompts");
}
static void admissions_lifecycle(void) {
boot(); assert(start_runtime()==ESP_OK);
accepts_remaining=3; accept_connections();
assert(allocations==2 && s_counters.capacity_rejections==1);
assert(s_counters.handshake_admissions==2);
assert(s_slots[0].generation==1 && s_slots[1].generation==1);
assert(cleanup_slot(&s_slots[0]) && cleanup_slot(&s_slots[1]));
allocation_fail=true; accepts_remaining=4; accept_connections();
assert(allocations==6 && s_counters.handshake_admissions==6 && s_counters.handshake_failures==4);
accepts_remaining=1; accept_connections();
assert(allocations==6 && s_counters.handshake_throttle_rejections==1);
WS_UserAuthData p=password(), k=key(false); db_accept=false;
for(unsigned i=0;i<6;++i) assert(auth(fresh(i%2),&p)==WOLFSSH_USERAUTH_INVALID_PASSWORD);
for(unsigned i=0;i<12;++i) assert(auth(fresh(i%2),&k)==WOLFSSH_USERAUTH_INVALID_PUBLICKEY);
ssh_auth_policy_t saved=s_auth_policy;
assert(stop_runtime()==ESP_OK && start_runtime()==ESP_OK);
assert(memcmp(&saved,&s_auth_policy,sizeof(saved))==0);
s_initialized=false; assert(ssh_transport_clear_counters()==ESP_ERR_INVALID_STATE);
s_initialized=true; assert(ssh_transport_clear_counters()==ESP_OK);
zero_bytes(&s_counters,sizeof(s_counters));
assert(memcmp(&saved,&s_auth_policy,sizeof(saved))==0);
accepts_remaining=1; accept_connections(); assert(allocations==6);
assert(auth(fresh(0),&p)==WOLFSSH_USERAUTH_REJECTED);
assert(auth(fresh(1),&k)==WOLFSSH_USERAUTH_REJECTED);
assert(s_counters.handshake_throttle_rejections==1 && s_counters.authentication_throttle_rejections==1 && s_counters.authentication_probe_rejections==1);
assert(cleanup_slot(&s_slots[0]) && cleanup_slot(&s_slots[1]));
now_us=9999999; accepts_remaining=1; accept_connections(); assert(allocations==6);
now_us=10000000; allocation_fail=false; accepts_remaining=2; accept_connections();
assert(allocations==7 && s_counters.handshake_admissions==1);
uint64_t count=UINT64_MAX-1; add_counter(&count,1); assert(count==UINT64_MAX);
add_counter(&count,1); assert(count==UINT64_MAX); count=1;
add_counter(&count,UINT64_MAX); assert(count==UINT64_MAX);
s_counters.authentication_attempts=UINT64_MAX;
s_counters.authentication_failures=UINT64_MAX;
ssh_slot_t *s=fresh(1); s->authentication_attempts=UINT8_MAX;
assert(!complete_authentication_attempt(s,true));
assert(s->authentication_attempts==UINT8_MAX && s_counters.authentication_attempts==UINT64_MAX && s_counters.authentication_failures==UINT64_MAX);
puts("PASS actual accept loop capacity/budget, failed allocations consume admission, restart/clear retain all pools, saturation");
}
static void buffers(void) {
boot(); ssh_slot_t *s=fresh(0); s->route=SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE;
memcpy(s->rx_buffer,payload,sizeof(payload)); s->rx_length=sizeof(payload);
feed_ok=true; feed_consumed=2; assert(flush_admin_input(s,0));
zero_bytes(s->rx_buffer,2); assert(s->rx_offset==2 && !memcmp(s->rx_buffer+2,payload+2,6));
feed_ok=false; feed_consumed=2; assert(flush_admin_input(s,0));
zero_bytes(s->rx_buffer,4); assert(s->rx_offset==4 && !memcmp(s->rx_buffer+4,payload+4,4));
assert(s_counters.rx_accepted_bytes==4 && !s_counters.admin_console_input_rejections);
feed_consumed=0; assert(flush_admin_input(s,0)); assert(s->rx_offset==4 && s_counters.admin_console_input_rejections==1);
feed_ok=true; feed_consumed=4; assert(flush_admin_input(s,0));
zero_bytes(s->rx_buffer,sizeof(payload)); assert(!s->rx_length && !s->rx_offset);
const int retry[]={0,WS_WANT_READ,WS_WANT_WRITE,WS_REKEYING,WS_WINDOW_FULL,WS_CHAN_RXD};
for(unsigned route=SSH_TRANSPORT_ROUTE_BROKER;route<=SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE;++route) {
s->route=route; memcpy(s->tx_buffer,payload,sizeof(payload)); s->tx_length=sizeof(payload); s->tx_offset=0;
for(size_t i=0;i<sizeof(retry)/sizeof(retry[0]);++i) {
send_result=retry[i]; ssh_error=0; assert(flush_client_output(s));
assert(!s->tx_offset && s->tx_length==sizeof(payload) && !memcmp(s->tx_buffer,payload,sizeof(payload)));
}
send_result=3; assert(flush_client_output(s)); assert(s->tx_offset==3);
if(route==SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE) zero_bytes(s->tx_buffer,3);
else assert(!memcmp(s->tx_buffer,payload,3));
assert(!memcmp(s->tx_buffer+3,payload+3,5));
send_result=WS_ERROR; ssh_error=WS_WANT_WRITE; assert(flush_client_output(s)); assert(s->tx_offset==3);
ssh_error=WS_ERROR; assert(!flush_client_output(s)); assert(!memcmp(s->tx_buffer+3,payload+3,5));
send_result=5; assert(flush_client_output(s)); assert(!s->tx_offset && !s->tx_length);
if(route==SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE) zero_bytes(s->tx_buffer,sizeof(payload));
else assert(!memcmp(s->tx_buffer,payload,sizeof(payload)));
}
for(size_t i=0;i<sizeof(retry)/sizeof(retry[0]);++i) {
read_result=retry[i]; ssh_error=0; assert(receive_admin_input(s,0)); assert(!s->rx_length);
assert(receive_client_input(s));
}
read_result=WS_ERROR; ssh_error=WS_ERROR; assert(!receive_admin_input(s,0)); assert(!receive_client_input(s));
read_result=sizeof(payload); feed_consumed=3; feed_ok=false; assert(receive_admin_input(s,0));
zero_bytes(s->rx_buffer,3); assert(s->rx_offset==3 && !memcmp(s->rx_buffer+3,payload+3,5));
feed_consumed=5; assert(receive_admin_input(s,0)); zero_bytes(s->rx_buffer,sizeof(payload));
s->route=SSH_TRANSPORT_ROUTE_BROKER; s->writer=true; broker_accepted=3;
assert(receive_client_input(s)); assert(s->rx_offset==3 && !memcmp(s->rx_buffer,payload,sizeof(payload)));
broker_error=ESP_ERR_TIMEOUT; broker_accepted=0; assert(flush_client_input(s)); assert(s->rx_offset==3);
broker_error=ESP_OK; broker_accepted=5; assert(flush_client_input(s)); assert(!s->rx_length && !memcmp(s->rx_buffer,payload,sizeof(payload)));
puts("PASS consumed admin RX including false+consumed, retry/partial TX wiping, binary serial buffers unchanged");
}
static void retirement(void) {
boot(); ssh_slot_t *s=&s_slots[0]; memset(s,0xa5,sizeof(*s));
s->generation=0x12345678; s->socket_fd=42; s->ssh=(WOLFSSH *)&s_lock;
s->route=SSH_TRANSPORT_ROUTE_ADMIN_CONSOLE; s->broker_client_id=7;
broker_error=ESP_FAIL; assert(!cleanup_slot(s));
assert(s->state==SSH_TRANSPORT_SESSION_CLOSING && s->generation==0x12345678);
assert(s->socket_fd==-1 && s->ssh==NULL && frees==1);
broker_error=ESP_OK; assert(cleanup_slot(s));
assert(wipe_address==s && wipe_size==sizeof(*s));
ssh_slot_t expected; memset(&expected,0,sizeof(expected));
expected.state=SSH_TRANSPORT_SESSION_FREE; expected.generation=0x12345678; expected.socket_fd=-1;
assert(!memcmp(s,&expected,sizeof(*s)) && frees==1 && close_calls==1);
assert(cleanup_slot(s)); assert(!memcmp(s,&expected,sizeof(*s)));
puts("PASS retirement whole-slot wipe, exact generation and fd=-1 sentinel, deferred broker cleanup");
}
int main(void) {
passwords(); signed_keys(); fences_and_probes(); admissions_lifecycle(); buffers(); retirement();
puts("PASS SSH transport extracted-production host suite");
return 0;
}