libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
os_crypto.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License,
4 * a copy of which is in the file LICENSE in the top-level directory
5 * of this distribution.
6 *
7 * src/inc/os_crypto.h
8 * L0 cryptography building blocks (PLAN.md 19.2), v1 scope: only
9 * the two real, stated use cases -- checksums/hashing for WAL
10 * integrity, and a per-loop CSPRNG. Backed ONLY by OpenSSL (already
11 * a build dependency for TLS); the guard is the SAME macro
12 * src/io/tls_openssl.c uses to detect the selected TLS backend
13 * (XTC_TLS_BACKEND_OPENSSL), so this header/implementation need no
14 * new configure flag.
15 *
16 * When OpenSSL is NOT the selected backend (XTC_TLS_BACKEND_OPENSSL
17 * undefined -- e.g. --with-tls=none, mbedtls, gnutls, ...) every
18 * function below is still linkable; each returns XTC_E_NOSYS at
19 * runtime, following the same "always linkable, NOSYS when
20 * unsupported" convention as src/io/tls_none.c.
21 *
22 * Extended past the original v1 scope for future TDE/WAL-encryption
23 * work (built ahead of a concrete consumer, per PLAN.md 19.2):
24 * ChaCha20-Poly1305 AEAD, SHA-3 (256/512), BLAKE3, and HKDF-SHA256.
25 * ChaCha20-Poly1305, SHA-3, and HKDF are OpenSSL-backed (NOSYS when
26 * OpenSSL is not the selected backend); BLAKE3 is a small
27 * self-contained portable C reimplementation and is therefore
28 * available on EVERY build regardless of TLS backend.
29 *
30 * Still DEFERRED (see man/man3/__os_crypto.3): any backend other
31 * than OpenSSL (libsodium, BoringSSL, SChannel, CommonCrypto).
32 * This is a library-internal L0 primitive; there is no public
33 * xtc_* wrapper in this pass because no consumer needs direct
34 * access yet.
35 */
36
37#ifndef XTC_OS_CRYPTO_H
38#define XTC_OS_CRYPTO_H
39
40#include "xtc_export.h"
41
42#include <stddef.h>
43#include <stdint.h>
44
45#define XTC_CRYPTO_SHA256_LEN 32
46#define XTC_CRYPTO_SHA3_256_LEN 32
47#define XTC_CRYPTO_SHA3_512_LEN 64
48#define XTC_CRYPTO_BLAKE3_LEN 32 /* BLAKE3 default output length */
49#define XTC_CRYPTO_AES_KEY_LEN 32 /* AES-256 */
50#define XTC_CRYPTO_AES_IV_LEN 12 /* GCM standard nonce length */
51#define XTC_CRYPTO_AES_TAG_LEN 16 /* GCM standard tag length */
52#define XTC_CRYPTO_CHACHA_KEY_LEN 32 /* ChaCha20-Poly1305 key length */
53#define XTC_CRYPTO_CHACHA_NONCE_LEN 12 /* ChaCha20-Poly1305 nonce length */
54#define XTC_CRYPTO_CHACHA_TAG_LEN 16 /* Poly1305 tag length */
55
56/*
57 * --- One-shot hashing (WAL/checksum use) ---
58 *
59 * __os_crypto_sha256 writes the 32-byte SHA-256 digest of data[0..len)
60 * to out. __os_crypto_hmac_sha256 writes the 32-byte HMAC-SHA256 of
61 * data[0..len) under key[0..keylen) to out.
62 *
63 * Both return XTC_OK, XTC_E_INVAL (NULL data/out with nonzero len, or
64 * NULL out), or XTC_E_INTERNAL (the underlying OpenSSL call failed --
65 * an invariant violation, not an expected runtime condition). When
66 * OpenSSL is not the selected TLS backend, both return XTC_E_NOSYS.
67 *
68 * PUBLIC: int __os_crypto_sha256 __P((const void *, size_t, uint8_t *));
69 * PUBLIC: int __os_crypto_hmac_sha256 __P((const void *, size_t, const void *, size_t, uint8_t *));
70 */
71XTC_API int __os_crypto_sha256(const void *data, size_t len,
72 uint8_t out[XTC_CRYPTO_SHA256_LEN]);
73XTC_API int __os_crypto_hmac_sha256(const void *key, size_t keylen,
74 const void *data, size_t len, uint8_t out[XTC_CRYPTO_SHA256_LEN]);
75
76/*
77 * --- SHA-3 one-shot digests ---
78 *
79 * __os_crypto_sha3_256 / _sha3_512 write the 32- resp. 64-byte SHA-3
80 * (FIPS 202, Keccak) digest of data[0..len) to out. Same return
81 * codes and NULL-argument contract as __os_crypto_sha256. Return
82 * XTC_E_NOSYS when OpenSSL is not the selected TLS backend AND on
83 * BoringSSL, which -- despite defining XTC_TLS_BACKEND_OPENSSL --
84 * ships no SHA-3 (no EVP_sha3_*, no Keccak in its EVP surface). A
85 * portable Keccak reimplementation was judged not worth carrying for
86 * a deferred-until-needed primitive on one backend; BLAKE3 (which
87 * OpenSSL lacks too) IS carried portably because it was the primary
88 * hashing target.
89 *
90 * PUBLIC: int __os_crypto_sha3_256 __P((const void *, size_t, uint8_t *));
91 * PUBLIC: int __os_crypto_sha3_512 __P((const void *, size_t, uint8_t *));
92 */
93XTC_API int __os_crypto_sha3_256(const void *data, size_t len,
94 uint8_t out[XTC_CRYPTO_SHA3_256_LEN]);
95XTC_API int __os_crypto_sha3_512(const void *data, size_t len,
96 uint8_t out[XTC_CRYPTO_SHA3_512_LEN]);
97
98/*
99 * --- BLAKE3 one-shot digest ---
100 *
101 * __os_crypto_blake3 writes the 32-byte (default-length) BLAKE3
102 * digest of data[0..len) to out. UNLIKE the other primitives here,
103 * BLAKE3 is a small self-contained PORTABLE reimplementation compiled
104 * directly into os_crypto.c (OpenSSL does not provide BLAKE3), so it
105 * is available on EVERY build and NEVER returns XTC_E_NOSYS. Returns
106 * XTC_OK or XTC_E_INVAL (NULL out, or NULL data with nonzero len).
107 *
108 * PUBLIC: int __os_crypto_blake3 __P((const void *, size_t, uint8_t *));
109 */
110XTC_API int __os_crypto_blake3(const void *data, size_t len,
111 uint8_t out[XTC_CRYPTO_BLAKE3_LEN]);
112
113/*
114 * --- AES-256-GCM one-shot authenticated encryption ---
115 *
116 * key is XTC_CRYPTO_AES_KEY_LEN (32) bytes, iv is XTC_CRYPTO_AES_IV_LEN
117 * (12) bytes (the standard GCM nonce length -- reuse of an (key, iv)
118 * pair is a catastrophic confidentiality break; callers must supply a
119 * fresh iv per encryption, e.g. from __os_csprng_bytes). aad may be
120 * NULL/0 (no additional authenticated data). ciphertext and
121 * plaintext are exactly len bytes; tag is exactly
122 * XTC_CRYPTO_AES_TAG_LEN (16) bytes.
123 *
124 * __os_crypto_aes_gcm_encrypt writes len bytes of ciphertext and the
125 * 16-byte tag. Returns XTC_OK, XTC_E_INVAL (bad argument), or
126 * XTC_E_INTERNAL.
127 *
128 * __os_crypto_aes_gcm_decrypt verifies the tag BEFORE trusting the
129 * recovered bytes. On a tag mismatch it returns XTC_E_INVAL and does
130 * NOT write plaintext (the output buffer is left untouched -- the
131 * caller must not read it). On success it writes len bytes of
132 * plaintext and returns XTC_OK. Returns XTC_E_INTERNAL on an
133 * unexpected OpenSSL failure unrelated to tag verification.
134 *
135 * When OpenSSL is not the selected TLS backend both return
136 * XTC_E_NOSYS.
137 *
138 * PUBLIC: int __os_crypto_aes_gcm_encrypt __P((const uint8_t *, const uint8_t *, const void *, size_t, const void *, size_t, void *, uint8_t *));
139 * PUBLIC: int __os_crypto_aes_gcm_decrypt __P((const uint8_t *, const uint8_t *, const void *, size_t, const void *, size_t, const uint8_t *, void *));
140 */
141XTC_API int __os_crypto_aes_gcm_encrypt(
142 const uint8_t key[XTC_CRYPTO_AES_KEY_LEN],
143 const uint8_t iv[XTC_CRYPTO_AES_IV_LEN],
144 const void *aad, size_t aadlen,
145 const void *plaintext, size_t len,
146 void *ciphertext_out, uint8_t tag_out[XTC_CRYPTO_AES_TAG_LEN]);
147XTC_API int __os_crypto_aes_gcm_decrypt(
148 const uint8_t key[XTC_CRYPTO_AES_KEY_LEN],
149 const uint8_t iv[XTC_CRYPTO_AES_IV_LEN],
150 const void *aad, size_t aadlen,
151 const void *ciphertext, size_t len,
152 const uint8_t tag[XTC_CRYPTO_AES_TAG_LEN],
153 void *plaintext_out);
154
155/*
156 * --- ChaCha20-Poly1305 one-shot authenticated encryption ---
157 *
158 * A second AEAD, same shape as the AES-256-GCM pair above: key is
159 * XTC_CRYPTO_CHACHA_KEY_LEN (32) bytes, nonce is
160 * XTC_CRYPTO_CHACHA_NONCE_LEN (12) bytes (the RFC 8439 96-bit nonce;
161 * reuse of a (key, nonce) pair is a catastrophic confidentiality
162 * break -- supply a fresh nonce per encryption, e.g. from
163 * __os_csprng_bytes). aad may be NULL/0. ciphertext and plaintext
164 * are exactly len bytes; tag is XTC_CRYPTO_CHACHA_TAG_LEN (16) bytes.
165 *
166 * _decrypt verifies the Poly1305 tag BEFORE trusting the recovered
167 * bytes; on a tag mismatch it returns XTC_E_INVAL, zeroes any
168 * provisional plaintext, and does NOT leave the plaintext in
169 * plaintext_out (identical guarantee to __os_crypto_aes_gcm_decrypt).
170 *
171 * OpenSSL is the only backend, so both return XTC_E_NOSYS when
172 * OpenSSL is not the selected TLS backend.
173 *
174 * PUBLIC: int __os_crypto_chacha20_poly1305_encrypt __P((const uint8_t *, const uint8_t *, const void *, size_t, const void *, size_t, void *, uint8_t *));
175 * PUBLIC: int __os_crypto_chacha20_poly1305_decrypt __P((const uint8_t *, const uint8_t *, const void *, size_t, const void *, size_t, const uint8_t *, void *));
176 */
177XTC_API int __os_crypto_chacha20_poly1305_encrypt(
178 const uint8_t key[XTC_CRYPTO_CHACHA_KEY_LEN],
179 const uint8_t nonce[XTC_CRYPTO_CHACHA_NONCE_LEN],
180 const void *aad, size_t aadlen,
181 const void *plaintext, size_t len,
182 void *ciphertext_out,
183 uint8_t tag_out[XTC_CRYPTO_CHACHA_TAG_LEN]);
184XTC_API int __os_crypto_chacha20_poly1305_decrypt(
185 const uint8_t key[XTC_CRYPTO_CHACHA_KEY_LEN],
186 const uint8_t nonce[XTC_CRYPTO_CHACHA_NONCE_LEN],
187 const void *aad, size_t aadlen,
188 const void *ciphertext, size_t len,
189 const uint8_t tag[XTC_CRYPTO_CHACHA_TAG_LEN],
190 void *plaintext_out);
191
192/*
193 * --- HKDF-SHA256 (RFC 5869) key derivation ---
194 *
195 * __os_crypto_hkdf_extract runs the HKDF-Extract step: it computes
196 * PRK = HMAC-SHA256(salt, ikm) and writes the 32-byte PRK to
197 * prk_out. salt may be NULL/0 (RFC 5869 then substitutes a string of
198 * HashLen zero bytes).
199 *
200 * __os_crypto_hkdf_expand runs the HKDF-Expand step: it derives
201 * outlen bytes of output key material from prk (32 bytes) and the
202 * optional info (may be NULL/0) into out. outlen must be in
203 * [1, 255*32] per RFC 5869; a larger request returns XTC_E_INVAL.
204 *
205 * __os_crypto_hkdf combines both (Extract then Expand) in one call --
206 * the common case -- deriving outlen bytes from (ikm, salt, info).
207 *
208 * OpenSSL is the only backend, so all three return XTC_E_NOSYS when
209 * OpenSSL is not the selected TLS backend. Otherwise XTC_OK,
210 * XTC_E_INVAL (bad argument), XTC_E_RANGE (a length exceeds INT_MAX),
211 * or XTC_E_INTERNAL.
212 *
213 * PUBLIC: int __os_crypto_hkdf_extract __P((const void *, size_t, const void *, size_t, uint8_t *));
214 * PUBLIC: int __os_crypto_hkdf_expand __P((const uint8_t *, const void *, size_t, void *, size_t));
215 * PUBLIC: int __os_crypto_hkdf __P((const void *, size_t, const void *, size_t, const void *, size_t, void *, size_t));
216 */
217XTC_API int __os_crypto_hkdf_extract(const void *salt, size_t saltlen,
218 const void *ikm, size_t ikmlen,
219 uint8_t prk_out[XTC_CRYPTO_SHA256_LEN]);
220XTC_API int __os_crypto_hkdf_expand(
221 const uint8_t prk[XTC_CRYPTO_SHA256_LEN],
222 const void *info, size_t infolen, void *out, size_t outlen);
223XTC_API int __os_crypto_hkdf(const void *ikm, size_t ikmlen,
224 const void *salt, size_t saltlen,
225 const void *info, size_t infolen, void *out, size_t outlen);
226
227/*
228 * --- Per-loop CSPRNG ---
229 *
230 * An opaque handle around an OpenSSL DRBG (see os_crypto.c for which
231 * one and why). __os_csprng_init seeds it from __os_rand_u64 (the
232 * existing per-thread entropy primitive in src/os/os_rand.c) mixed
233 * with OpenSSL's own default RAND_bytes seed material -- no new
234 * entropy source is invented. __os_csprng_bytes streams len
235 * cryptographically-strong pseudorandom bytes into buf.
236 * __os_csprng_destroy releases the handle; NULL is a no-op.
237 *
238 * Returns XTC_OK, XTC_E_INVAL, XTC_E_NOMEM (init only), or
239 * XTC_E_INTERNAL. When OpenSSL is not the selected TLS backend,
240 * init/bytes return XTC_E_NOSYS and destroy is a no-op.
241 *
242 * PUBLIC: int __os_csprng_init __P((__os_csprng_t **));
243 * PUBLIC: int __os_csprng_bytes __P((__os_csprng_t *, void *, size_t));
244 * PUBLIC: void __os_csprng_destroy __P((__os_csprng_t *));
245 */
246typedef struct __os_csprng __os_csprng_t;
247
248XTC_API int __os_csprng_init(__os_csprng_t **out);
249XTC_API int __os_csprng_bytes(__os_csprng_t *rng, void *buf, size_t len);
250XTC_API void __os_csprng_destroy(__os_csprng_t *rng);
251
252#endif /* XTC_OS_CRYPTO_H */