ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
snmp_crypto.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 snmp_crypto.h
6 * @brief The two USM transforms: key localization and the privacy cipher (PROTOCORE_ENABLE_SNMP_V3).
7 *
8 * Exactly what the User-based Security Model needs, and nothing more.
9 *
10 * **Key localization.** RFC 3414 sec 2.6 defines a localized key as one derived from a user's
11 * secret and the authoritative snmpEngineID, so a key that leaks reaches one engine only. RFC 7860
12 * sec 5 says that localization for the HMAC-SHA-2 protocols "SHALL be performed according to
13 * [RFC3414] using the same SHA-2 hash function as in the HMAC-SHA-2 authentication protocol", and
14 * RFC 7860 sec 9.3 states the password-to-key derivation: the password is repeated to a 1,048,576
15 * octet string and hashed to digest1, then digest1, snmpEngineID and digest1 again are hashed to
16 * the localized key. It is the RFC 3414 password-to-key algorithm with SHA-256 in place of MD5.
17 * The same procedure produces the authentication key and the privacy key, each from its own
18 * password.
19 *
20 * **Privacy.** RFC 3826 defines usmAesCfb128Protocol: CFB128-AES-128. The cipher is AES (FIPS 197,
21 * not an RFC) and the mode is CFB with a 128-bit feedback segment (NIST SP 800-38A, not an RFC).
22 * RFC 3826 sec 3.1.2.1 states the 16-octet IV: snmpEngineBoots big-endian, then snmpEngineTime
23 * big-endian, then the 8-octet msgPrivacyParameters salt. The privacy key is the first 16 octets
24 * of the localized key.
25 *
26 * CFB is a stream mode, so the output length equals the input length and no padding is added, and
27 * decryption is the same construction with the feedback taken from the ciphertext. The transform
28 * is safe in place. SHA-256 and HMAC-SHA-256 come from the shared hash and MAC modules. This
29 * software AES is not constant time.
30 *
31 * @author Douglas Quigg (dstroy0)
32 * @date 2026
33 */
34
35#ifndef PROTOCORE_SNMP_CRYPTO_H
36#define PROTOCORE_SNMP_CRYPTO_H
37
38#include "protocore_config.h" // the entry point: protocore_types.h for the widths
39
40#if PROTOCORE_ENABLE_SNMP_V3
41
43
44/** @brief Localized-key length: the SHA-256 digest size (RFC 7860 sec 9.3). */
45#define SNMP_USM_KEY_LEN 32
46
47/** @brief RFC 7860 sec 9.3: what a password-to-key derivation reads, and where the key lands. */
48typedef struct
49{
50 const char *password; ///< the user's authentication or privacy password
51 const uint8_t *engine_id; ///< the authoritative snmpEngineID (RFC 3414 sec 2.6)
52 size_t engine_id_len; ///< how many octets
53 uint8_t *out; ///< where ::SNMP_USM_KEY_LEN localized-key octets land
54} SnmpUsmKeyArgs;
55
56/** @brief RFC 3826 sec 3.1.2.1: what the privacy transform reads and where it writes. */
57typedef struct
58{
59 const uint8_t *key; ///< the 16-octet AES key: the first 16 octets of the localized privacy key
60 const uint8_t *iv; ///< the 16-octet IV: snmpEngineBoots, snmpEngineTime, msgPrivacyParameters
61 const uint8_t *in; ///< the octets to transform
62 uint8_t *out; ///< where they land; may be @c in
63 size_t len; ///< how many
64 proto_bool encrypt; ///< encrypt, otherwise decrypt with the feedback taken from the ciphertext
65} SnmpUsmPrivArgs;
66
67/**
68 * @brief The USM transforms (RFC 3414 sec 2.6, RFC 7860 sec 9.3, RFC 3826 sec 3.1.2.1).
69 *
70 * A caller sets the members a call takes, invokes it through ::SnmpCrypto, and reads the outcome
71 * off the same handle.
72 *
73 * No storage member: both transforms read their inputs and write the caller's destination, so the
74 * module holds no key and no state between calls.
75 *
76 * @var SnmpCryptoNs::work the caller's scratch region the hash borrows
77 * @var SnmpCryptoNs::key what a password-to-key derivation reads, and where its key lands
78 * @var SnmpCryptoNs::priv what the privacy transform reads and where it writes
79 * @var SnmpCryptoNs::ok a call's true/false outcome
80 * @var SnmpCryptoNs::localize_key derive the engine-localized key from a password
81 * @var SnmpCryptoNs::aes_cfb128 run CFB128-AES-128 over @c priv.in into @c priv.out
82 */
83typedef struct
84{
85 uint8_t *work; ///< PROTOCORE_HMAC_SHA256_BORROW octets, aligned for uint32_t, alive across the call
86 SnmpUsmKeyArgs key; ///< what a derivation reads and writes
87 SnmpUsmPrivArgs priv; ///< what the privacy transform reads and writes
88 proto_bool ok;
89} SnmpCryptoVars;
90
91/** @brief The operands and the outcome. */
92extern SnmpCryptoVars SnmpCryptoV;
93
94/** @brief The entries. */
95typedef struct
96{
97 void (*const localize_key)(uint8_t *work);
98 void (*const aes_cfb128)(uint8_t *work);
99} SnmpCryptoNs;
100
101// What the table binds, defined once in the .c and taking one parameter each: everything
102// else an entry needs is an operand in SnmpCryptoV or a region of the borrow at a fixed offset.
103void protocore_snmp_crypto_localize_key(uint8_t *work);
104void protocore_snmp_crypto_aes_cfb128(uint8_t *work);
105
106// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
107// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
108// `SnmpCrypto.localize_key(work)` resolves to a named function and becomes a DIRECT call. An extern table
109// leaves the call indirect and the symbol live at every level, -O2 -flto included.
110static const SnmpCryptoNs SnmpCrypto __attribute__((unused)) = {
111 .localize_key = protocore_snmp_crypto_localize_key,
112 .aes_cfb128 = protocore_snmp_crypto_aes_cfb128,
113};
114
116
117#endif // PROTOCORE_ENABLE_SNMP_V3
118
119#endif // PROTOCORE_SNMP_CRYPTO_H
#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