William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 1 | /* |
| 2 | * Utility functions for SSL: |
| 3 | * Mostly generic functions that retrieve information from certificates |
| 4 | * |
| 5 | * Copyright (C) 2012 EXCELIANCE, Emeric Brun <ebrun@exceliance.fr> |
| 6 | * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com> |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU General Public License |
| 10 | * as published by the Free Software Foundation; either version |
| 11 | * 2 of the License, or (at your option) any later version. |
| 12 | */ |
| 13 | |
| 14 | |
Willy Tarreau | 2741c8c | 2020-06-02 11:28:02 +0200 | [diff] [blame] | 15 | #include <haproxy/api.h> |
| 16 | #include <haproxy/buf-t.h> |
Willy Tarreau | cb72b7e | 2021-05-08 12:52:56 +0200 | [diff] [blame] | 17 | #include <haproxy/chunk.h> |
Willy Tarreau | 6019fab | 2020-05-27 16:26:00 +0200 | [diff] [blame] | 18 | #include <haproxy/openssl-compat.h> |
Willy Tarreau | 209108d | 2020-06-04 20:30:20 +0200 | [diff] [blame] | 19 | #include <haproxy/ssl_sock.h> |
Tim Duesterhus | 2a0688a | 2022-05-14 22:15:26 +0200 | [diff] [blame] | 20 | #include <haproxy/ssl_utils.h> |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 21 | |
| 22 | /* fill a buffer with the algorithm and size of a public key */ |
| 23 | int cert_get_pkey_algo(X509 *crt, struct buffer *out) |
| 24 | { |
| 25 | int bits = 0; |
| 26 | int sig = TLSEXT_signature_anonymous; |
| 27 | int len = -1; |
| 28 | EVP_PKEY *pkey; |
| 29 | |
| 30 | pkey = X509_get_pubkey(crt); |
| 31 | if (pkey) { |
| 32 | bits = EVP_PKEY_bits(pkey); |
| 33 | switch(EVP_PKEY_base_id(pkey)) { |
| 34 | case EVP_PKEY_RSA: |
| 35 | sig = TLSEXT_signature_rsa; |
| 36 | break; |
| 37 | case EVP_PKEY_EC: |
| 38 | sig = TLSEXT_signature_ecdsa; |
| 39 | break; |
| 40 | case EVP_PKEY_DSA: |
| 41 | sig = TLSEXT_signature_dsa; |
| 42 | break; |
| 43 | } |
| 44 | EVP_PKEY_free(pkey); |
| 45 | } |
| 46 | |
| 47 | switch(sig) { |
| 48 | case TLSEXT_signature_rsa: |
| 49 | len = chunk_printf(out, "RSA%d", bits); |
| 50 | break; |
| 51 | case TLSEXT_signature_ecdsa: |
| 52 | len = chunk_printf(out, "EC%d", bits); |
| 53 | break; |
| 54 | case TLSEXT_signature_dsa: |
| 55 | len = chunk_printf(out, "DSA%d", bits); |
| 56 | break; |
| 57 | default: |
| 58 | return 0; |
| 59 | } |
| 60 | if (len < 0) |
| 61 | return 0; |
| 62 | return 1; |
| 63 | } |
| 64 | |
| 65 | /* Extract a serial from a cert, and copy it to a chunk. |
| 66 | * Returns 1 if serial is found and copied, 0 if no serial found and |
| 67 | * -1 if output is not large enough. |
| 68 | */ |
| 69 | int ssl_sock_get_serial(X509 *crt, struct buffer *out) |
| 70 | { |
| 71 | ASN1_INTEGER *serial; |
| 72 | |
| 73 | serial = X509_get_serialNumber(crt); |
| 74 | if (!serial) |
| 75 | return 0; |
| 76 | |
| 77 | if (out->size < serial->length) |
| 78 | return -1; |
| 79 | |
| 80 | memcpy(out->area, serial->data, serial->length); |
| 81 | out->data = serial->length; |
| 82 | return 1; |
| 83 | } |
| 84 | |
| 85 | /* Extract a cert to der, and copy it to a chunk. |
| 86 | * Returns 1 if the cert is found and copied, 0 on der conversion failure |
| 87 | * and -1 if the output is not large enough. |
| 88 | */ |
| 89 | int ssl_sock_crt2der(X509 *crt, struct buffer *out) |
| 90 | { |
| 91 | int len; |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 92 | unsigned char *p = (unsigned char *) out->area; |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 93 | |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 94 | len = i2d_X509(crt, NULL); |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 95 | if (len <= 0) |
| 96 | return 1; |
| 97 | |
| 98 | if (out->size < len) |
| 99 | return -1; |
| 100 | |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 101 | i2d_X509(crt, &p); |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 102 | out->data = len; |
| 103 | return 1; |
| 104 | } |
| 105 | |
| 106 | |
| 107 | /* Copy Date in ASN1_UTCTIME format in struct buffer out. |
| 108 | * Returns 1 if serial is found and copied, 0 if no valid time found |
| 109 | * and -1 if output is not large enough. |
| 110 | */ |
| 111 | int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out) |
| 112 | { |
| 113 | if (tm->type == V_ASN1_GENERALIZEDTIME) { |
| 114 | ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm; |
| 115 | |
| 116 | if (gentm->length < 12) |
| 117 | return 0; |
| 118 | if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30) |
| 119 | return 0; |
| 120 | if (out->size < gentm->length-2) |
| 121 | return -1; |
| 122 | |
| 123 | memcpy(out->area, gentm->data+2, gentm->length-2); |
| 124 | out->data = gentm->length-2; |
| 125 | return 1; |
| 126 | } |
| 127 | else if (tm->type == V_ASN1_UTCTIME) { |
| 128 | ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm; |
| 129 | |
| 130 | if (utctm->length < 10) |
| 131 | return 0; |
| 132 | if (utctm->data[0] >= 0x35) |
| 133 | return 0; |
| 134 | if (out->size < utctm->length) |
| 135 | return -1; |
| 136 | |
| 137 | memcpy(out->area, utctm->data, utctm->length); |
| 138 | out->data = utctm->length; |
| 139 | return 1; |
| 140 | } |
| 141 | |
| 142 | return 0; |
| 143 | } |
| 144 | |
| 145 | /* Extract an entry from a X509_NAME and copy its value to an output chunk. |
| 146 | * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough. |
| 147 | */ |
| 148 | int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, |
| 149 | struct buffer *out) |
| 150 | { |
| 151 | X509_NAME_ENTRY *ne; |
| 152 | ASN1_OBJECT *obj; |
| 153 | ASN1_STRING *data; |
| 154 | const unsigned char *data_ptr; |
| 155 | int data_len; |
| 156 | int i, j, n; |
| 157 | int cur = 0; |
| 158 | const char *s; |
| 159 | char tmp[128]; |
| 160 | int name_count; |
| 161 | |
| 162 | name_count = X509_NAME_entry_count(a); |
| 163 | |
| 164 | out->data = 0; |
| 165 | for (i = 0; i < name_count; i++) { |
| 166 | if (pos < 0) |
| 167 | j = (name_count-1) - i; |
| 168 | else |
| 169 | j = i; |
| 170 | |
| 171 | ne = X509_NAME_get_entry(a, j); |
| 172 | obj = X509_NAME_ENTRY_get_object(ne); |
| 173 | data = X509_NAME_ENTRY_get_data(ne); |
| 174 | data_ptr = ASN1_STRING_get0_data(data); |
| 175 | data_len = ASN1_STRING_length(data); |
| 176 | n = OBJ_obj2nid(obj); |
| 177 | if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| 178 | i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| 179 | s = tmp; |
| 180 | } |
| 181 | |
| 182 | if (chunk_strcasecmp(entry, s) != 0) |
| 183 | continue; |
| 184 | |
| 185 | if (pos < 0) |
| 186 | cur--; |
| 187 | else |
| 188 | cur++; |
| 189 | |
| 190 | if (cur != pos) |
| 191 | continue; |
| 192 | |
| 193 | if (data_len > out->size) |
| 194 | return -1; |
| 195 | |
| 196 | memcpy(out->area, data_ptr, data_len); |
| 197 | out->data = data_len; |
| 198 | return 1; |
| 199 | } |
| 200 | |
| 201 | return 0; |
| 202 | |
| 203 | } |
| 204 | |
| 205 | /* |
| 206 | * Extract the DN in the specified format from the X509_NAME and copy result to a chunk. |
| 207 | * Currently supports rfc2253 for returning LDAP V3 DNs. |
| 208 | * Returns 1 if dn entries exist, 0 if no dn entry was found. |
| 209 | */ |
| 210 | int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out) |
| 211 | { |
| 212 | BIO *bio = NULL; |
| 213 | int ret = 0; |
| 214 | int data_len = 0; |
| 215 | |
| 216 | if (chunk_strcmp(format, "rfc2253") == 0) { |
| 217 | bio = BIO_new(BIO_s_mem()); |
| 218 | if (bio == NULL) |
| 219 | goto out; |
| 220 | |
| 221 | if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0) |
| 222 | goto out; |
| 223 | |
| 224 | if ((data_len = BIO_read(bio, out->area, out->size)) <= 0) |
| 225 | goto out; |
| 226 | |
| 227 | out->data = data_len; |
| 228 | |
| 229 | ret = 1; |
| 230 | } |
| 231 | out: |
| 232 | if (bio) |
| 233 | BIO_free(bio); |
| 234 | return ret; |
| 235 | } |
| 236 | |
| 237 | /* Extract and format full DN from a X509_NAME and copy result into a chunk |
| 238 | * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. |
| 239 | */ |
| 240 | int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out) |
| 241 | { |
| 242 | X509_NAME_ENTRY *ne; |
| 243 | ASN1_OBJECT *obj; |
| 244 | ASN1_STRING *data; |
| 245 | const unsigned char *data_ptr; |
| 246 | int data_len; |
| 247 | int i, n, ln; |
| 248 | int l = 0; |
| 249 | const char *s; |
| 250 | char *p; |
| 251 | char tmp[128]; |
| 252 | int name_count; |
| 253 | |
| 254 | |
| 255 | name_count = X509_NAME_entry_count(a); |
| 256 | |
| 257 | out->data = 0; |
| 258 | p = out->area; |
| 259 | for (i = 0; i < name_count; i++) { |
| 260 | ne = X509_NAME_get_entry(a, i); |
| 261 | obj = X509_NAME_ENTRY_get_object(ne); |
| 262 | data = X509_NAME_ENTRY_get_data(ne); |
| 263 | data_ptr = ASN1_STRING_get0_data(data); |
| 264 | data_len = ASN1_STRING_length(data); |
| 265 | n = OBJ_obj2nid(obj); |
| 266 | if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| 267 | i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| 268 | s = tmp; |
| 269 | } |
| 270 | ln = strlen(s); |
| 271 | |
| 272 | l += 1 + ln + 1 + data_len; |
| 273 | if (l > out->size) |
| 274 | return -1; |
| 275 | out->data = l; |
| 276 | |
| 277 | *(p++)='/'; |
| 278 | memcpy(p, s, ln); |
| 279 | p += ln; |
| 280 | *(p++)='='; |
| 281 | memcpy(p, data_ptr, data_len); |
| 282 | p += data_len; |
| 283 | } |
| 284 | |
| 285 | if (!out->data) |
| 286 | return 0; |
| 287 | |
| 288 | return 1; |
| 289 | } |
| 290 | |
Remi Tricot-Le Breton | 74f6ab6 | 2021-08-19 18:06:30 +0200 | [diff] [blame] | 291 | |
| 292 | extern int ssl_client_crt_ref_index; |
| 293 | |
| 294 | /* |
| 295 | * This function fetches the SSL certificate for a specific connection (either |
| 296 | * client certificate or server certificate depending on the cert_peer |
| 297 | * parameter). |
| 298 | * When trying to get the peer certificate from the server side, we first try to |
| 299 | * use the dedicated SSL_get_peer_certificate function, but we fall back to |
| 300 | * trying to get the client certificate reference that might have been stored in |
| 301 | * the SSL structure's ex_data during the verification process. |
| 302 | * Returns NULL in case of failure. |
| 303 | */ |
| 304 | X509* ssl_sock_get_peer_certificate(SSL *ssl) |
| 305 | { |
| 306 | X509* cert; |
| 307 | |
| 308 | cert = SSL_get_peer_certificate(ssl); |
| 309 | /* Get the client certificate reference stored in the SSL |
| 310 | * structure's ex_data during the verification process. */ |
| 311 | if (!cert) { |
| 312 | cert = SSL_get_ex_data(ssl, ssl_client_crt_ref_index); |
| 313 | if (cert) |
| 314 | X509_up_ref(cert); |
| 315 | } |
| 316 | |
| 317 | return cert; |
| 318 | } |
William Lallemand | 44d862d | 2021-08-21 23:16:06 +0200 | [diff] [blame] | 319 | |
| 320 | /* |
Abhijeet Rastogi | df97f47 | 2023-05-13 20:04:45 -0700 | [diff] [blame] | 321 | * This function fetches the x509* for the root CA of client certificate |
| 322 | * from the verified chain. We use the SSL_get0_verified_chain and get the |
| 323 | * last certificate in the x509 stack. |
| 324 | * |
| 325 | * Returns NULL in case of failure. |
| 326 | */ |
William Lallemand | 6e0c39d | 2023-05-15 12:05:55 +0200 | [diff] [blame] | 327 | #ifdef HAVE_SSL_get0_verified_chain |
Abhijeet Rastogi | df97f47 | 2023-05-13 20:04:45 -0700 | [diff] [blame] | 328 | X509* ssl_sock_get_verified_chain_root(SSL *ssl) |
| 329 | { |
| 330 | STACK_OF(X509) *chain = NULL; |
| 331 | X509 *crt = NULL; |
| 332 | int i; |
| 333 | |
| 334 | chain = SSL_get0_verified_chain(ssl); |
| 335 | if (!chain) |
| 336 | return NULL; |
| 337 | |
| 338 | for (i = 0; i < sk_X509_num(chain); i++) { |
| 339 | crt = sk_X509_value(chain, i); |
| 340 | |
| 341 | if (X509_check_issued(crt, crt) == X509_V_OK) |
| 342 | break; |
| 343 | } |
| 344 | |
| 345 | return crt; |
| 346 | } |
William Lallemand | 6e0c39d | 2023-05-15 12:05:55 +0200 | [diff] [blame] | 347 | #endif |
Abhijeet Rastogi | df97f47 | 2023-05-13 20:04:45 -0700 | [diff] [blame] | 348 | |
| 349 | /* |
William Lallemand | 44d862d | 2021-08-21 23:16:06 +0200 | [diff] [blame] | 350 | * Take an OpenSSL version in text format and return a numeric openssl version |
| 351 | * Return 0 if it failed to parse the version |
| 352 | * |
| 353 | * https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_VERSION_NUMBER.html |
| 354 | * |
| 355 | * MNNFFPPS: major minor fix patch status |
| 356 | * |
| 357 | * The status nibble has one of the values 0 for development, 1 to e for betas |
| 358 | * 1 to 14, and f for release. |
| 359 | * |
| 360 | * for example |
| 361 | * |
| 362 | * 0x0090821f 0.9.8zh |
| 363 | * 0x1000215f 1.0.2u |
| 364 | * 0x30000000 3.0.0-alpha17 |
| 365 | * 0x30000002 3.0.0-beta2 |
| 366 | * 0x3000000e 3.0.0-beta14 |
| 367 | * 0x3000000f 3.0.0 |
| 368 | */ |
| 369 | unsigned int openssl_version_parser(const char *version) |
| 370 | { |
| 371 | unsigned int numversion; |
| 372 | unsigned int major = 0, minor = 0, fix = 0, patch = 0, status = 0; |
| 373 | char *p, *end; |
| 374 | |
| 375 | p = (char *)version; |
| 376 | |
| 377 | if (!p || !*p) |
| 378 | return 0; |
| 379 | |
| 380 | major = strtol(p, &end, 10); |
| 381 | if (*end != '.' || major > 0xf) |
| 382 | goto error; |
| 383 | p = end + 1; |
| 384 | |
| 385 | minor = strtol(p, &end, 10); |
| 386 | if (*end != '.' || minor > 0xff) |
| 387 | goto error; |
| 388 | p = end + 1; |
| 389 | |
| 390 | fix = strtol(p, &end, 10); |
| 391 | if (fix > 0xff) |
| 392 | goto error; |
| 393 | p = end; |
| 394 | |
| 395 | if (!*p) { |
| 396 | /* end of the string, that's a release */ |
| 397 | status = 0xf; |
| 398 | } else if (*p == '-') { |
| 399 | /* after the hyphen, only the beta will increment the status |
| 400 | * counter, all others versions will be considered as "dev" and |
| 401 | * does not increment anything */ |
| 402 | p++; |
| 403 | |
| 404 | if (!strncmp(p, "beta", 4)) { |
| 405 | p += 4; |
William Lallemand | 8b673f0 | 2021-08-22 13:36:11 +0200 | [diff] [blame] | 406 | status = strtol(p, &end, 10); |
| 407 | if (status > 14) |
| 408 | goto error; |
William Lallemand | 44d862d | 2021-08-21 23:16:06 +0200 | [diff] [blame] | 409 | } |
| 410 | } else { |
| 411 | /* that's a patch release */ |
| 412 | patch = 1; |
| 413 | |
| 414 | /* add the value of each letter */ |
| 415 | while (*p) { |
| 416 | patch += (*p & ~0x20) - 'A'; |
| 417 | p++; |
| 418 | } |
| 419 | status = 0xf; |
| 420 | } |
| 421 | |
| 422 | end: |
| 423 | numversion = ((major & 0xf) << 28) | ((minor & 0xff) << 20) | ((fix & 0xff) << 12) | ((patch & 0xff) << 4) | (status & 0xf); |
| 424 | return numversion; |
| 425 | |
| 426 | error: |
| 427 | return 0; |
| 428 | |
| 429 | } |
Marcin Deranek | 959a48c | 2021-07-13 15:14:21 +0200 | [diff] [blame] | 430 | |
| 431 | /* Exclude GREASE (RFC8701) values from input buffer */ |
| 432 | void exclude_tls_grease(char *input, int len, struct buffer *output) |
| 433 | { |
| 434 | int ptr = 0; |
| 435 | |
| 436 | while (ptr < len - 1) { |
| 437 | if (input[ptr] != input[ptr+1] || (input[ptr] & 0x0f) != 0x0a) { |
| 438 | if (output->data <= output->size - 2) { |
| 439 | memcpy(output->area + output->data, input + ptr, 2); |
| 440 | output->data += 2; |
| 441 | } else |
| 442 | break; |
| 443 | } |
| 444 | ptr += 2; |
| 445 | } |
| 446 | if (output->size - output->data > 0 && len - ptr > 0) |
| 447 | output->area[output->data++] = input[ptr]; |
| 448 | } |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 449 | |
| 450 | /* |
| 451 | * The following generates an array <x509_v_codes> in which the X509_V_ERR_* |
| 452 | * codes are populated with there string equivalent. Depending on the version |
| 453 | * of the SSL library, some code does not exist, these will be populated as |
| 454 | * "-1" in the array. |
| 455 | * |
| 456 | * The list was taken from |
| 457 | * https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| 458 | * and must be updated when new constant are introduced. |
| 459 | */ |
| 460 | |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 461 | #undef _Q |
Willy Tarreau | a8a83bc | 2022-11-14 08:11:32 +0100 | [diff] [blame] | 462 | #define _Q(x) (#x) |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 463 | #undef V |
Willy Tarreau | a8a83bc | 2022-11-14 08:11:32 +0100 | [diff] [blame] | 464 | #define V(x) { .code = -1, .value = _Q(x), .string = #x } |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 465 | |
Willy Tarreau | a8a83bc | 2022-11-14 08:11:32 +0100 | [diff] [blame] | 466 | static struct x509_v_codes { |
| 467 | int code; // integer value of the code or -1 if undefined |
| 468 | const char *value; // value of the macro as a string or its name |
| 469 | const char *string; // name of the macro |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 470 | } x509_v_codes[] = { |
| 471 | V(X509_V_OK), |
| 472 | V(X509_V_ERR_UNSPECIFIED), |
| 473 | V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT), |
| 474 | V(X509_V_ERR_UNABLE_TO_GET_CRL), |
| 475 | V(X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE), |
| 476 | V(X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE), |
| 477 | V(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY), |
| 478 | V(X509_V_ERR_CERT_SIGNATURE_FAILURE), |
| 479 | V(X509_V_ERR_CRL_SIGNATURE_FAILURE), |
| 480 | V(X509_V_ERR_CERT_NOT_YET_VALID), |
| 481 | V(X509_V_ERR_CERT_HAS_EXPIRED), |
| 482 | V(X509_V_ERR_CRL_NOT_YET_VALID), |
| 483 | V(X509_V_ERR_CRL_HAS_EXPIRED), |
| 484 | V(X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD), |
| 485 | V(X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD), |
| 486 | V(X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD), |
| 487 | V(X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD), |
| 488 | V(X509_V_ERR_OUT_OF_MEM), |
| 489 | V(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT), |
| 490 | V(X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN), |
| 491 | V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY), |
| 492 | V(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE), |
| 493 | V(X509_V_ERR_CERT_CHAIN_TOO_LONG), |
| 494 | V(X509_V_ERR_CERT_REVOKED), |
| 495 | V(X509_V_ERR_NO_ISSUER_PUBLIC_KEY), |
| 496 | V(X509_V_ERR_PATH_LENGTH_EXCEEDED), |
| 497 | V(X509_V_ERR_INVALID_PURPOSE), |
| 498 | V(X509_V_ERR_CERT_UNTRUSTED), |
| 499 | V(X509_V_ERR_CERT_REJECTED), |
| 500 | V(X509_V_ERR_SUBJECT_ISSUER_MISMATCH), |
| 501 | V(X509_V_ERR_AKID_SKID_MISMATCH), |
| 502 | V(X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH), |
| 503 | V(X509_V_ERR_KEYUSAGE_NO_CERTSIGN), |
| 504 | V(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER), |
| 505 | V(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION), |
| 506 | V(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN), |
| 507 | V(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION), |
| 508 | V(X509_V_ERR_INVALID_NON_CA), |
| 509 | V(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED), |
| 510 | V(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE), |
| 511 | V(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED), |
| 512 | V(X509_V_ERR_INVALID_EXTENSION), |
| 513 | V(X509_V_ERR_INVALID_POLICY_EXTENSION), |
| 514 | V(X509_V_ERR_NO_EXPLICIT_POLICY), |
| 515 | V(X509_V_ERR_DIFFERENT_CRL_SCOPE), |
| 516 | V(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE), |
| 517 | V(X509_V_ERR_UNNESTED_RESOURCE), |
| 518 | V(X509_V_ERR_PERMITTED_VIOLATION), |
| 519 | V(X509_V_ERR_EXCLUDED_VIOLATION), |
| 520 | V(X509_V_ERR_SUBTREE_MINMAX), |
| 521 | V(X509_V_ERR_APPLICATION_VERIFICATION), |
| 522 | V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE), |
| 523 | V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX), |
| 524 | V(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX), |
| 525 | V(X509_V_ERR_CRL_PATH_VALIDATION_ERROR), |
| 526 | V(X509_V_ERR_PATH_LOOP), |
| 527 | V(X509_V_ERR_SUITE_B_INVALID_VERSION), |
| 528 | V(X509_V_ERR_SUITE_B_INVALID_ALGORITHM), |
| 529 | V(X509_V_ERR_SUITE_B_INVALID_CURVE), |
| 530 | V(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM), |
| 531 | V(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED), |
| 532 | V(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256), |
| 533 | V(X509_V_ERR_HOSTNAME_MISMATCH), |
| 534 | V(X509_V_ERR_EMAIL_MISMATCH), |
| 535 | V(X509_V_ERR_IP_ADDRESS_MISMATCH), |
| 536 | V(X509_V_ERR_DANE_NO_MATCH), |
| 537 | V(X509_V_ERR_EE_KEY_TOO_SMALL), |
| 538 | V(X509_V_ERR_CA_KEY_TOO_SMALL), |
| 539 | V(X509_V_ERR_CA_MD_TOO_WEAK), |
| 540 | V(X509_V_ERR_INVALID_CALL), |
| 541 | V(X509_V_ERR_STORE_LOOKUP), |
| 542 | V(X509_V_ERR_NO_VALID_SCTS), |
| 543 | V(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION), |
| 544 | V(X509_V_ERR_OCSP_VERIFY_NEEDED), |
| 545 | V(X509_V_ERR_OCSP_VERIFY_FAILED), |
| 546 | V(X509_V_ERR_OCSP_CERT_UNKNOWN), |
| 547 | V(X509_V_ERR_UNSUPPORTED_SIGNATURE_ALGORITHM), |
| 548 | V(X509_V_ERR_SIGNATURE_ALGORITHM_MISMATCH), |
| 549 | V(X509_V_ERR_SIGNATURE_ALGORITHM_INCONSISTENCY), |
| 550 | V(X509_V_ERR_INVALID_CA), |
| 551 | V(X509_V_ERR_PATHLEN_INVALID_FOR_NON_CA), |
| 552 | V(X509_V_ERR_PATHLEN_WITHOUT_KU_KEY_CERT_SIGN), |
| 553 | V(X509_V_ERR_KU_KEY_CERT_SIGN_INVALID_FOR_NON_CA), |
| 554 | V(X509_V_ERR_ISSUER_NAME_EMPTY), |
| 555 | V(X509_V_ERR_SUBJECT_NAME_EMPTY), |
| 556 | V(X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER), |
| 557 | V(X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER), |
| 558 | V(X509_V_ERR_EMPTY_SUBJECT_ALT_NAME), |
| 559 | V(X509_V_ERR_EMPTY_SUBJECT_SAN_NOT_CRITICAL), |
| 560 | V(X509_V_ERR_CA_BCONS_NOT_CRITICAL), |
| 561 | V(X509_V_ERR_AUTHORITY_KEY_IDENTIFIER_CRITICAL), |
| 562 | V(X509_V_ERR_SUBJECT_KEY_IDENTIFIER_CRITICAL), |
| 563 | V(X509_V_ERR_CA_CERT_MISSING_KEY_USAGE), |
| 564 | V(X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3), |
| 565 | V(X509_V_ERR_EC_KEY_EXPLICIT_PARAMS), |
Willy Tarreau | a8a83bc | 2022-11-14 08:11:32 +0100 | [diff] [blame] | 566 | { 0, NULL, NULL }, |
William Lallemand | 960fb74 | 2022-11-03 16:31:50 +0100 | [diff] [blame] | 567 | }; |
| 568 | |
| 569 | /* |
| 570 | * Return the X509_V_ERR code corresponding to the name of the constant. |
| 571 | * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| 572 | * If not found, return -1 |
| 573 | */ |
| 574 | int x509_v_err_str_to_int(const char *str) |
| 575 | { |
| 576 | int i; |
| 577 | |
| 578 | for (i = 0; x509_v_codes[i].string; i++) { |
| 579 | if (strcmp(str, x509_v_codes[i].string) == 0) { |
| 580 | return x509_v_codes[i].code; |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | return -1; |
| 585 | } |
| 586 | |
| 587 | /* |
| 588 | * Return the constant name corresponding to the X509_V_ERR code |
| 589 | * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| 590 | * If not found, return NULL; |
| 591 | */ |
| 592 | const char *x509_v_err_int_to_str(int code) |
| 593 | { |
| 594 | int i; |
| 595 | |
| 596 | if (code == -1) |
| 597 | return NULL; |
| 598 | |
| 599 | for (i = 0; x509_v_codes[i].string; i++) { |
| 600 | if (x509_v_codes[i].code == code) { |
| 601 | return x509_v_codes[i].string; |
| 602 | } |
| 603 | } |
| 604 | return NULL; |
| 605 | } |
Willy Tarreau | a8a83bc | 2022-11-14 08:11:32 +0100 | [diff] [blame] | 606 | |
| 607 | void init_x509_v_err_tab(void) |
| 608 | { |
| 609 | int i; |
| 610 | |
| 611 | for (i = 0; x509_v_codes[i].string; i++) { |
| 612 | /* either the macro exists or it's equal to its own name */ |
| 613 | if (strcmp(x509_v_codes[i].string, x509_v_codes[i].value) == 0) |
| 614 | continue; |
| 615 | x509_v_codes[i].code = atoi(x509_v_codes[i].value); |
| 616 | } |
| 617 | } |
| 618 | |
| 619 | INITCALL0(STG_REGISTER, init_x509_v_err_tab); |
Remi Tricot-Le Breton | c8d814e | 2022-12-20 11:11:17 +0100 | [diff] [blame] | 620 | |
| 621 | |
| 622 | /* |
| 623 | * This function returns the number of seconds elapsed |
| 624 | * since the Epoch, 1970-01-01 00:00:00 +0000 (UTC) and the |
| 625 | * date presented un ASN1_GENERALIZEDTIME. |
| 626 | * |
| 627 | * In parsing error case, it returns -1. |
| 628 | */ |
| 629 | long asn1_generalizedtime_to_epoch(ASN1_GENERALIZEDTIME *d) |
| 630 | { |
| 631 | long epoch; |
| 632 | char *p, *end; |
| 633 | const unsigned short month_offset[12] = { |
| 634 | 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 |
| 635 | }; |
| 636 | unsigned long year, month; |
| 637 | |
| 638 | if (!d || (d->type != V_ASN1_GENERALIZEDTIME)) return -1; |
| 639 | |
| 640 | p = (char *)d->data; |
| 641 | end = p + d->length; |
| 642 | |
| 643 | if (end - p < 4) return -1; |
| 644 | year = 1000 * (p[0] - '0') + 100 * (p[1] - '0') + 10 * (p[2] - '0') + p[3] - '0'; |
| 645 | p += 4; |
| 646 | if (end - p < 2) return -1; |
| 647 | month = 10 * (p[0] - '0') + p[1] - '0'; |
| 648 | if (month < 1 || month > 12) return -1; |
| 649 | /* Compute the number of seconds since 1 jan 1970 and the beginning of current month |
| 650 | We consider leap years and the current month (<marsh or not) */ |
| 651 | epoch = ( ((year - 1970) * 365) |
| 652 | + ((year - (month < 3)) / 4 - (year - (month < 3)) / 100 + (year - (month < 3)) / 400) |
| 653 | - ((1970 - 1) / 4 - (1970 - 1) / 100 + (1970 - 1) / 400) |
| 654 | + month_offset[month-1] |
| 655 | ) * 24 * 60 * 60; |
| 656 | p += 2; |
| 657 | if (end - p < 2) return -1; |
| 658 | /* Add the number of seconds of completed days of current month */ |
| 659 | epoch += (10 * (p[0] - '0') + p[1] - '0' - 1) * 24 * 60 * 60; |
| 660 | p += 2; |
| 661 | if (end - p < 2) return -1; |
| 662 | /* Add the completed hours of the current day */ |
| 663 | epoch += (10 * (p[0] - '0') + p[1] - '0') * 60 * 60; |
| 664 | p += 2; |
| 665 | if (end - p < 2) return -1; |
| 666 | /* Add the completed minutes of the current hour */ |
| 667 | epoch += (10 * (p[0] - '0') + p[1] - '0') * 60; |
| 668 | p += 2; |
| 669 | if (p == end) return -1; |
| 670 | /* Test if there is available seconds */ |
| 671 | if (p[0] < '0' || p[0] > '9') |
| 672 | goto nosec; |
| 673 | if (end - p < 2) return -1; |
| 674 | /* Add the seconds of the current minute */ |
| 675 | epoch += 10 * (p[0] - '0') + p[1] - '0'; |
| 676 | p += 2; |
| 677 | if (p == end) return -1; |
| 678 | /* Ignore seconds float part if present */ |
| 679 | if (p[0] == '.') { |
| 680 | do { |
| 681 | if (++p == end) return -1; |
| 682 | } while (p[0] >= '0' && p[0] <= '9'); |
| 683 | } |
| 684 | |
| 685 | nosec: |
| 686 | if (p[0] == 'Z') { |
| 687 | if (end - p != 1) return -1; |
| 688 | return epoch; |
| 689 | } |
| 690 | else if (p[0] == '+') { |
| 691 | if (end - p != 5) return -1; |
| 692 | /* Apply timezone offset */ |
| 693 | return epoch - ((10 * (p[1] - '0') + p[2] - '0') * 60 * 60 + (10 * (p[3] - '0') + p[4] - '0')) * 60; |
| 694 | } |
| 695 | else if (p[0] == '-') { |
| 696 | if (end - p != 5) return -1; |
| 697 | /* Apply timezone offset */ |
| 698 | return epoch + ((10 * (p[1] - '0') + p[2] - '0') * 60 * 60 + (10 * (p[3] - '0') + p[4] - '0')) * 60; |
| 699 | } |
| 700 | |
| 701 | return -1; |
| 702 | } |