Fun API Documentation 0.42.1
The programming language that makes you have fun!
Loading...
Searching...
No Matches
openssl.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2026 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
10/**
11 * @file openssl.c
12 * @brief OpenSSL-based hashing helpers used by crypto-related VM opcodes.
13 *
14 * This module centralizes small, concrete helpers around the OpenSSL EVP
15 * message-digest API so that VM opcodes under src/vm/crypto/*.c can remain
16 * minimal and focus on VM stack marshalling. Keeping the algorithm-specific
17 * logic in src/extensions/ mirrors other extensions (PCRE2, SQLite, XML2,
18 * JSON, INI) and improves maintainability.
19 *
20 * Build-time feature flag:
21 * - All code in this file is compiled only when FUN_WITH_OPENSSL is enabled.
22 * When disabled, the helpers provide safe fallbacks that allocate and return
23 * an empty string (""), preserving the VM's expectations around string
24 * ownership while signaling the absence of cryptographic support.
25 *
26 * Algorithms covered:
27 * - MD5
28 * - SHA-256
29 * - SHA-512
30 * - RIPEMD-160 (may be unavailable on some OpenSSL builds; see notes below)
31 *
32 * Ownership and memory model:
33 * - Each helper returns a newly allocated, NUL-terminated lowercase hex string
34 * that the caller owns and must free() when no longer needed.
35 * - On allocation failure or when an algorithm/provider is unavailable, NULL
36 * is returned (except in disabled builds where an allocated empty string is
37 * returned). Callers should check for NULL before use.
38 *
39 * Zero-length input:
40 * - The EVP_Digest() API supports computing a digest for zero-length inputs.
41 * Passing len==0 is valid; data may be NULL in that case. Passing data==NULL
42 * with len>0 returns NULL to indicate misuse.
43 *
44 * Error handling:
45 * - Any failure to acquire an EVP_MD, an unexpected digest length, or memory
46 * allocation failure results in a NULL return (again, except in the disabled
47 * build where an allocated empty string is returned to keep the shape).
48 *
49 * OpenSSL 3.x provider note:
50 * - RIPEMD-160 is part of the legacy provider in OpenSSL 3.x and may not be
51 * available unless the provider is enabled at runtime/compile time. In such
52 * environments EVP_ripemd160() can return NULL and this module will surface
53 * that as a NULL return value to the caller.
54 *
55 * Thread-safety:
56 * - The helpers are stateless and thread-safe as long as the underlying
57 * OpenSSL library initialization/finalization follows OpenSSL's guidelines
58 * for multithreaded use. No shared global state is kept here.
59 */
60
61/*
62 * OpenSSL integration helpers (MD5, RIPEMD-160, SHA-256, SHA-512)
63 */
64
65#ifdef FUN_WITH_OPENSSL
66#include <openssl/evp.h>
67/**
68 * @brief Forward declaration for EVP_MD_get_size on older headers.
69 *
70 * Some older OpenSSL headers might not declare EVP_MD_get_size even though the
71 * symbol is available in libcrypto. Guarded declaration keeps compilation
72 * working across versions while always calling the same function.
73 */
74#ifndef EVP_MD_get_size
75int EVP_MD_get_size(const EVP_MD *md);
76#endif
77#endif
78#include <stdlib.h>
79
80/**
81 * @brief Compute MD5 and return it as a lowercase hexadecimal string.
82 *
83 * Behavior and edge cases:
84 * - Accepts zero-length input (len==0); in this case data may be NULL.
85 * - Returns a string of length 32 characters (2 hex chars per 16-byte digest)
86 * plus the NUL terminator, on success.
87 * - Returns NULL if the algorithm is unavailable or on allocation failure.
88 * - When FUN_WITH_OPENSSL is disabled, returns an allocated empty string "".
89 *
90 * @param data Pointer to input bytes (may be NULL if len==0).
91 * @param len Number of input bytes.
92 * @return char* Newly-allocated NUL-terminated hex string on success; NULL on
93 * failure (except disabled builds return an allocated empty
94 * string). The caller must free() the returned buffer.
95 */
96static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) {
97 static const char hexdig[] = "0123456789abcdef";
98 if (!data && len != 0) return NULL;
99#ifdef FUN_WITH_OPENSSL
100 const EVP_MD *md = EVP_md5();
101 if (!md) return NULL;
102 int dlen = EVP_MD_get_size(md);
103 if (dlen <= 0) return NULL;
104 unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
105 if (!digest) return NULL;
106 unsigned int out_len = 0;
107 int ok;
108 if (len == 0) {
109 // EVP_Digest handles zero-length fine as well
110 ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
111 } else {
112 ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
113 }
114 if (ok != 1 || (int)out_len != dlen) {
115 free(digest);
116 return NULL;
117 }
118 char *hex = (char *)malloc((size_t)dlen * 2 + 1);
119 if (!hex) {
120 free(digest);
121 return NULL;
122 }
123 for (int i = 0; i < dlen; ++i) {
124 hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF];
125 hex[2 * i + 1] = hexdig[digest[i] & 0xF];
126 }
127 hex[dlen * 2] = '\0';
128 free(digest);
129 return hex;
130#else
131 char *hex = (char *)malloc(1);
132 if (hex) hex[0] = '\0';
133 return hex;
134#endif
135}
136
137/**
138 * @brief Compute SHA-256 and return it as a lowercase hexadecimal string.
139 *
140 * Details:
141 * - Output length is 64 hex characters (2 per 32-byte digest) plus NUL.
142 * - data may be NULL if len==0; otherwise must be non-NULL.
143 * - Returns NULL on failure; in disabled builds returns an allocated "".
144 *
145 * @param data Pointer to input bytes (may be NULL if len==0).
146 * @param len Number of input bytes.
147 * @return char* Newly-allocated hex string on success; NULL on failure (except
148 * disabled builds return an allocated empty string). The caller
149 * must free() the buffer.
150 */
151static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) {
152 static const char hexdig[] = "0123456789abcdef";
153 if (!data && len != 0) return NULL;
154#ifdef FUN_WITH_OPENSSL
155 const EVP_MD *md = EVP_sha256();
156 if (!md) return NULL;
157 int dlen = EVP_MD_get_size(md);
158 if (dlen <= 0) return NULL;
159 unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
160 if (!digest) return NULL;
161 unsigned int out_len = 0;
162 int ok;
163 if (len == 0) {
164 ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
165 } else {
166 ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
167 }
168 if (ok != 1 || (int)out_len != dlen) {
169 free(digest);
170 return NULL;
171 }
172 char *hex = (char *)malloc((size_t)dlen * 2 + 1);
173 if (!hex) {
174 free(digest);
175 return NULL;
176 }
177 for (int i = 0; i < dlen; ++i) {
178 hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF];
179 hex[2 * i + 1] = hexdig[digest[i] & 0xF];
180 }
181 hex[dlen * 2] = '\0';
182 free(digest);
183 return hex;
184#else
185 char *hex = (char *)malloc(1);
186 if (hex) hex[0] = '\0';
187 return hex;
188#endif
189}
190
191/**
192 * @brief Compute SHA-512 and return it as a lowercase hexadecimal string.
193 *
194 * Details:
195 * - Output length is 128 hex characters (2 per 64-byte digest) plus NUL.
196 * - data may be NULL if len==0; otherwise must be non-NULL.
197 * - Returns NULL on failure; in disabled builds returns an allocated "".
198 *
199 * @param data Pointer to input bytes (may be NULL if len==0).
200 * @param len Number of input bytes.
201 * @return char* Newly-allocated hex string on success; NULL on failure (except
202 * disabled builds return an allocated empty string). The caller
203 * must free() the buffer.
204 */
205static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) {
206 static const char hexdig[] = "0123456789abcdef";
207 if (!data && len != 0) return NULL;
208#ifdef FUN_WITH_OPENSSL
209 const EVP_MD *md = EVP_sha512();
210 if (!md) return NULL;
211 int dlen = EVP_MD_get_size(md);
212 if (dlen <= 0) return NULL;
213 unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
214 if (!digest) return NULL;
215 unsigned int out_len = 0;
216 int ok;
217 if (len == 0) {
218 ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
219 } else {
220 ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
221 }
222 if (ok != 1 || (int)out_len != dlen) {
223 free(digest);
224 return NULL;
225 }
226 char *hex = (char *)malloc((size_t)dlen * 2 + 1);
227 if (!hex) {
228 free(digest);
229 return NULL;
230 }
231 for (int i = 0; i < dlen; ++i) {
232 hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF];
233 hex[2 * i + 1] = hexdig[digest[i] & 0xF];
234 }
235 hex[dlen * 2] = '\0';
236 free(digest);
237 return hex;
238#else
239 char *hex = (char *)malloc(1);
240 if (hex) hex[0] = '\0';
241 return hex;
242#endif
243}
244
245/**
246 * @brief Compute RIPEMD-160 and return it as a lowercase hexadecimal string.
247 *
248 * Availability and details:
249 * - On OpenSSL 3.x, RIPEMD-160 typically resides in the legacy provider and
250 * may be unavailable unless explicitly enabled; EVP_ripemd160() can return
251 * NULL in that case and this helper returns NULL.
252 * - Output length is 40 hex characters (2 per 20-byte digest) plus NUL.
253 * - data may be NULL if len==0; otherwise must be non-NULL.
254 * - In disabled builds, returns an allocated empty string.
255 *
256 * @param data Pointer to input bytes (may be NULL if len==0).
257 * @param len Number of input bytes.
258 * @return char* Newly-allocated hex string on success; NULL if the algorithm
259 * is unavailable or on error (except disabled builds return an
260 * allocated empty string). Caller must free() the buffer.
261 */
262static char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) {
263 static const char hexdig[] = "0123456789abcdef";
264 if (!data && len != 0) return NULL;
265#ifdef FUN_WITH_OPENSSL
266 const EVP_MD *md = EVP_ripemd160();
267 if (!md) return NULL;
268 int dlen = EVP_MD_get_size(md);
269 if (dlen <= 0) return NULL;
270 unsigned char *digest = (unsigned char *)malloc((size_t)dlen);
271 if (!digest) return NULL;
272 unsigned int out_len = 0;
273 int ok;
274 if (len == 0) {
275 ok = EVP_Digest(NULL, 0, digest, &out_len, md, NULL);
276 } else {
277 ok = EVP_Digest(data, len, digest, &out_len, md, NULL);
278 }
279 if (ok != 1 || (int)out_len != dlen) {
280 free(digest);
281 return NULL;
282 }
283 char *hex = (char *)malloc((size_t)dlen * 2 + 1);
284 if (!hex) {
285 free(digest);
286 return NULL;
287 }
288 for (int i = 0; i < dlen; ++i) {
289 hex[2 * i] = hexdig[(digest[i] >> 4) & 0xF];
290 hex[2 * i + 1] = hexdig[digest[i] & 0xF];
291 }
292 hex[dlen * 2] = '\0';
293 free(digest);
294 return hex;
295#else
296 char *hex = (char *)malloc(1);
297 if (hex) hex[0] = '\0';
298 return hex;
299#endif
300}
static char * fun_openssl_sha512_hex(const unsigned char *data, size_t len)
Compute SHA-512 and return it as a lowercase hexadecimal string.
Definition openssl.c:205
static char * fun_openssl_ripemd160_hex(const unsigned char *data, size_t len)
Compute RIPEMD-160 and return it as a lowercase hexadecimal string.
Definition openssl.c:262
static char * fun_openssl_sha256_hex(const unsigned char *data, size_t len)
Compute SHA-256 and return it as a lowercase hexadecimal string.
Definition openssl.c:151
static char * fun_openssl_md5_hex(const unsigned char *data, size_t len)
Compute MD5 and return it as a lowercase hexadecimal string.
Definition openssl.c:96
int EVP_MD_get_size(const EVP_MD *md)
Forward declaration for EVP_MD_get_size on older headers.