30#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
59static bool read_string(
const uint8_t *p,
size_t len,
size_t *off,
char *out,
size_t outcap)
65 uint32_t n = ((uint32_t)p[*off] << 24) | ((uint32_t)p[*off + 1] << 16) | ((uint32_t)p[*off + 2] << 8) |
66 (uint32_t)p[*off + 3];
76 memcpy(out, p + *off, n);
82static void put_u32(uint8_t *p, uint32_t v)
84 p[0] = (uint8_t)(v >> 24);
85 p[1] = (uint8_t)(v >> 16);
86 p[2] = (uint8_t)(v >> 8);
91static bool read_string_ref(
const uint8_t *p,
size_t len,
size_t *off,
const uint8_t **out, uint32_t *slen)
97 uint32_t n = ((uint32_t)p[*off] << 24) | ((uint32_t)p[*off + 1] << 16) | ((uint32_t)p[*off + 2] << 8) |
98 (uint32_t)p[*off + 3];
111static bool mpint_to_fixed(
const uint8_t *m, uint32_t mlen, uint8_t *out,
size_t outlen)
114 while (off < mlen && m[off] == 0)
118 uint32_t vlen = mlen - off;
123 memset(out, 0, outlen);
124 memcpy(out + (outlen - vlen), m + off, vlen);
129static bool parse_ssh_rsa_blob(
const uint8_t *blob, uint32_t blen, uint8_t n_be[
PC_RSA_KEY_BYTES], uint8_t e_be[4])
134 if (!read_string_ref(blob, blen, &off, &type, &type_len))
138 if (type_len != 7 || memcmp(type,
"ssh-rsa", 7) != 0)
145 if (!read_string_ref(blob, blen, &off, &e_mp, &e_len))
149 if (!mpint_to_fixed(e_mp, e_len, e_be, 4))
156 if (!read_string_ref(blob, blen, &off, &n_mp, &n_len))
169static bool parse_pc_ed25519_blob(
const uint8_t *blob, uint32_t blen, uint8_t pub[32])
176 if (!read_string_ref(blob, blen, &off, &type, &type_len))
180 if (type_len != 11 || memcmp(type,
"ssh-ed25519", 11) != 0)
186 if (!read_string_ref(blob, blen, &off, &pk, &pk_len))
200static bool parse_pc_ecdsa_blob(
const uint8_t *blob, uint32_t blen, uint8_t pub[
PC_ECDSA_P256_PUB_LEN])
206 if (!read_string_ref(blob, blen, &off, &type, &type_len))
210 if (type_len != 19 || memcmp(type,
"ecdsa-sha2-nistp256", 19) != 0)
214 const uint8_t *curve;
216 if (!read_string_ref(blob, blen, &off, &curve, &curve_len))
220 if (curve_len != 8 || memcmp(curve,
"nistp256", 8) != 0)
226 if (!read_string_ref(blob, blen, &off, &q, &q_len))
246 if (!read_string_ref(sig, slen, &off, &r, &r_len) || !read_string_ref(sig, slen, &off, &s, &s_len))
267 if (!read_string(payload, len, &off, svc,
sizeof(svc)))
271 if (strcmp(svc,
"ssh-userauth") != 0)
277 static const char name[] =
"ssh-userauth";
278 uint32_t nl = (uint32_t)(
sizeof(name) - 1);
279 if (cap < 1 + 4 + nl)
284 put_u32(out + 1, nl);
285 memcpy(out + 5, name, nl);
296 memset(req, 0,
sizeof(*req));
303 if (!read_string(payload, len, &off, req->
user,
sizeof(req->
user)))
307 if (!read_string(payload, len, &off, req->
service,
sizeof(req->
service)))
311 if (!read_string(payload, len, &off, req->
method,
sizeof(req->
method)))
316 if (strcmp(req->
method,
"password") == 0)
330 else if (strcmp(req->
method,
"publickey") == 0)
338 if (!read_string(payload, len, &off, req->
pk_algo,
sizeof(req->
pk_algo)))
353 const uint8_t *sigblob;
354 uint32_t sigblob_len;
355 if (!read_string_ref(payload, len, &off, &sigblob, &sigblob_len))
361 const uint8_t *salgo;
363 if (!read_string_ref(sigblob, sigblob_len, &so, &salgo, &salgo_len))
374#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
375 else if (strcmp(req->
method,
"keyboard-interactive") == 0)
392#if PC_SSH_ALLOW_PASSWORD
393#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
394 static const char methods[] =
"publickey,password,keyboard-interactive";
396 static const char methods[] =
"publickey,password";
399 static const char methods[] =
"publickey";
401 uint32_t ml = (uint32_t)(
sizeof(methods) - 1);
402 if (cap < 1 + 4 + ml + 1)
407 put_u32(out + 1, ml);
408 memcpy(out + 5, methods, ml);
409 out[5 + ml] = partial ? 1 : 0;
410 *out_len = 5 + ml + 1;
427static int build_pk_ok(
const SshAuthReq *req, uint8_t *out,
size_t *out_len,
size_t cap)
429 uint32_t al = (uint32_t)strnlen(req->
pk_algo,
sizeof(req->
pk_algo));
430 if (cap < (
size_t)1 + 4 + al + 4 + req->
pk_blob_len)
436 put_u32(out + o, al);
438 memcpy(out + o, req->
pk_algo, al);
448#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
451static int build_info_request(uint8_t *out,
size_t *out_len,
size_t cap)
453 static const char prompt[] =
"Password: ";
454 const uint32_t pl = (uint32_t)(
sizeof(prompt) - 1);
455 const size_t need = 1 + 4 + 4 + 4 + 4 + 4 + pl + 1;
470 put_u32(out + o, pl);
472 memcpy(out + o, prompt, pl);
486static int pc_ssh_auth_handle_pubkey(uint8_t i,
const SshAuthReq *req, uint8_t *out,
size_t *out_len,
size_t cap)
495 "ecdsa-sha2-nistp256",
503 const pc_span &n_be = n_be_b.span();
532 return build_pk_ok(req, out, out_len, cap);
548 put_u32(signed_data.
buf + sd, (uint32_t)sid_len);
599 return pc_ssh_auth_handle_pubkey(i, &req, out, out_len, cap);
602#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
610 s_auth.ki[i].pending =
true;
611 size_t ul = strnlen(req.
user,
sizeof(s_auth.ki[i].user) - 1);
612 memcpy(s_auth.ki[i].user, req.
user, ul);
613 s_auth.ki[i].user[ul] =
'\0';
614 return build_info_request(out, out_len, cap);
620#if PC_SSH_ALLOW_PASSWORD
638#if PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
639int pc_ssh_auth_handle_info_response(uint8_t i,
const uint8_t *payload,
size_t len, uint8_t *out,
size_t *out_len,
646 if (!s_auth.ki[i].pending)
650 s_auth.ki[i].pending =
false;
663 uint32_t nr = ((uint32_t)payload[off] << 24) | ((uint32_t)payload[off + 1] << 16) |
664 ((uint32_t)payload[off + 2] << 8) | (uint32_t)payload[off + 3];
669 if (nr == 1 && read_string(payload, len, &off, resp,
sizeof(resp)))
671 ok = s_auth.
pw_cb && s_auth.
pw_cb(s_auth.ki[i].user, resp);
675 pc_secure_wipe(resp,
sizeof(resp));
676 pc_secure_wipe(s_auth.ki[i].user,
sizeof(s_auth.ki[i].user));
A scratch borrow whose acquire and release are one call each.
Constant-time comparison for secret-dependent checks.
bool pc_ecdsa_p256_verify(const uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t *msg, size_t mlen, const uint8_t sig[PC_ECDSA_P256_SIG_LEN])
Verify a P-256 ECDSA signature (SHA-256) against an uncompressed public point.
NIST P-256 primitives for SSH: ECDSA signatures and ECDH (RFC 5656 / FIPS 186-4).
#define PC_ECDSA_P256_COORD_LEN
P-256 coordinate length (one of X, Y).
#define PC_ECDSA_P256_SIG_LEN
Raw ECDSA signature length: r || s (32 + 32, big-endian).
#define PC_ECDSA_P256_PUB_LEN
P-256 uncompressed public point length: 0x04 || X || Y.
bool pc_ed25519_verify(const uint8_t pub[32], const uint8_t *msg, size_t mlen, const uint8_t sig[64])
Ed25519 signatures (RFC 8032) for ssh-ed25519 host keys + client auth.
#define SSH_AUTH_PASS_MAX
Max stored password length.
#define SSH_AUTH_USER_MAX
Max stored user name (RFC 4252 imposes no limit; we cap for BSS).
#define SSH_PKT_BUF_SIZE
Packet assembly buffer per SSH connection (bytes).
int pc_rsa_verify(const uint8_t n_be[PC_RSA_KEY_BYTES], const uint8_t e_be4[4], const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len, pc_rsa_hash hash)
Verify an RSA-2048 PKCS#1 v1.5 signature over msg.
pc_rsa_hash
Hash algorithm selecting the RSA signature scheme (RFC 8017 §9.2).
@ SHA256
RSASSA-PKCS1-v1.5 with SHA-256.
@ SHA512
RSASSA-PKCS1-v1.5 with SHA-512.
#define PC_RSA_KEY_BYTES
RSA modulus / signature size in bytes (RSA-2048).
pc_span pc_scratch_span(size_t n, size_t align)
Borrow n bytes as a span whose capacity is bound to the allocation.
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
Secure pool accessor - borrows that hold key material.
bool pc_span_ok(const pc_span &s)
True when the span refers to real storage and every write so far has fit.
void pc_ssh_auth_set_pubkey_cb(SshPubkeyCb cb)
Install the publickey-authorization callback (nullptr → all fail).
int pc_ssh_auth_build_failure(uint8_t *out, size_t *out_len, size_t cap, bool partial)
Build SSH_MSG_USERAUTH_FAILURE advertising "password".
int pc_ssh_auth_build_success(uint8_t *out, size_t *out_len, size_t cap)
Build SSH_MSG_USERAUTH_SUCCESS.
void pc_ssh_auth_set_password_cb(SshPasswordCb cb)
Install the password-verification callback (nullptr → all fail).
int pc_ssh_auth_parse_request(const uint8_t *payload, size_t len, SshAuthReq *req)
Parse an SSH_MSG_USERAUTH_REQUEST into req.
int pc_ssh_auth_handle_service_request(const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_SERVICE_REQUEST; emit SERVICE_ACCEPT for ssh-userauth.
int pc_ssh_auth_handle_request(uint8_t i, const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle a USERAUTH_REQUEST end-to-end for slot i.
SSH user-authentication layer (RFC 4252).
bool(* SshPasswordCb)(const char *user, const char *password)
Application callback that validates a username/password pair.
bool(* SshPubkeyCb)(const char *user, const uint8_t *blob, size_t blob_len)
Application callback that decides whether a public key is authorized for user. blob is the "ssh-rsa" ...
#define SSH_KEXHASH_MAX_LEN
longest exchange-hash / session_id (SHA-512)
SSH binary packet protocol: framing, AES-256-CTR encryption, HMAC-SHA2-256 integrity,...
#define SSH_MSG_USERAUTH_INFO_REQUEST
#define SSH_MSG_USERAUTH_INFO_RESPONSE
#define SSH_MSG_USERAUTH_REQUEST
#define SSH_MSG_USERAUTH_PK_OK
#define SSH_MSG_SERVICE_ACCEPT
#define SSH_MSG_SERVICE_REQUEST
#define SSH_MSG_USERAUTH_SUCCESS
#define SSH_MSG_USERAUTH_FAILURE
SSH RSA host-key layer: NVS-backed host key, host-key signing, and "ssh-rsa" blob encoding.
SshSession ssh_sess[MAX_SSH_CONNS]
Static pool of SSH session state (BSS), one per SSH slot.
SSH transport-layer protocol state machine (RFC 4253).
@ SSH_PHASE_OPEN
Authenticated; connection/channel protocol active.
Parsed SSH_MSG_USERAUTH_REQUEST.
char method[24]
Method name ("none", "password", "publickey", "keyboard-interactive").
const uint8_t * signature
Raw signature bytes (points into the payload).
bool is_pubkey
True if a publickey method-request was parsed.
char pk_algo[20]
Public-key algorithm name.
bool is_password
True if a password method-request was parsed.
char password[SSH_AUTH_PASS_MAX]
Password (method == "password").
char user[SSH_AUTH_USER_MAX]
User name, null-terminated.
uint32_t pk_blob_len
Length of pk_blob.
char service[32]
Requested service ("ssh-connection").
bool is_kbdint
True if a keyboard-interactive method-request was parsed (RFC 4256).
size_t signed_prefix_len
Length of signed_prefix (payload up to the signature).
const uint8_t * pk_blob
Public-key blob (points into the payload).
const uint8_t * signed_prefix
Bytes of the request that the signature covers.
uint32_t signature_len
Length of signature.
bool has_signature
True if the request carried a signature.
uint8_t session_id_len
session_id length (the first KEX's exchange-hash length).
SshPhase phase
Current handshake phase.
bool authed
True after successful user authentication.
uint8_t session_id[SSH_KEXHASH_MAX_LEN]
H from the first KEX (RFC 4253 §7.2); 32 or 64 bytes.
A writable byte region: the storage, the capacity that belongs to it, and what has been produced into...
uint8_t * buf
first byte, or nullptr when the region could not be obtained
size_t cap
bytes writable at buf (0 whenever buf is nullptr)