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
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/evp.h>
45#include <openssl/pem.h>
46#include <openssl/rsa.h>
47#include <openssl/x509.h>
48#include <openssl/x509v3.h>
49
51#define N_X509_KEY_BITS 2048
53#define N_X509_SERIAL_BITS 64
55#define N_X509_SAN_BUF 300
56
57/* Copy the contents of a memory BIO into a newly allocated N_STR. Returns 0 on
58 * success, -1 on error. */
59static int bio_to_nstr(BIO* bio, N_STR** out) {
60 BUF_MEM* mem = NULL;
61 BIO_get_mem_ptr(bio, &mem);
62 if (!mem || !mem->data || mem->length == 0)
63 return -1;
64 *out = NULL;
65 if (char_to_nstr_ex(mem->data, mem->length, out) != TRUE || !*out)
66 return -1;
67 return 0;
68}
69
70/* Serialize a private key to a PEM N_STR. Returns 0 on success, -1 on error. */
71static int pkey_to_pem(EVP_PKEY* pkey, N_STR** out) {
72 BIO* bio = BIO_new(BIO_s_mem());
73 int rc = -1;
74 if (!bio)
75 return -1;
76 if (PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL) == 1)
77 rc = bio_to_nstr(bio, out);
78 BIO_free(bio);
79 return rc;
80}
81
82/* Serialize a certificate to a PEM N_STR. Returns 0 on success, -1 on error. */
83static int x509_to_pem(X509* x, N_STR** out) {
84 BIO* bio = BIO_new(BIO_s_mem());
85 int rc = -1;
86 if (!bio)
87 return -1;
88 if (PEM_write_bio_X509(bio, x) == 1)
89 rc = bio_to_nstr(bio, out);
90 BIO_free(bio);
91 return rc;
92}
93
94/* Assign a fresh random serial number to a certificate. 0 on success, -1 on error. */
95static int set_random_serial(X509* x) {
96 BIGNUM* bn = BN_new();
97 int rc = -1;
98 if (!bn)
99 return -1;
100 if (BN_rand(bn, N_X509_SERIAL_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 1) {
101 ASN1_INTEGER* serial = X509_get_serialNumber(x);
102 if (serial && BN_to_ASN1_INTEGER(bn, serial) != NULL)
103 rc = 0;
104 }
105 BN_free(bn);
106 return rc;
107}
108
109/* Add a v3 extension to subject, using issuer for the extension context. */
110static int add_ext(X509* issuer, X509* subject, int nid, const char* value) {
111 X509V3_CTX ctx;
112 X509_EXTENSION* ext;
113 int rc = -1;
114 X509V3_set_ctx_nodb(&ctx);
115 X509V3_set_ctx(&ctx, issuer, subject, NULL, NULL, 0);
116 ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
117 if (ext) {
118 if (X509_add_ext(subject, ext, -1) == 1)
119 rc = 0;
120 X509_EXTENSION_free(ext);
121 }
122 return rc;
123}
124
125/* Set the common name (CN) entry on an X509 name. 0 on success, -1 on error. */
126static int set_cn(X509_NAME* name, const char* cn) {
127 if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0) == 1)
128 return 0;
129 return -1;
130}
131
132/* Return 1 if host is a literal IPv4 or IPv6 address, 0 otherwise. */
133static int host_is_ip(const char* host) {
134 unsigned char buf[16];
135 if (inet_pton(AF_INET, host, buf) == 1)
136 return 1;
137 if (inet_pton(AF_INET6, host, buf) == 1)
138 return 1;
139 return 0;
140}
141
142/* Generate an RSA keypair of the given bit size using the portable EVP keygen
143 * API (works across OpenSSL 1.1.1 and 3.x). Returns a new EVP_PKEY or NULL. */
144static EVP_PKEY* gen_rsa(int bits) {
145 EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
146 EVP_PKEY* pkey = NULL;
147 if (!ctx)
148 return NULL;
149 if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0)
150 pkey = NULL;
151 EVP_PKEY_CTX_free(ctx);
152 return pkey;
153}
154
155int n_x509_keypair_pem(int bits, N_STR** key_pem) {
156 EVP_PKEY* pkey;
157 int rc;
158 __n_assert(key_pem, return -1);
159 if (bits <= 0)
160 bits = N_X509_KEY_BITS;
161 pkey = gen_rsa(bits);
162 if (!pkey) {
163 n_log(LOG_ERR, "n_x509: RSA key generation failed (%d bits)", bits);
164 return -1;
165 }
166 rc = pkey_to_pem(pkey, key_pem);
167 EVP_PKEY_free(pkey);
168 return rc;
169}
170
171int n_x509_generate_ca(const char* cn, int days, N_STR** ca_cert_pem, N_STR** ca_key_pem) {
172 EVP_PKEY* pkey = NULL;
173 X509* x = NULL;
174 X509_NAME* name;
175 int rc = -1;
176 __n_assert(cn, return -1);
177 __n_assert(ca_cert_pem, return -1);
178 __n_assert(ca_key_pem, return -1);
179 if (days <= 0)
180 days = 3650;
181
182 pkey = gen_rsa(N_X509_KEY_BITS);
183 if (!pkey) {
184 n_log(LOG_ERR, "n_x509: CA key generation failed");
185 goto cleanup;
186 }
187 x = X509_new();
188 if (!x)
189 goto cleanup;
190 if (X509_set_version(x, 2) != 1)
191 goto cleanup;
192 if (set_random_serial(x) != 0)
193 goto cleanup;
194 X509_gmtime_adj(X509_getm_notBefore(x), 0);
195 X509_gmtime_adj(X509_getm_notAfter(x), (long)days * 86400L);
196 if (X509_set_pubkey(x, pkey) != 1)
197 goto cleanup;
198 name = X509_get_subject_name(x);
199 if (set_cn(name, cn) != 0)
200 goto cleanup;
201 if (X509_set_issuer_name(x, name) != 1)
202 goto cleanup;
203 if (add_ext(x, x, NID_basic_constraints, "critical,CA:TRUE") != 0)
204 goto cleanup;
205 if (add_ext(x, x, NID_key_usage, "critical,keyCertSign,cRLSign") != 0)
206 goto cleanup;
207 if (add_ext(x, x, NID_subject_key_identifier, "hash") != 0)
208 goto cleanup;
209 if (X509_sign(x, pkey, EVP_sha256()) == 0) {
210 n_log(LOG_ERR, "n_x509: CA self-sign failed");
211 goto cleanup;
212 }
213 if (x509_to_pem(x, ca_cert_pem) != 0)
214 goto cleanup;
215 if (pkey_to_pem(pkey, ca_key_pem) != 0) {
216 free_nstr(ca_cert_pem);
217 goto cleanup;
218 }
219 rc = 0;
220
221cleanup:
222 if (x)
223 X509_free(x);
224 if (pkey)
225 EVP_PKEY_free(pkey);
226 return rc;
227}
228
229int 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) {
230 EVP_PKEY* leaf_key = NULL;
231 EVP_PKEY* ca_key = NULL;
232 X509* ca = NULL;
233 X509* x = NULL;
234 BIO* bio_cert = NULL;
235 BIO* bio_key = NULL;
236 X509_NAME* name;
237 char san[N_X509_SAN_BUF];
238 int rc = -1;
239 __n_assert(host, return -1);
240 __n_assert(ca_cert_pem && ca_cert_pem->data, return -1);
241 __n_assert(ca_key_pem && ca_key_pem->data, return -1);
242 __n_assert(leaf_cert_pem, return -1);
243 __n_assert(leaf_key_pem, return -1);
244 if (days <= 0)
245 days = 825;
246
247 bio_cert = BIO_new_mem_buf(ca_cert_pem->data, (int)ca_cert_pem->written);
248 bio_key = BIO_new_mem_buf(ca_key_pem->data, (int)ca_key_pem->written);
249 if (!bio_cert || !bio_key)
250 goto cleanup;
251 ca = PEM_read_bio_X509(bio_cert, NULL, NULL, NULL);
252 ca_key = PEM_read_bio_PrivateKey(bio_key, NULL, NULL, NULL);
253 if (!ca || !ca_key) {
254 n_log(LOG_ERR, "n_x509: could not parse CA certificate/key");
255 goto cleanup;
256 }
257
258 leaf_key = gen_rsa(N_X509_KEY_BITS);
259 if (!leaf_key)
260 goto cleanup;
261 x = X509_new();
262 if (!x)
263 goto cleanup;
264 if (X509_set_version(x, 2) != 1)
265 goto cleanup;
266 if (set_random_serial(x) != 0)
267 goto cleanup;
268 X509_gmtime_adj(X509_getm_notBefore(x), 0);
269 X509_gmtime_adj(X509_getm_notAfter(x), (long)days * 86400L);
270 if (X509_set_pubkey(x, leaf_key) != 1)
271 goto cleanup;
272 name = X509_get_subject_name(x);
273 if (set_cn(name, host) != 0)
274 goto cleanup;
275 if (X509_set_issuer_name(x, X509_get_subject_name(ca)) != 1)
276 goto cleanup;
277 if (add_ext(ca, x, NID_basic_constraints, "critical,CA:FALSE") != 0)
278 goto cleanup;
279 if (add_ext(ca, x, NID_key_usage, "critical,digitalSignature,keyEncipherment") != 0)
280 goto cleanup;
281 if (add_ext(ca, x, NID_ext_key_usage, "serverAuth") != 0)
282 goto cleanup;
283 snprintf(san, sizeof(san), "%s:%s", host_is_ip(host) ? "IP" : "DNS", host);
284 if (add_ext(ca, x, NID_subject_alt_name, san) != 0)
285 goto cleanup;
286 if (X509_sign(x, ca_key, EVP_sha256()) == 0) {
287 n_log(LOG_ERR, "n_x509: leaf signing failed");
288 goto cleanup;
289 }
290 if (x509_to_pem(x, leaf_cert_pem) != 0)
291 goto cleanup;
292 if (pkey_to_pem(leaf_key, leaf_key_pem) != 0) {
293 free_nstr(leaf_cert_pem);
294 goto cleanup;
295 }
296 rc = 0;
297
298cleanup:
299 if (x)
300 X509_free(x);
301 if (ca)
302 X509_free(ca);
303 if (leaf_key)
304 EVP_PKEY_free(leaf_key);
305 if (ca_key)
306 EVP_PKEY_free(ca_key);
307 if (bio_cert)
308 BIO_free(bio_cert);
309 if (bio_key)
310 BIO_free(bio_key);
311 return rc;
312}
313
314#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:89
#define LOG_ERR
error conditions
Definition n_log.h:76
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:232
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:171
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:155
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:229
Common headers and low-level functions & define.
Generic log system.
static int add_ext(X509 *issuer, X509 *subject, int nid, const char *value)
Definition n_x509.c:110
static int pkey_to_pem(EVP_PKEY *pkey, N_STR **out)
Definition n_x509.c:71
static EVP_PKEY * gen_rsa(int bits)
Definition n_x509.c:144
static int x509_to_pem(X509 *x, N_STR **out)
Definition n_x509.c:83
#define N_X509_SERIAL_BITS
random serial number size in bits
Definition n_x509.c:53
static int set_cn(X509_NAME *name, const char *cn)
Definition n_x509.c:126
static int set_random_serial(X509 *x)
Definition n_x509.c:95
#define N_X509_KEY_BITS
RSA modulus size in bits used for the CA and leaf keys.
Definition n_x509.c:51
static int host_is_ip(const char *host)
Definition n_x509.c:133
static int bio_to_nstr(BIO *bio, N_STR **out)
Definition n_x509.c:59
#define N_X509_SAN_BUF
buffer size for a subjectAltName value string
Definition n_x509.c:55
X.509 helpers: self-signed CA generation and per-host leaf minting.