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> |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 20 | |
| 21 | /* fill a buffer with the algorithm and size of a public key */ |
| 22 | int cert_get_pkey_algo(X509 *crt, struct buffer *out) |
| 23 | { |
| 24 | int bits = 0; |
| 25 | int sig = TLSEXT_signature_anonymous; |
| 26 | int len = -1; |
| 27 | EVP_PKEY *pkey; |
| 28 | |
| 29 | pkey = X509_get_pubkey(crt); |
| 30 | if (pkey) { |
| 31 | bits = EVP_PKEY_bits(pkey); |
| 32 | switch(EVP_PKEY_base_id(pkey)) { |
| 33 | case EVP_PKEY_RSA: |
| 34 | sig = TLSEXT_signature_rsa; |
| 35 | break; |
| 36 | case EVP_PKEY_EC: |
| 37 | sig = TLSEXT_signature_ecdsa; |
| 38 | break; |
| 39 | case EVP_PKEY_DSA: |
| 40 | sig = TLSEXT_signature_dsa; |
| 41 | break; |
| 42 | } |
| 43 | EVP_PKEY_free(pkey); |
| 44 | } |
| 45 | |
| 46 | switch(sig) { |
| 47 | case TLSEXT_signature_rsa: |
| 48 | len = chunk_printf(out, "RSA%d", bits); |
| 49 | break; |
| 50 | case TLSEXT_signature_ecdsa: |
| 51 | len = chunk_printf(out, "EC%d", bits); |
| 52 | break; |
| 53 | case TLSEXT_signature_dsa: |
| 54 | len = chunk_printf(out, "DSA%d", bits); |
| 55 | break; |
| 56 | default: |
| 57 | return 0; |
| 58 | } |
| 59 | if (len < 0) |
| 60 | return 0; |
| 61 | return 1; |
| 62 | } |
| 63 | |
| 64 | /* Extract a serial from a cert, and copy it to a chunk. |
| 65 | * Returns 1 if serial is found and copied, 0 if no serial found and |
| 66 | * -1 if output is not large enough. |
| 67 | */ |
| 68 | int ssl_sock_get_serial(X509 *crt, struct buffer *out) |
| 69 | { |
| 70 | ASN1_INTEGER *serial; |
| 71 | |
| 72 | serial = X509_get_serialNumber(crt); |
| 73 | if (!serial) |
| 74 | return 0; |
| 75 | |
| 76 | if (out->size < serial->length) |
| 77 | return -1; |
| 78 | |
| 79 | memcpy(out->area, serial->data, serial->length); |
| 80 | out->data = serial->length; |
| 81 | return 1; |
| 82 | } |
| 83 | |
| 84 | /* Extract a cert to der, and copy it to a chunk. |
| 85 | * Returns 1 if the cert is found and copied, 0 on der conversion failure |
| 86 | * and -1 if the output is not large enough. |
| 87 | */ |
| 88 | int ssl_sock_crt2der(X509 *crt, struct buffer *out) |
| 89 | { |
| 90 | int len; |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 91 | unsigned char *p = (unsigned char *) out->area; |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 92 | |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 93 | len = i2d_X509(crt, NULL); |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 94 | if (len <= 0) |
| 95 | return 1; |
| 96 | |
| 97 | if (out->size < len) |
| 98 | return -1; |
| 99 | |
William Dauchy | 98c3504 | 2020-08-06 18:11:37 +0200 | [diff] [blame] | 100 | i2d_X509(crt, &p); |
William Lallemand | 6a66a5e | 2020-05-15 12:01:17 +0200 | [diff] [blame] | 101 | out->data = len; |
| 102 | return 1; |
| 103 | } |
| 104 | |
| 105 | |
| 106 | /* Copy Date in ASN1_UTCTIME format in struct buffer out. |
| 107 | * Returns 1 if serial is found and copied, 0 if no valid time found |
| 108 | * and -1 if output is not large enough. |
| 109 | */ |
| 110 | int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out) |
| 111 | { |
| 112 | if (tm->type == V_ASN1_GENERALIZEDTIME) { |
| 113 | ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm; |
| 114 | |
| 115 | if (gentm->length < 12) |
| 116 | return 0; |
| 117 | if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30) |
| 118 | return 0; |
| 119 | if (out->size < gentm->length-2) |
| 120 | return -1; |
| 121 | |
| 122 | memcpy(out->area, gentm->data+2, gentm->length-2); |
| 123 | out->data = gentm->length-2; |
| 124 | return 1; |
| 125 | } |
| 126 | else if (tm->type == V_ASN1_UTCTIME) { |
| 127 | ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm; |
| 128 | |
| 129 | if (utctm->length < 10) |
| 130 | return 0; |
| 131 | if (utctm->data[0] >= 0x35) |
| 132 | return 0; |
| 133 | if (out->size < utctm->length) |
| 134 | return -1; |
| 135 | |
| 136 | memcpy(out->area, utctm->data, utctm->length); |
| 137 | out->data = utctm->length; |
| 138 | return 1; |
| 139 | } |
| 140 | |
| 141 | return 0; |
| 142 | } |
| 143 | |
| 144 | /* Extract an entry from a X509_NAME and copy its value to an output chunk. |
| 145 | * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough. |
| 146 | */ |
| 147 | int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, |
| 148 | struct buffer *out) |
| 149 | { |
| 150 | X509_NAME_ENTRY *ne; |
| 151 | ASN1_OBJECT *obj; |
| 152 | ASN1_STRING *data; |
| 153 | const unsigned char *data_ptr; |
| 154 | int data_len; |
| 155 | int i, j, n; |
| 156 | int cur = 0; |
| 157 | const char *s; |
| 158 | char tmp[128]; |
| 159 | int name_count; |
| 160 | |
| 161 | name_count = X509_NAME_entry_count(a); |
| 162 | |
| 163 | out->data = 0; |
| 164 | for (i = 0; i < name_count; i++) { |
| 165 | if (pos < 0) |
| 166 | j = (name_count-1) - i; |
| 167 | else |
| 168 | j = i; |
| 169 | |
| 170 | ne = X509_NAME_get_entry(a, j); |
| 171 | obj = X509_NAME_ENTRY_get_object(ne); |
| 172 | data = X509_NAME_ENTRY_get_data(ne); |
| 173 | data_ptr = ASN1_STRING_get0_data(data); |
| 174 | data_len = ASN1_STRING_length(data); |
| 175 | n = OBJ_obj2nid(obj); |
| 176 | if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| 177 | i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| 178 | s = tmp; |
| 179 | } |
| 180 | |
| 181 | if (chunk_strcasecmp(entry, s) != 0) |
| 182 | continue; |
| 183 | |
| 184 | if (pos < 0) |
| 185 | cur--; |
| 186 | else |
| 187 | cur++; |
| 188 | |
| 189 | if (cur != pos) |
| 190 | continue; |
| 191 | |
| 192 | if (data_len > out->size) |
| 193 | return -1; |
| 194 | |
| 195 | memcpy(out->area, data_ptr, data_len); |
| 196 | out->data = data_len; |
| 197 | return 1; |
| 198 | } |
| 199 | |
| 200 | return 0; |
| 201 | |
| 202 | } |
| 203 | |
| 204 | /* |
| 205 | * Extract the DN in the specified format from the X509_NAME and copy result to a chunk. |
| 206 | * Currently supports rfc2253 for returning LDAP V3 DNs. |
| 207 | * Returns 1 if dn entries exist, 0 if no dn entry was found. |
| 208 | */ |
| 209 | int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out) |
| 210 | { |
| 211 | BIO *bio = NULL; |
| 212 | int ret = 0; |
| 213 | int data_len = 0; |
| 214 | |
| 215 | if (chunk_strcmp(format, "rfc2253") == 0) { |
| 216 | bio = BIO_new(BIO_s_mem()); |
| 217 | if (bio == NULL) |
| 218 | goto out; |
| 219 | |
| 220 | if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0) |
| 221 | goto out; |
| 222 | |
| 223 | if ((data_len = BIO_read(bio, out->area, out->size)) <= 0) |
| 224 | goto out; |
| 225 | |
| 226 | out->data = data_len; |
| 227 | |
| 228 | ret = 1; |
| 229 | } |
| 230 | out: |
| 231 | if (bio) |
| 232 | BIO_free(bio); |
| 233 | return ret; |
| 234 | } |
| 235 | |
| 236 | /* Extract and format full DN from a X509_NAME and copy result into a chunk |
| 237 | * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. |
| 238 | */ |
| 239 | int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out) |
| 240 | { |
| 241 | X509_NAME_ENTRY *ne; |
| 242 | ASN1_OBJECT *obj; |
| 243 | ASN1_STRING *data; |
| 244 | const unsigned char *data_ptr; |
| 245 | int data_len; |
| 246 | int i, n, ln; |
| 247 | int l = 0; |
| 248 | const char *s; |
| 249 | char *p; |
| 250 | char tmp[128]; |
| 251 | int name_count; |
| 252 | |
| 253 | |
| 254 | name_count = X509_NAME_entry_count(a); |
| 255 | |
| 256 | out->data = 0; |
| 257 | p = out->area; |
| 258 | for (i = 0; i < name_count; i++) { |
| 259 | ne = X509_NAME_get_entry(a, i); |
| 260 | obj = X509_NAME_ENTRY_get_object(ne); |
| 261 | data = X509_NAME_ENTRY_get_data(ne); |
| 262 | data_ptr = ASN1_STRING_get0_data(data); |
| 263 | data_len = ASN1_STRING_length(data); |
| 264 | n = OBJ_obj2nid(obj); |
| 265 | if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| 266 | i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| 267 | s = tmp; |
| 268 | } |
| 269 | ln = strlen(s); |
| 270 | |
| 271 | l += 1 + ln + 1 + data_len; |
| 272 | if (l > out->size) |
| 273 | return -1; |
| 274 | out->data = l; |
| 275 | |
| 276 | *(p++)='/'; |
| 277 | memcpy(p, s, ln); |
| 278 | p += ln; |
| 279 | *(p++)='='; |
| 280 | memcpy(p, data_ptr, data_len); |
| 281 | p += data_len; |
| 282 | } |
| 283 | |
| 284 | if (!out->data) |
| 285 | return 0; |
| 286 | |
| 287 | return 1; |
| 288 | } |
| 289 | |
Remi Tricot-Le Breton | 74f6ab6 | 2021-08-19 18:06:30 +0200 | [diff] [blame] | 290 | |
| 291 | extern int ssl_client_crt_ref_index; |
| 292 | |
| 293 | /* |
| 294 | * This function fetches the SSL certificate for a specific connection (either |
| 295 | * client certificate or server certificate depending on the cert_peer |
| 296 | * parameter). |
| 297 | * When trying to get the peer certificate from the server side, we first try to |
| 298 | * use the dedicated SSL_get_peer_certificate function, but we fall back to |
| 299 | * trying to get the client certificate reference that might have been stored in |
| 300 | * the SSL structure's ex_data during the verification process. |
| 301 | * Returns NULL in case of failure. |
| 302 | */ |
| 303 | X509* ssl_sock_get_peer_certificate(SSL *ssl) |
| 304 | { |
| 305 | X509* cert; |
| 306 | |
| 307 | cert = SSL_get_peer_certificate(ssl); |
| 308 | /* Get the client certificate reference stored in the SSL |
| 309 | * structure's ex_data during the verification process. */ |
| 310 | if (!cert) { |
| 311 | cert = SSL_get_ex_data(ssl, ssl_client_crt_ref_index); |
| 312 | if (cert) |
| 313 | X509_up_ref(cert); |
| 314 | } |
| 315 | |
| 316 | return cert; |
| 317 | } |
William Lallemand | 44d862d | 2021-08-21 23:16:06 +0200 | [diff] [blame] | 318 | |
| 319 | /* |
| 320 | * Take an OpenSSL version in text format and return a numeric openssl version |
| 321 | * Return 0 if it failed to parse the version |
| 322 | * |
| 323 | * https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_VERSION_NUMBER.html |
| 324 | * |
| 325 | * MNNFFPPS: major minor fix patch status |
| 326 | * |
| 327 | * The status nibble has one of the values 0 for development, 1 to e for betas |
| 328 | * 1 to 14, and f for release. |
| 329 | * |
| 330 | * for example |
| 331 | * |
| 332 | * 0x0090821f 0.9.8zh |
| 333 | * 0x1000215f 1.0.2u |
| 334 | * 0x30000000 3.0.0-alpha17 |
| 335 | * 0x30000002 3.0.0-beta2 |
| 336 | * 0x3000000e 3.0.0-beta14 |
| 337 | * 0x3000000f 3.0.0 |
| 338 | */ |
| 339 | unsigned int openssl_version_parser(const char *version) |
| 340 | { |
| 341 | unsigned int numversion; |
| 342 | unsigned int major = 0, minor = 0, fix = 0, patch = 0, status = 0; |
| 343 | char *p, *end; |
| 344 | |
| 345 | p = (char *)version; |
| 346 | |
| 347 | if (!p || !*p) |
| 348 | return 0; |
| 349 | |
| 350 | major = strtol(p, &end, 10); |
| 351 | if (*end != '.' || major > 0xf) |
| 352 | goto error; |
| 353 | p = end + 1; |
| 354 | |
| 355 | minor = strtol(p, &end, 10); |
| 356 | if (*end != '.' || minor > 0xff) |
| 357 | goto error; |
| 358 | p = end + 1; |
| 359 | |
| 360 | fix = strtol(p, &end, 10); |
| 361 | if (fix > 0xff) |
| 362 | goto error; |
| 363 | p = end; |
| 364 | |
| 365 | if (!*p) { |
| 366 | /* end of the string, that's a release */ |
| 367 | status = 0xf; |
| 368 | } else if (*p == '-') { |
| 369 | /* after the hyphen, only the beta will increment the status |
| 370 | * counter, all others versions will be considered as "dev" and |
| 371 | * does not increment anything */ |
| 372 | p++; |
| 373 | |
| 374 | if (!strncmp(p, "beta", 4)) { |
| 375 | p += 4; |
William Lallemand | 8b673f0 | 2021-08-22 13:36:11 +0200 | [diff] [blame] | 376 | status = strtol(p, &end, 10); |
| 377 | if (status > 14) |
| 378 | goto error; |
William Lallemand | 44d862d | 2021-08-21 23:16:06 +0200 | [diff] [blame] | 379 | } |
| 380 | } else { |
| 381 | /* that's a patch release */ |
| 382 | patch = 1; |
| 383 | |
| 384 | /* add the value of each letter */ |
| 385 | while (*p) { |
| 386 | patch += (*p & ~0x20) - 'A'; |
| 387 | p++; |
| 388 | } |
| 389 | status = 0xf; |
| 390 | } |
| 391 | |
| 392 | end: |
| 393 | numversion = ((major & 0xf) << 28) | ((minor & 0xff) << 20) | ((fix & 0xff) << 12) | ((patch & 0xff) << 4) | (status & 0xf); |
| 394 | return numversion; |
| 395 | |
| 396 | error: |
| 397 | return 0; |
| 398 | |
| 399 | } |
Marcin Deranek | 959a48c | 2021-07-13 15:14:21 +0200 | [diff] [blame] | 400 | |
| 401 | /* Exclude GREASE (RFC8701) values from input buffer */ |
| 402 | void exclude_tls_grease(char *input, int len, struct buffer *output) |
| 403 | { |
| 404 | int ptr = 0; |
| 405 | |
| 406 | while (ptr < len - 1) { |
| 407 | if (input[ptr] != input[ptr+1] || (input[ptr] & 0x0f) != 0x0a) { |
| 408 | if (output->data <= output->size - 2) { |
| 409 | memcpy(output->area + output->data, input + ptr, 2); |
| 410 | output->data += 2; |
| 411 | } else |
| 412 | break; |
| 413 | } |
| 414 | ptr += 2; |
| 415 | } |
| 416 | if (output->size - output->data > 0 && len - ptr > 0) |
| 417 | output->area[output->data++] = input[ptr]; |
| 418 | } |