ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
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 rsa.h
6 * @brief RSA-2048 PKCS#1 v1.5 signature primitive (RFC 8017) - verify + software sign.
7 *
8 * The shared, protocol-agnostic RSA primitive: it takes raw big-endian key material (modulus n,
9 * exponent) and a message, and does the RSASSA-PKCS1-v1.5 math with a SHA-256 or SHA-512 digest. It
10 * knows nothing about SSH key blobs, host-key storage, or the "ssh-rsa" / "rsa-sha2-256" wire names -
11 * that layer lives in network_drivers/presentation/ssh/transport/ssh_rsa and calls into this primitive.
12 *
13 * Both entries take raw big-endian key material: verify takes n and the four-byte public exponent,
14 * sign takes n and the full-width private exponent d. One modular multiply underneath them has an
15 * accelerated arm and a software arm; everything else is the same code on every part.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_RSA_H
22#define PROTOCORE_RSA_H
23
24#include "protocore_config.h" // the entry point: PROTO_ENUM_PACKED, and protocore_types.h for the widths
25
26#if PROTOCORE_ENABLE_RSA
27
29
30// PROTOCORE_RSA_KEY_BYTES - the modulus size, RSA-2048 - is stated in protocore_config.h, which
31// sizes the X.509 verification borrow out of it.
32
33/** @brief PKCS#1 v1.5 signature size for RSA-2048 in bytes. */
34#define PROTOCORE_RSA_SIG_BYTES 256
35
36/**
37 * @brief Hash algorithm selecting the RSA signature scheme (RFC 8017 §9.2).
38 *
39 * Only the message hash and its DigestInfo OID differ between the two.
40 */
41typedef enum PROTO_ENUM_PACKED
42{
43 PROTOCORE_RSA_HASH_SHA256 = 0, ///< RSASSA-PKCS1-v1.5 with SHA-256
44 PROTOCORE_RSA_HASH_SHA512 = 1, ///< RSASSA-PKCS1-v1.5 with SHA-512
45 PROTOCORE_RSA_HASH_PSS_SHA256 = 2 ///< RSASSA-PSS with SHA-256, MGF1-SHA-256 and a 32-octet salt
46} protocore_rsa_hash;
47
48/** @brief Length of the DER DigestInfo wrapper for SHA-256 (RFC 8017 / RFC 5754). */
49#define PROTOCORE_PKCS1_DIGESTINFO_LEN 19
50
51/** @brief Length of the DER DigestInfo wrapper for SHA-512. */
52#define PROTOCORE_PKCS1_SHA512_DIGESTINFO_LEN 19
53
54/** @brief The DER-encoded DigestInfo wrapper for SHA-256 (prepend to the 32-byte digest). */
55extern const uint8_t protocore_pkcs1_sha256_digestinfo[PROTOCORE_PKCS1_DIGESTINFO_LEN];
56
57/** @brief The DER-encoded DigestInfo wrapper for SHA-512 (prepend to the 64-byte digest). */
58extern const uint8_t protocore_pkcs1_sha512_digestinfo[PROTOCORE_PKCS1_SHA512_DIGESTINFO_LEN];
59
60// PROTOCORE_RSA_BORROW - the bytes one signature operation runs out of - is stated in
61// protocore_config.h, which sums it into the secure arena. A caller takes them once and passes the
62// pointer to every call.
63
64/** @brief The key, message and signature a verify checks. */
65typedef struct
66{
67 const uint8_t *n; ///< modulus n, PROTOCORE_RSA_KEY_BYTES big-endian
68 const uint8_t *e; ///< public exponent e, 4 bytes big-endian (typically 65537)
69 const uint8_t *msg; ///< the message that was signed; this hashes it, do not pre-hash
70 size_t msg_len; ///< its length
71 const uint8_t *sig; ///< the signature, big-endian
72 size_t sig_len; ///< its length; must equal PROTOCORE_RSA_KEY_BYTES
73 protocore_rsa_hash hash; ///< digest algorithm (SHA-256 / SHA-512)
74} RsaVerifyArgs;
75
76/** @brief The key and message a sign covers. */
77typedef struct
78{
79 const uint8_t *n; ///< modulus n, PROTOCORE_RSA_KEY_BYTES big-endian
80 const uint8_t *d; ///< private exponent d, PROTOCORE_RSA_KEY_BYTES big-endian
81 const uint8_t *msg; ///< the message to sign; this hashes it
82 size_t msg_len; ///< its length
83 protocore_rsa_hash hash; ///< digest algorithm (SHA-256 / SHA-512)
84 uint8_t *sig; ///< PROTOCORE_RSA_SIG_BYTES big-endian signature bytes
85} RsaSignArgs;
86
87/**
88 * @brief RSASSA-PKCS1-v1.5 over RSA-2048 (RFC 8017 §8.2).
89 *
90 * A caller sets the members a call takes, invokes it through ::Rsa with the bytes it runs out of, and
91 * reads the outcome off the same handle. How those bytes are carved is this module's and is never
92 * named here.
93 *
94 * Rsa.verify_args.n = n_be;
95 * Rsa.verify_args.e = e_be4;
96 * Rsa.verify_args.msg = msg;
97 * Rsa.verify_args.msg_len = msg_len;
98 * Rsa.verify_args.sig = sig;
99 * Rsa.verify_args.sig_len = sig_len;
100 * Rsa.verify_args.hash = PROTOCORE_RSA_HASH_SHA256;
101 * Rsa.verify(work);
102 * // Rsa.ok is true only for a signature that verified
103 *
104 * @var RsaNs::verify_args the key, message and signature a verify checks
105 * @var RsaNs::sign_args the key and message a sign covers
106 * @var RsaNs::ok a call's true/false outcome; false on a null pointer, a signature that is
107 * not PROTOCORE_RSA_KEY_BYTES long, a representative that is not below n, and
108 * a block that does not match
109 * @var RsaNs::verify hash the message, recover the signature block, compare the two in constant
110 * time
111 * @var RsaNs::sign hash the message, PKCS#1 v1.5 encode it, raise it to d mod n; NOT
112 * constant-time - see SECURITY.md, timing
113 *
114 * @c work is PROTOCORE_RSA_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the call,
115 * so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes it,
116 * holds it, releases it, nor wipes it. That is what keeps the private exponent and the encoded block a sign works in
117 * from outliving the caller. The digest each entry takes runs out of those bytes too, so a verify costs one borrow and
118 * no wipe.
119 *
120 * No storage member and no context: a caller sets operands and reads @ref RsaNs::ok, and that is all
121 * the surface there is.
122 */
123typedef struct
124{
125 RsaVerifyArgs verify_args;
126 RsaSignArgs sign_args;
127 proto_bool ok;
128} RsaVars;
129
130/** @brief The operands and the outcome. */
131extern RsaVars RsaV;
132
133/** @brief The entries. */
134typedef struct
135{
136 void (*const verify)(uint8_t *work);
137 void (*const sign)(uint8_t *work);
138} RsaNs;
139
140// What the table binds, defined once in the .c and taking one parameter each: everything
141// else an entry needs is an operand in RsaV or a region of the borrow at a fixed offset.
142void protocore_rsa_verify(uint8_t *work);
143void protocore_rsa_sign(uint8_t *work);
144
145// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
146// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
147// `Rsa.verify(work)` resolves to a named function and becomes a DIRECT call. An extern table
148// leaves the call indirect and the symbol live at every level, -O2 -flto included.
149static const RsaNs Rsa __attribute__((unused)) = {
150 .verify = protocore_rsa_verify,
151 .sign = protocore_rsa_sign,
152};
153
155
156#endif // PROTOCORE_ENABLE_RSA
157
158#endif // PROTOCORE_RSA_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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