ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
auth.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file auth.h
6 * @brief RFC 4252 user authentication.
7 *
8 * After NEWKEYS the client requests the "ssh-userauth" service; the server accepts it and then
9 * drives SSH_MSG_USERAUTH_REQUEST exchanges until a method succeeds or the connection is dropped.
10 * The "none" method is always answered with a failure that advertises what may continue
11 * (RFC 4252 sec 5.2), which is how a client discovers the supported methods.
12 */
13
14#ifndef PROTOCORE_AUTH_AUTH_H
15#define PROTOCORE_AUTH_AUTH_H
16
18
20
21/** @brief Parsed SSH_MSG_USERAUTH_REQUEST. */
22typedef struct
23{
24 char user[SSH_AUTH_USER_MAX]; ///< User name, null-terminated.
25 char service[32]; ///< Requested service ("ssh-connection").
26 char method[24]; ///< Method name ("none", "password", "publickey", "keyboard-interactive").
27 char password[SSH_AUTH_PASS_MAX]; ///< Password (method == "password"); the old one on a change.
28 char new_password[SSH_AUTH_PASS_MAX]; ///< New password when is_pw_change (RFC 4252 sec 8).
29 proto_bool is_password; ///< True if a password method-request was parsed.
30 proto_bool is_pw_change; ///< True if the request set the change-password flag.
31 proto_bool is_kbdint; ///< True if a keyboard-interactive method-request was parsed (RFC 4256).
32
33 // publickey method (RFC 4252 §7)
34 proto_bool is_pubkey; ///< True if a publickey method-request was parsed.
35 proto_bool has_signature; ///< True if the request carried a signature.
36 char pk_algo[SSH_AUTH_ALGO_MAX]; ///< Public-key algorithm name.
37 const uint8_t *pk_blob; ///< Public-key blob (points into the payload).
38 uint32_t pk_blob_len; ///< Length of pk_blob.
39 const uint8_t *signature; ///< Raw signature bytes (points into the payload).
40 uint32_t signature_len; ///< Length of signature.
41 const uint8_t *signed_prefix; ///< Bytes of the request that the signature covers.
42 size_t signed_prefix_len; ///< Length of signed_prefix (payload up to the signature).
44
45/**
46 * @brief Application callback that validates a username/password pair.
47 * @return true to accept the credentials.
48 */
49typedef proto_bool (*SshPasswordCb)(const char *user, const char *password);
50
51/** @brief Install the password-verification callback (nullptr → all fail). */
52
53/**
54 * @brief Application callback that STARTS a password change (RFC 4252 sec 8) for slot @p slot.
55 *
56 * The application owns the store and its encryption, and a store can be slow (flash). This callback
57 * must not block: it copies what it needs, kicks off its own work, and returns. When the change has
58 * finished (or failed to verify @p old_password) the application reports the outcome with
59 * protocore_ssh_auth_pw_change_report(); the reply to the client is deferred until then, so the SSH worker
60 * is never held on the store. @p old_password / @p new_password are wiped once this returns.
61 */
62typedef void (*SshPasswordChangeCb)(uint8_t slot, const char *user, const char *old_password, const char *new_password);
63
64/** @brief A slot's password-change state: idle, handed to the application, or finished either way. */
72
73/** @brief Install the password-change start callback (nullptr → change requests are refused busy). */
74
75/**
76 * @brief Report the outcome of the change the start callback began for slot @p slot.
77 *
78 * @p ok true when the old password verified and the new one was stored. Moves the slot to OK or
79 * FAIL, which the next poll drains into the deferred reply. A no-op when no change is in flight
80 * (a stale report after the connection left).
81 */
82
83/**
84 * @brief Take slot @p i's finished change outcome, clearing it back to NONE.
85 *
86 * @return OK or FAIL once the application has reported, NONE while idle or still in flight.
87 */
88/**
89 * @brief Write the "publickey" USERAUTH_REQUEST body that RFC 4252 sec 7 signs and sends.
90 *
91 * sec 7 gives one field order and uses it twice: the signature covers
92 *
93 * string session identifier
94 * byte SSH_MSG_USERAUTH_REQUEST
95 * string user name
96 * string service name
97 * string "publickey"
98 * boolean TRUE
99 * string public key algorithm name
100 * string public key to be used for authentication
101 *
102 * and the request that carries the signature is the same bytes from SSH_MSG_USERAUTH_REQUEST on,
103 * with the signature appended. Writing it in one place is what keeps the two identical - a
104 * verifier hashes what arrived, so any divergence here is a signature that never validates.
105 *
106 * @param w Span written into.
107 * @param sid Session identifier, or null to start at SSH_MSG_USERAUTH_REQUEST (the request form).
108 * @param sid_len Length of @p sid; ignored when @p sid is null.
109 * @param user User name.
110 * @param service Service name, normally "ssh-connection".
111 * @param pk_algo Public key algorithm name.
112 * @param pk_blob Public key blob.
113 * @param pk_len Length of @p pk_blob.
114 */
115
116/**
117 * @brief True once slot @p i has gone too long without authenticating (RFC 4252 sec 4).
118 *
119 * "The server SHOULD have a timeout for authentication and disconnect if the authentication has not
120 * been accepted within the timeout period. The RECOMMENDED timeout period is 10 minutes." The clock
121 * starts on the first call for a slot and stops once authentication completes; SSH_AUTH_TIMEOUT_MS
122 * of 0 disables it. Poll it, and disconnect when it answers true.
123 */
124
126
127/**
128 * @brief Drop a parked password change without replying to it.
129 *
130 * RFC 4252 sec 5.1 sends SSH_MSG_USERAUTH_SUCCESS once, so a change whose answer arrives after some
131 * other method already succeeded is discarded rather than answered.
132 */
133
134/**
135 * @brief Drop slot @p i's half-finished authentication state and wipe its username.
136 *
137 * A keyboard-interactive exchange is armed by the USERAUTH_REQUEST and consumed by the matching
138 * INFO_RESPONSE. A connection that leaves between the two ends here.
139 */
140
141/**
142 * @brief Application callback that decides whether a public key is authorized
143 * for @p user. @p blob is the "ssh-rsa" public-key blob.
144 * @return true if the key may authenticate this user.
145 */
146typedef proto_bool (*SshPubkeyCb)(const char *user, const uint8_t *blob, size_t blob_len);
147
148/** @brief Install the publickey-authorization callback (nullptr → all fail). */
149
150/**
151 * @brief Parse an SSH_MSG_USERAUTH_REQUEST into @p req.
152 * @return 0 on success, -1 if malformed.
153 */
154
155/** @brief Build SSH_MSG_USERAUTH_FAILURE advertising "password". */
156
157/** @brief Build SSH_MSG_USERAUTH_SUCCESS. */
158
159/**
160 * @brief Handle a USERAUTH_REQUEST end-to-end for slot @p i.
161 *
162 * Parses the request, checks "password" credentials via the installed callback,
163 * and writes either USERAUTH_SUCCESS or USERAUTH_FAILURE to @p out. On success
164 * the session is marked authenticated and advanced to the connection phase.
165 *
166 * @return 0 if a response was produced (check the message type), -1 on parse
167 * error.
168 */
169
170#if PROTOCORE_ENABLE_SSH_KEYBOARD_INTERACTIVE
171/**
172 * @brief Handle an SSH_MSG_USERAUTH_INFO_RESPONSE (RFC 4256 §3.4) for slot @p i.
173 *
174 * The response to the single "Password:" prompt this server sends is verified through the installed
175 * password callback (keyboard-interactive is the challenge-response face of password auth here). Writes
176 * USERAUTH_SUCCESS or USERAUTH_FAILURE to @p out. Only valid while a keyboard-interactive exchange is
177 * pending for the slot (a prior USERAUTH_REQUEST selected it); otherwise fails.
178 *
179 * @return 0 if a response was produced (check the message type), -1 on parse error / no exchange pending.
180 */
181#endif
182
183/** @brief Dispatch messages 50 to 79; 80 and above need authentication (RFC 4252 sec 6). */
184
185/** @brief Send the reply a finished password change (RFC 4252 sec 8) deferred on slot @p i. */
186
187/** @brief RFC 4252 sec 5: the body of one userauth message. */
188typedef struct
189{
190 const uint8_t *payload; ///< the message body
191 size_t len; ///< how many bytes it has
193
194/** @brief Where a reply is written, either as a buffer or as a span. */
195typedef struct
196{
197 uint8_t *out; ///< where a reply is written
198 size_t out_len; ///< what was written
199 size_t cap; ///< how much room it has
200 mmgr_span *w; ///< the span a publickey request is built in
202
203/** @brief RFC 4252 sec 5 / sec 7: the names one USERAUTH_REQUEST carries, and the key it offers. */
204typedef struct
205{
206 const uint8_t *sid; ///< the session identifier it signs over, or NULL for the request form
207 size_t sid_len; ///< its length; ignored when sid is NULL
208 const char *user; ///< the user name
209 const char *service; ///< the service name, normally "ssh-connection"
210 const char *pk_algo; ///< the public key algorithm name
211 const uint8_t *pk_blob; ///< the public key blob
212 size_t pk_len; ///< its length
214
215/** @brief RFC 4252 sec 7 / sec 8: what an attempt of each method is checked against. */
216typedef struct
217{
218 SshPasswordCb password_cb; ///< what a password attempt is checked against
219 SshPasswordChangeCb password_change_cb; ///< what a change request is handed to
220 SshPubkeyCb pubkey_cb; ///< what a public key is checked against
221} SshAuthCbs;
222
223/**
224 * @brief The SSH authentication protocol (RFC 4252): what a slot must satisfy before a service runs.
225 *
226 * A caller sets the members a call takes, invokes it through ::SshAuth, and reads the outcome off
227 * the same handle.
228 *
229 * @var SshAuthNs::slot the SSH slot a call acts on
230 * @var SshAuthNs::msg_type the message a dispatch routes (sec 6: 50-79 here, 80+ need auth)
231 * @var SshAuthNs::msg sec 5 the message body a dispatch is given
232 * @var SshAuthNs::out_args where a reply is written
233 * @var SshAuthNs::req where a parse lands the request (sec 5)
234 * @var SshAuthNs::partial the failure is a partial success (sec 5.1)
235 * @var SshAuthNs::userauth sec 5 / sec 7 the fields one request names
236 * @var SshAuthNs::cbs what an attempt of each method is checked against
237 * @var SshAuthNs::ok a call's true/false outcome
238 * @var SshAuthNs::i32 a call's signed outcome
239 * @var SshAuthNs::set_password_cb install the password check
240 * @var SshAuthNs::set_password_change_cb install the change handler
241 * @var SshAuthNs::set_pubkey_cb install the public key check
242 * @var SshAuthNs::pw_change_report report a finished change back to the slot
243 * @var SshAuthNs::pw_change_clear drop a pending change on the slot
244 * @var SshAuthNs::passwd_change_reply send the reply a finished change deferred
245 * @var SshAuthNs::write_publickey_request build a publickey request into out_args.w (sec 7)
246 * @var SshAuthNs::timed_out the slot has gone too long unauthenticated (sec 4)
247 * @var SshAuthNs::reset clear the slot's authentication state
248 * @var SshAuthNs::parse_request parse a USERAUTH_REQUEST into req (sec 5)
249 * @var SshAuthNs::build_failure write USERAUTH_FAILURE (sec 5.1)
250 * @var SshAuthNs::build_success write USERAUTH_SUCCESS (sec 5.1)
251 * @var SshAuthNs::handle_request answer a USERAUTH_REQUEST
252 * @var SshAuthNs::handle_info_response answer a USERAUTH_INFO_RESPONSE (RFC 4256 sec 3.4)
253 * @var SshAuthNs::dispatch route messages 50 to 79
254 */
255typedef struct
256{
257 uint8_t slot; ///< the SSH slot a call acts on
258 uint8_t msg_type; ///< the message a dispatch routes
259 SshAuthReq *req; ///< where a parse lands the request (sec 5)
260 proto_bool partial; ///< the failure is a partial success (sec 5.1)
261 SshAuthMsgArgs msg; ///< sec 5 the message body a dispatch is given
262 SshAuthOutArgs out_args; ///< where a reply is written
263 SshUserauthArgs userauth; ///< sec 5 / sec 7 the fields one request names
264 SshAuthCbs cbs; ///< what an attempt is checked against
266 int i32;
267#if PROTOCORE_ENABLE_SSH_KEYBOARD_INTERACTIVE
268#endif
270
271/** @brief The operands and the outcome. */
272extern SshAuthVars SshAuthV;
273
274/** @brief The entries. */
275typedef struct
276{
277 void (*const set_password_cb)(uint8_t *work);
278 void (*const set_password_change_cb)(uint8_t *work);
279 void (*const set_pubkey_cb)(uint8_t *work);
280 void (*const pw_change_report)(uint8_t *work);
281 void (*const pw_change_clear)(uint8_t *work);
282 void (*const passwd_change_reply)(uint8_t *work);
283 void (*const write_publickey_request)(uint8_t *work);
284 void (*const timed_out)(uint8_t *work);
285 void (*const reset)(uint8_t *work);
286 void (*const parse_request)(uint8_t *work);
287 void (*const build_failure)(uint8_t *work);
288 void (*const build_success)(uint8_t *work);
289 void (*const handle_request)(uint8_t *work);
290 void (*const handle_info_response)(uint8_t *work);
291 void (*const dispatch)(uint8_t *work);
292} SshAuthNs;
293
294// What the table binds, defined once in the .c and taking one parameter each: everything
295// else an entry needs is an operand in SshAuthV or a region of the borrow at a fixed offset.
304void protocore_ssh_auth_reset(uint8_t *work);
309#if PROTOCORE_ENABLE_SSH_KEYBOARD_INTERACTIVE
310void protocore_ssh_auth_handle_info_response(uint8_t *work);
311#endif
312void protocore_ssh_auth_dispatch(uint8_t *work);
313
314// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
315// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
316// `SshAuth.set_password_cb(work)` resolves to a named function and becomes a DIRECT call. An extern table
317// leaves the call indirect and the symbol live at every level, -O2 -flto included.
318static const SshAuthNs SshAuth __attribute__((unused)) = {
320 .set_password_change_cb = protocore_ssh_auth_set_password_change_cb,
321 .set_pubkey_cb = protocore_ssh_auth_set_pubkey_cb,
322 .pw_change_report = protocore_ssh_auth_pw_change_report,
323 .pw_change_clear = protocore_ssh_auth_pw_change_clear,
324 .passwd_change_reply = protocore_ssh_auth_passwd_change_reply,
325 .write_publickey_request = protocore_ssh_auth_write_publickey_request,
326 .timed_out = protocore_ssh_auth_timed_out,
328 .parse_request = protocore_ssh_auth_parse_request,
329 .build_failure = protocore_ssh_auth_build_failure,
330 .build_success = protocore_ssh_auth_build_success,
331 .handle_request = protocore_ssh_auth_handle_request,
332#if PROTOCORE_ENABLE_SSH_KEYBOARD_INTERACTIVE
333 .handle_info_response = protocore_ssh_auth_handle_info_response,
334#endif
335 .dispatch = protocore_ssh_auth_dispatch,
336};
337
338/**
339 * @brief The PROTOCORE_SSH_AUTH_BORROW bytes this module's state lives in.
340 *
341 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
342 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
343 * walks, so the state lasts the life of the program.
344 *
345 * @return the span.
346 */
348
350
351#endif // PROTOCORE_AUTH_AUTH_H
#define SSH_AUTH_PASS_MAX
Max stored password length.
#define SSH_AUTH_ALGO_MAX
Max stored public-key algorithm name ("rsa-sha2-512", "ecdsa-sha2-nistp256", RFC 4253 sec 6....
#define SSH_AUTH_USER_MAX
Max stored user name (RFC 4252 imposes no limit; we cap for BSS).
Root infrastructure: fixed widths, serializers, opcodes and sizes, for every layer above.
void protocore_ssh_auth_build_success(uint8_t *work)
proto_bool(* SshPubkeyCb)(const char *user, const uint8_t *blob, size_t blob_len)
Drop a parked password change without replying to it.
Definition auth.h:146
void protocore_ssh_auth_timed_out(uint8_t *work)
SshPwChange
A slot's password-change state: idle, handed to the application, or finished either way.
Definition auth.h:66
@ PROTOCORE_SSH_PW_CHANGE_NONE
Definition auth.h:67
@ PROTOCORE_SSH_PW_CHANGE_FAIL
Definition auth.h:70
@ PROTOCORE_SSH_PW_CHANGE_BUSY
Definition auth.h:68
@ PROTOCORE_SSH_PW_CHANGE_OK
Definition auth.h:69
SshPwChange protocore_ssh_auth_pw_change_take(uint8_t i)
Install the password-change start callback (nullptr → change requests are refused busy).
proto_bool(* SshPasswordCb)(const char *user, const char *password)
Application callback that validates a username/password pair.
Definition auth.h:49
void protocore_ssh_auth_pw_change_report(uint8_t *work)
void protocore_ssh_auth_passwd_change_reply(uint8_t *work)
void protocore_ssh_auth_set_password_change_cb(uint8_t *work)
void protocore_ssh_auth_handle_request(uint8_t *work)
void protocore_ssh_auth_write_publickey_request(uint8_t *work)
void protocore_ssh_auth_parse_request(uint8_t *work)
void protocore_ssh_auth_set_pubkey_cb(uint8_t *work)
uint8_t * protocore_ssh_auth_span(void)
The PROTOCORE_SSH_AUTH_BORROW bytes this module's state lives in.
void protocore_ssh_auth_build_failure(uint8_t *work)
SshAuthVars SshAuthV
The operands and the outcome.
void(* SshPasswordChangeCb)(uint8_t slot, const char *user, const char *old_password, const char *new_password)
Install the password-verification callback (nullptr → all fail).
Definition auth.h:62
void protocore_ssh_auth_set_password_cb(uint8_t *work)
void protocore_ssh_auth_pw_change_clear(uint8_t *work)
void protocore_ssh_auth_dispatch(uint8_t *work)
void protocore_ssh_auth_reset(uint8_t *work)
RFC 4252 sec 7 / sec 8: what an attempt of each method is checked against.
Definition auth.h:217
SshPubkeyCb pubkey_cb
what a public key is checked against
Definition auth.h:220
SshPasswordCb password_cb
what a password attempt is checked against
Definition auth.h:218
SshPasswordChangeCb password_change_cb
what a change request is handed to
Definition auth.h:219
Install the publickey-authorization callback (nullptr → all fail).
Definition auth.h:189
size_t len
how many bytes it has
Definition auth.h:191
const uint8_t * payload
the message body
Definition auth.h:190
The entries.
Definition auth.h:276
void(*const set_password_cb)(uint8_t *work)
Definition auth.h:277
Where a reply is written, either as a buffer or as a span.
Definition auth.h:196
size_t out_len
what was written
Definition auth.h:198
size_t cap
how much room it has
Definition auth.h:199
uint8_t * out
where a reply is written
Definition auth.h:197
mmgr_span * w
the span a publickey request is built in
Definition auth.h:200
Parsed SSH_MSG_USERAUTH_REQUEST.
Definition auth.h:23
const uint8_t * signature
Raw signature bytes (points into the payload).
Definition auth.h:39
proto_bool is_pw_change
True if the request set the change-password flag.
Definition auth.h:30
proto_bool is_password
True if a password method-request was parsed.
Definition auth.h:29
proto_bool is_pubkey
True if a publickey method-request was parsed.
Definition auth.h:34
proto_bool is_kbdint
True if a keyboard-interactive method-request was parsed (RFC 4256).
Definition auth.h:31
uint32_t pk_blob_len
Length of pk_blob.
Definition auth.h:38
size_t signed_prefix_len
Length of signed_prefix (payload up to the signature).
Definition auth.h:42
const uint8_t * pk_blob
Public-key blob (points into the payload).
Definition auth.h:37
const uint8_t * signed_prefix
Bytes of the request that the signature covers.
Definition auth.h:41
proto_bool has_signature
True if the request carried a signature.
Definition auth.h:35
uint32_t signature_len
Length of signature.
Definition auth.h:40
SshAuthMsgArgs msg
sec 5 the message body a dispatch is given
Definition auth.h:261
int i32
Definition auth.h:266
uint8_t slot
the SSH slot a call acts on
Definition auth.h:257
SshAuthReq * req
where a parse lands the request (sec 5)
Definition auth.h:259
uint8_t msg_type
the message a dispatch routes
Definition auth.h:258
proto_bool ok
Definition auth.h:265
SshAuthOutArgs out_args
where a reply is written
Definition auth.h:262
SshAuthCbs cbs
what an attempt is checked against
Definition auth.h:264
SshUserauthArgs userauth
sec 5 / sec 7 the fields one request names
Definition auth.h:263
proto_bool partial
the failure is a partial success (sec 5.1)
Definition auth.h:260
RFC 4252 sec 5 / sec 7: the names one USERAUTH_REQUEST carries, and the key it offers.
Definition auth.h:205
const char * user
the user name
Definition auth.h:208
const char * service
the service name, normally "ssh-connection"
Definition auth.h:209
size_t pk_len
its length
Definition auth.h:212
size_t sid_len
its length; ignored when sid is NULL
Definition auth.h:207
const uint8_t * pk_blob
the public key blob
Definition auth.h:211
const char * pk_algo
the public key algorithm name
Definition auth.h:210
const uint8_t * sid
the session identifier it signs over, or NULL for the request form
Definition auth.h:206
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97