Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_x509.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
26
27#include "nilorea/n_x509.h"
28
29#ifdef HAVE_OPENSSL
30
31#include "nilorea/n_common.h"
32#include "nilorea/n_log.h"
33
34/* inet_pton()/AF_INET/AF_INET6 live in <arpa/inet.h> on POSIX and in the
35 * Winsock headers (<ws2tcpip.h>, pulled in via n_windows.h) on Windows. */
36#ifdef __windows__
37#include "nilorea/n_windows.h"
38#else
39#include <arpa/inet.h>
40#endif
41#include <string.h>
42
43#include <openssl/bn.h>
44#include <openssl/err.h>
45#include <openssl/evp.h>
46#include <openssl/pem.h>
47#include <openssl/rsa.h>
48#include <openssl/x509.h>
49#include <openssl/x509v3.h>
50
51/* Compatibility shims for OpenSSL 1.0.2 (RHEL 7 and friends). Everything below
52 * was introduced in 1.1.0: the BN_rand top/bottom named constants, the
53 * X509_getm_notBefore()/X509_getm_notAfter() mutable accessors, and the const
54 * qualifier on the value argument of X509V3_EXT_conf_nid(). LibreSSL reports
55 * OPENSSL_VERSION_NUMBER as 0x20000000L and already provides all of them, so it
56 * takes the modern path. */
57#if OPENSSL_VERSION_NUMBER < 0x10100000L
58#ifndef BN_RAND_TOP_ANY
60#define BN_RAND_TOP_ANY (-1)
61#endif
62#ifndef BN_RAND_BOTTOM_ANY
64#define BN_RAND_BOTTOM_ANY 0
65#endif
66#ifndef X509_getm_notBefore
68#define X509_getm_notBefore(x) X509_get_notBefore(x)
69#endif
70#ifndef X509_getm_notAfter
72#define X509_getm_notAfter(x) X509_get_notAfter(x)
73#endif
75#define N_X509_EXT_VALUE(v) ((char*)(uintptr_t)(v))
76#else
78#define N_X509_EXT_VALUE(v) (v)
79#endif
80
81#if OPENSSL_VERSION_NUMBER < 0x10100000L
82/* OpenSSL 1.1.0 initialises itself on first use. Before that the EVP algorithm
83 * table starts out empty, and while signing still works (X509_sign() is handed
84 * the EVP_MD directly) anything that later VERIFIES the certificate has to look
85 * the signature algorithm up by OID, which fails: X509_verify_cert() reports
86 * "certificate signature failure" and ASN1_item_verify() sets "unknown message
87 * digest algorithm". Populate the table once so a caller that uses n_x509 on
88 * its own, without n_network and therefore without netw_init_openssl(), still
89 * gets certificates that verify. */
90static pthread_once_t n_x509_once = PTHREAD_ONCE_INIT;
91
92static void n_x509_init_openssl(void) {
93 OpenSSL_add_all_algorithms();
94 ERR_load_crypto_strings();
95}
96
98#define N_X509_INIT() ((void)pthread_once(&n_x509_once, n_x509_init_openssl))
99#else
101#define N_X509_INIT() ((void)0)
102#endif
103
105#define N_X509_KEY_BITS 2048
107#define N_X509_SERIAL_BITS 64
109#define N_X509_SAN_BUF 300
110
111/* Copy the contents of a memory BIO into a newly allocated N_STR. Returns 0 on
112 * success, -1 on error. */
113static int bio_to_nstr(BIO* bio, N_STR** out) {
114 BUF_MEM* mem = NULL;
115 BIO_get_mem_ptr(bio, &mem);
116 if (!mem || !mem->data || mem->length == 0)
117 return -1;
118 *out = NULL;
119 if (char_to_nstr_ex(mem->data, mem->length, out) != TRUE || !*out)
120 return -1;
121 return 0;
122}
123
124/* Serialize a private key to a PEM N_STR. Returns 0 on success, -1 on error. */
125static int pkey_to_pem(EVP_PKEY* pkey, N_STR** out) {
126 BIO* bio = BIO_new(BIO_s_mem());
127 int rc = -1;
128 if (!bio)
129 return -1;
130 if (PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL) == 1)
131 rc = bio_to_nstr(bio, out);
132 BIO_free(bio);
133 return rc;
134}
135
136/* Serialize a certificate to a PEM N_STR. Returns 0 on success, -1 on error. */
137static int x509_to_pem(X509* x, N_STR** out) {
138 BIO* bio = BIO_new(BIO_s_mem());
139 int rc = -1;
140 if (!bio)
141 return -1;
142 if (PEM_write_bio_X509(bio, x) == 1)
143 rc = bio_to_nstr(bio, out);
144 BIO_free(bio);
145 return rc;
146}
147
148/* Assign a fresh random serial number to a certificate. 0 on success, -1 on error. */
149static int set_random_serial(X509* x) {
150 BIGNUM* bn = BN_new();
151 int rc = -1;
152 if (!bn)
153 return -1;
154 if (BN_rand(bn, N_X509_SERIAL_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 1) {
155 ASN1_INTEGER* serial = X509_get_serialNumber(x);
156 if (serial && BN_to_ASN1_INTEGER(bn, serial) != NULL)
157 rc = 0;
158 }
159 BN_free(bn);
160 return rc;
161}
162
163/* Add a v3 extension to subject, using issuer for the extension context. */
164static int add_ext(X509* issuer, X509* subject, int nid, const char* value) {
165 X509V3_CTX ctx;
166 X509_EXTENSION* ext;
167 int rc = -1;
168 X509V3_set_ctx_nodb(&ctx);
169 X509V3_set_ctx(&ctx, issuer, subject, NULL, NULL, 0);
170 ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, N_X509_EXT_VALUE(value));
171 if (ext) {
172 if (X509_add_ext(subject, ext, -1) == 1)
173 rc = 0;
174 X509_EXTENSION_free(ext);
175 }
176 return rc;
177}
178
179/* Set the common name (CN) entry on an X509 name. 0 on success, -1 on error. */
180static int set_cn(X509_NAME* name, const char* cn) {
181 if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0) == 1)
182 return 0;
183 return -1;
184}
185
186/* Return 1 if host is a literal IPv4 or IPv6 address, 0 otherwise. */
187static int host_is_ip(const char* host) {
188 unsigned char buf[16];
189 if (inet_pton(AF_INET, host, buf) == 1)
190 return 1;
191 if (inet_pton(AF_INET6, host, buf) == 1)
192 return 1;
193 return 0;
194}
195
196/* Generate an RSA keypair of the given bit size using the portable EVP keygen
197 * API (works across OpenSSL 1.1.1 and 3.x). Returns a new EVP_PKEY or NULL. */
198static EVP_PKEY* gen_rsa(int bits) {
199 EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
200 EVP_PKEY* pkey = NULL;
201 if (!ctx)
202 return NULL;
203 if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0)
204 pkey = NULL;
205 EVP_PKEY_CTX_free(ctx);
206 return pkey;
207}
208
209int n_x509_keypair_pem(int bits, N_STR** key_pem) {
210 EVP_PKEY* pkey;
211 int rc;
212 __n_assert(key_pem, return -1);
213 N_X509_INIT();
214 if (bits <= 0)
215 bits = N_X509_KEY_BITS;
216 pkey = gen_rsa(bits);
217 if (!pkey) {
218 n_log(LOG_ERR, "n_x509: RSA key generation failed (%d bits)", bits);
219 return -1;
220 }
221 rc = pkey_to_pem(pkey, key_pem);
222 EVP_PKEY_free(pkey);
223 return rc;
224}
225
226int n_x509_generate_ca(const char* cn, int days, N_STR** ca_cert_pem, N_STR** ca_key_pem) {
227 EVP_PKEY* pkey = NULL;
228 X509* x = NULL;
229 X509_NAME* name;
230 int rc = -1;
231 __n_assert(cn, return -1);
232 __n_assert(ca_cert_pem, return -1);
233 __n_assert(ca_key_pem, return -1);
234 N_X509_INIT();
235 if (days <= 0)
236 days = 3650;
237
238 pkey = gen_rsa(N_X509_KEY_BITS);
239 if (!pkey) {
240 n_log(LOG_ERR, "n_x509: CA key generation failed");
241 goto cleanup;
242 }
243 x = X509_new();
244 if (!x)
245 goto cleanup;
246 if (X509_set_version(x, 2) != 1)
247 goto cleanup;
248 if (set_random_serial(x) != 0)
249 goto cleanup;
250 X509_gmtime_adj(X509_getm_notBefore(x), 0);
251 X509_gmtime_adj(X509_getm_notAfter(x), (long)days * 86400L);
252 if (X509_set_pubkey(x, pkey) != 1)
253 goto cleanup;
254 name = X509_get_subject_name(x);
255 if (set_cn(name, cn) != 0)
256 goto cleanup;
257 if (X509_set_issuer_name(x, name) != 1)
258 goto cleanup;
259 if (add_ext(x, x, NID_basic_constraints, "critical,CA:TRUE") != 0)
260 goto cleanup;
261 if (add_ext(x, x, NID_key_usage, "critical,keyCertSign,cRLSign") != 0)
262 goto cleanup;
263 if (add_ext(x, x, NID_subject_key_identifier, "hash") != 0)
264 goto cleanup;
265 if (X509_sign(x, pkey, EVP_sha256()) == 0) {
266 n_log(LOG_ERR, "n_x509: CA self-sign failed");
267 goto cleanup;
268 }
269 if (x509_to_pem(x, ca_cert_pem) != 0)
270 goto cleanup;
271 if (pkey_to_pem(pkey, ca_key_pem) != 0) {
272 free_nstr(ca_cert_pem);
273 goto cleanup;
274 }
275 rc = 0;
276
277cleanup:
278 if (x)
279 X509_free(x);
280 if (pkey)
281 EVP_PKEY_free(pkey);
282 return rc;
283}
284
285int n_x509_mint_host_cert(const char* host, const N_STR* ca_cert_pem, const N_STR* ca_key_pem, int days, N_STR** leaf_cert_pem, N_STR** leaf_key_pem) {
286 EVP_PKEY* leaf_key = NULL;
287 EVP_PKEY* ca_key = NULL;
288 X509* ca = NULL;
289 X509* x = NULL;
290 BIO* bio_cert = NULL;
291 BIO* bio_key = NULL;
292 X509_NAME* name;
293 char san[N_X509_SAN_BUF];
294 int rc = -1;
295 __n_assert(host, return -1);
296 __n_assert(ca_cert_pem && ca_cert_pem->data, return -1);
297 __n_assert(ca_key_pem && ca_key_pem->data, return -1);
298 __n_assert(leaf_cert_pem, return -1);
299 __n_assert(leaf_key_pem, return -1);
300 N_X509_INIT();
301 if (days <= 0)
302 days = 825;
303
304 bio_cert = BIO_new_mem_buf(ca_cert_pem->data, (int)ca_cert_pem->written);
305 bio_key = BIO_new_mem_buf(ca_key_pem->data, (int)ca_key_pem->written);
306 if (!bio_cert || !bio_key)
307 goto cleanup;
308 ca = PEM_read_bio_X509(bio_cert, NULL, NULL, NULL);
309 ca_key = PEM_read_bio_PrivateKey(bio_key, NULL, NULL, NULL);
310 if (!ca || !ca_key) {
311 n_log(LOG_ERR, "n_x509: could not parse CA certificate/key");
312 goto cleanup;
313 }
314
315 leaf_key = gen_rsa(N_X509_KEY_BITS);
316 if (!leaf_key)
317 goto cleanup;
318 x = X509_new();
319 if (!x)
320 goto cleanup;
321 if (X509_set_version(x, 2) != 1)
322 goto cleanup;
323 if (set_random_serial(x) != 0)
324 goto cleanup;
325 X509_gmtime_adj(X509_getm_notBefore(x), 0);
326 X509_gmtime_adj(X509_getm_notAfter(x), (long)days * 86400L);
327 if (X509_set_pubkey(x, leaf_key) != 1)
328 goto cleanup;
329 name = X509_get_subject_name(x);
330 if (set_cn(name, host) != 0)
331 goto cleanup;
332 if (X509_set_issuer_name(x, X509_get_subject_name(ca)) != 1)
333 goto cleanup;
334 if (add_ext(ca, x, NID_basic_constraints, "critical,CA:FALSE") != 0)
335 goto cleanup;
336 if (add_ext(ca, x, NID_key_usage, "critical,digitalSignature,keyEncipherment") != 0)
337 goto cleanup;
338 if (add_ext(ca, x, NID_ext_key_usage, "serverAuth") != 0)
339 goto cleanup;
340 snprintf(san, sizeof(san), "%s:%s", host_is_ip(host) ? "IP" : "DNS", host);
341 if (add_ext(ca, x, NID_subject_alt_name, san) != 0)
342 goto cleanup;
343 if (X509_sign(x, ca_key, EVP_sha256()) == 0) {
344 n_log(LOG_ERR, "n_x509: leaf signing failed");
345 goto cleanup;
346 }
347 if (x509_to_pem(x, leaf_cert_pem) != 0)
348 goto cleanup;
349 if (pkey_to_pem(leaf_key, leaf_key_pem) != 0) {
350 free_nstr(leaf_cert_pem);
351 goto cleanup;
352 }
353 rc = 0;
354
355cleanup:
356 if (x)
357 X509_free(x);
358 if (ca)
359 X509_free(ca);
360 if (leaf_key)
361 EVP_PKEY_free(leaf_key);
362 if (ca_key)
363 EVP_PKEY_free(ca_key);
364 if (bio_cert)
365 BIO_free(bio_cert);
366 if (bio_key)
367 BIO_free(bio_key);
368 return rc;
369}
370
371#endif /* HAVE_OPENSSL */
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
int char_to_nstr_ex(const char *from, NSTRBYTE nboct, N_STR **to)
Convert a char into a N_STR, extended version.
Definition n_str.c:239
A box including a string and his lenght.
Definition n_str.h:61
int n_x509_generate_ca(const char *cn, int days, N_STR **ca_cert_pem, N_STR **ca_key_pem)
Generate a self-signed certificate authority (CA) keypair and cert.
Definition n_x509.c:226
int n_x509_keypair_pem(int bits, N_STR **key_pem)
Generate an RSA private key and return it as a PEM string.
Definition n_x509.c:209
int n_x509_mint_host_cert(const char *host, const N_STR *ca_cert_pem, const N_STR *ca_key_pem, int days, N_STR **leaf_cert_pem, N_STR **leaf_key_pem)
Mint a per-host leaf certificate signed by the given CA.
Definition n_x509.c:285
Common headers and low-level functions & define.
Generic log system.
#define N_X509_INIT()
populate the EVP algorithm table once, no-op from 1.1.0 on
Definition n_x509.c:98
static int add_ext(X509 *issuer, X509 *subject, int nid, const char *value)
Definition n_x509.c:164
static int pkey_to_pem(EVP_PKEY *pkey, N_STR **out)
Definition n_x509.c:125
#define X509_getm_notAfter(x)
pre-1.1.0 spelling of the mutable notAfter accessor
Definition n_x509.c:72
static EVP_PKEY * gen_rsa(int bits)
Definition n_x509.c:198
static void n_x509_init_openssl(void)
Definition n_x509.c:92
static int x509_to_pem(X509 *x, N_STR **out)
Definition n_x509.c:137
static pthread_once_t n_x509_once
Definition n_x509.c:90
#define N_X509_SERIAL_BITS
random serial number size in bits
Definition n_x509.c:107
static int set_cn(X509_NAME *name, const char *cn)
Definition n_x509.c:180
#define BN_RAND_TOP_ANY
BN_rand(): leave the most significant bit unconstrained.
Definition n_x509.c:60
#define N_X509_EXT_VALUE(v)
pre-1.1.0 X509V3_EXT_conf_nid() declares value as char*, it does not write to it
Definition n_x509.c:75
static int set_random_serial(X509 *x)
Definition n_x509.c:149
#define N_X509_KEY_BITS
RSA modulus size in bits used for the CA and leaf keys.
Definition n_x509.c:105
static int host_is_ip(const char *host)
Definition n_x509.c:187
#define X509_getm_notBefore(x)
pre-1.1.0 spelling of the mutable notBefore accessor
Definition n_x509.c:68
static int bio_to_nstr(BIO *bio, N_STR **out)
Definition n_x509.c:113
#define N_X509_SAN_BUF
buffer size for a subjectAltName value string
Definition n_x509.c:109
#define BN_RAND_BOTTOM_ANY
BN_rand(): leave the least significant bit unconstrained.
Definition n_x509.c:64
X.509 helpers: self-signed CA generation and per-host leaf minting.