ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ssh_rsa.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 ssh_rsa.h
6 * @brief SSH RSA host-key layer: NVS-backed host key, host-key signing, and "ssh-rsa" blob encoding.
7 *
8 * This is the SSH-specific wrapper around the shared RSA primitive (crypto/rsa): it owns the device's
9 * RSA-2048 host key, loaded from NVS, signs handshake data with it, and serializes the public key
10 * into the RFC 4253 / RFC 8332 "ssh-rsa" blob. The protocol-agnostic RSASSA-PKCS1-v1.5 math (verify,
11 * sign, PKCS#1 encoding, modexp) is ::Rsa in crypto/rsa, whose modular multiply is the arm a part
12 * with an RSA accelerator takes; peers' signatures are verified through the same namespace.
13 *
14 * ═══════════════════════════════════════════════════════════════════════════
15 * SECURITY MODEL - PRIVATE KEY LIFETIME
16 * ═══════════════════════════════════════════════════════════════════════════
17 * The RSA-2048 private key MUST NEVER live in static or global memory. The private exponent is a
18 * secure-pool borrow (mmgr/secure.h), the DER it was walked out of is released and wiped before the
19 * load returns, and ::RsaNs::sign wipes its own temporaries. The signature scheme is PKCS#1 v1.5
20 * (RFC 8017 §8.2), "rsa-sha2-256" / "rsa-sha2-512" (RFC 8332) - only the hash and its DigestInfo OID
21 * differ.
22 *
23 * NVS: the private key is a PKCS#8 DER in namespace "ssh_host_key" / key "priv_der"; n, e and d are
24 * walked out of it here.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_SSH_RSA_H
31#define PROTOCORE_SSH_RSA_H
32
33#include "protocore_config.h" // the entry point: protocore_types.h for the widths
34
35#if PROTOCORE_ENABLE_SSH_RSA
36
37#include "crypto/asymmetric/rsa/rsa.h" // the complete type a public struct below holds by value
38
40
41// PROTOCORE_SSH_RSA_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
42// it into its arena. A caller takes them once and passes the pointer to every call. How they
43// are carved is this module's and is never named here.
44
45/** @brief Maximum DER size for a PKCS#1 RSAPrivateKey with 2048-bit fields. */
46#define SSH_RSA_KEY_DER_MAX 1700
47
48/**
49 * @brief Key-blob type string for an RSA host key.
50 *
51 * Per RFC 8332 §3, the RSA *public-key blob* always carries the type string "ssh-rsa" - even when the
52 * negotiated *signature* algorithm is "rsa-sha2-256" / "rsa-sha2-512". Only the signature and
53 * authentication algorithm-name fields use "rsa-sha2-256"; the key blob format is unchanged from
54 * RFC 4253 §6.6.
55 */
56#define SSH_RSA_PUBKEY_ALG "ssh-rsa"
57
58/** @brief Length of SSH_RSA_PUBKEY_ALG ("ssh-rsa" = 7 bytes). */
59#define SSH_RSA_PUBKEY_ALG_LEN 7
60
61/** @brief Signature algorithm name for SHA-256 (RFC 8332). Used in the signature blob. */
62#define SSH_RSA_SIG_ALG_SHA256 "rsa-sha2-256"
63
64/** @brief Signature algorithm name for SHA-512 (RFC 8332). Used in the signature blob. */
65#define SSH_RSA_SIG_ALG_SHA512 "rsa-sha2-512"
66
67/** @brief Upper bound on the encoded "ssh-rsa" public-key blob (len+alg + mpint e + mpint n). */
68#define SSH_RSA_PUBKEY_BLOB_MAX (4 + 7 + 4 + 1 + 4 + 4 + 1 + 256)
69
70/**
71 * @brief RSA-2048 public host key parameters. Allocated in BSS; contains only n and e.
72 */
73typedef struct
74{
75 uint8_t n[PROTOCORE_RSA_KEY_BYTES]; ///< Modulus n (256 bytes, big-endian).
76 uint8_t e_bytes[4]; ///< Public exponent e (big-endian uint32).
77 proto_bool loaded; ///< True after protocore_ssh_rsa_load_pubkey() succeeds.
78} SshRsaPubKey;
79
80/** @brief What sign takes: crypto_work, msg, msg_len, hash, sig. */
81typedef struct
82{
83 uint8_t *crypto_work;
84 const uint8_t *msg;
85 size_t msg_len;
86 protocore_rsa_hash hash;
87 uint8_t *sig; ///< PROTOCORE_RSA_SIG_BYTES bytes.
88} SshRsaSignArgs;
89
90/** @brief What encode_pubkey takes: out, out_len, out_cap. */
91typedef struct
92{
93 uint8_t *out;
94 size_t *out_len;
95 size_t out_cap;
96} SshRsaEncodePubkeyArgs;
97
98/**
99 * @brief SSH RSA host-key layer: NVS-backed host key, host-key signing, and "ssh-rsa" blob encoding.
100 *
101 * A caller sets the members a call takes, invokes it through ::SshRsa with the bytes it runs
102 * out of, and reads the outcome off the same handle.
103 *
104 * SshRsa.load_pubkey(work);
105 * // SshRsa.n is what the call reports
106 *
107 * @var SshRsaNs::sign_args what sign takes: crypto_work, msg, msg_len, hash, sig
108 * @var SshRsaNs::encode_pubkey_args what encode_pubkey takes: out, out_len, out_cap
109 * @var SshRsaNs::ok a call's true/false outcome
110 * @var SshRsaNs::n 0 on success, -1 if the key is absent or malformed
111 * @var SshRsaNs::load_pubkey load the public portion of the RSA host key into ssh_host_pubkey. ...
112 * @var SshRsaNs::sign sign msg with the RSA host key (PKCS#1 v1.5, rsa-sha2-256/512)
113 * @var SshRsaNs::encode_pubkey encode ssh_host_pubkey as the RFC 4253 §6.6 "ssh-rsa" public-key ...
114 *
115 * @c work is PROTOCORE_SSH_RSA_BORROW bytes the CALLER took, at an address it knows. It is not held past the call, so
116 * nothing here aliases it. How those bytes are carved is this module's and is never named here.
117 */
118typedef struct
119{
120 SshRsaSignArgs sign_args;
121 SshRsaEncodePubkeyArgs encode_pubkey_args;
122 proto_bool ok;
123 int n;
124} SshRsaVars;
125
126/** @brief The operands and the outcome. */
127extern SshRsaVars SshRsaV;
128
129/** @brief The entries. */
130typedef struct
131{
132 void (*const load_pubkey)(uint8_t *work);
133 void (*const sign)(uint8_t *work);
134 void (*const encode_pubkey)(uint8_t *work);
135} SshRsaNs;
136
137// What the table binds, defined once in the .c and taking one parameter each: everything
138// else an entry needs is an operand in SshRsaV or a region of the borrow at a fixed offset.
139void protocore_ssh_rsa_load_pubkey(uint8_t *work);
140void protocore_ssh_rsa_sign(uint8_t *work);
141void protocore_ssh_rsa_encode_pubkey(uint8_t *work);
142
143// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
144// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
145// `SshRsa.load_pubkey(work)` resolves to a named function and becomes a DIRECT call. An extern table
146// leaves the call indirect and the symbol live at every level, -O2 -flto included.
147static const SshRsaNs SshRsa __attribute__((unused)) = {
148 .load_pubkey = protocore_ssh_rsa_load_pubkey,
149 .sign = protocore_ssh_rsa_sign,
150 .encode_pubkey = protocore_ssh_rsa_encode_pubkey,
151};
152
153/**
154 * @brief The RSA host key's public half: modulus and exponent, and whether they are loaded.
155 *
156 * Public by definition, so it is read directly rather than through the namespace; only the private
157 * exponent lives in the borrow. The transport reads @c loaded to decide whether an RSA host-key
158 * algorithm can be offered at all.
159 */
160extern SshRsaPubKey ssh_host_pubkey;
161
162/**
163 * @brief The PROTOCORE_SSH_RSA_BORROW bytes this module's state lives in.
164 *
165 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
166 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
167 * walks, so the state lasts the life of the program.
168 *
169 * @return the span.
170 */
171uint8_t *protocore_ssh_rsa_span(void);
172
174
175#endif // PROTOCORE_ENABLE_SSH_RSA
176
177#endif // PROTOCORE_SSH_RSA_H
#define PROTOCORE_RSA_KEY_BYTES
RSA-2048 PKCS#1 v1.5 signature primitive (RFC 8017) - verify + software sign.
#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