| /* |
| * Utility functions for SSL: |
| * Mostly generic functions that retrieve information from certificates |
| * |
| * Copyright (C) 2012 EXCELIANCE, Emeric Brun <ebrun@exceliance.fr> |
| * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| */ |
| |
| |
| #include <haproxy/api.h> |
| #include <haproxy/buf-t.h> |
| #include <haproxy/chunk.h> |
| #include <haproxy/openssl-compat.h> |
| #include <haproxy/ssl_sock.h> |
| #include <haproxy/ssl_utils.h> |
| |
| /* fill a buffer with the algorithm and size of a public key */ |
| int cert_get_pkey_algo(X509 *crt, struct buffer *out) |
| { |
| int bits = 0; |
| int sig = TLSEXT_signature_anonymous; |
| int len = -1; |
| EVP_PKEY *pkey; |
| |
| pkey = X509_get_pubkey(crt); |
| if (pkey) { |
| bits = EVP_PKEY_bits(pkey); |
| switch(EVP_PKEY_base_id(pkey)) { |
| case EVP_PKEY_RSA: |
| sig = TLSEXT_signature_rsa; |
| break; |
| case EVP_PKEY_EC: |
| sig = TLSEXT_signature_ecdsa; |
| break; |
| case EVP_PKEY_DSA: |
| sig = TLSEXT_signature_dsa; |
| break; |
| } |
| EVP_PKEY_free(pkey); |
| } |
| |
| switch(sig) { |
| case TLSEXT_signature_rsa: |
| len = chunk_printf(out, "RSA%d", bits); |
| break; |
| case TLSEXT_signature_ecdsa: |
| len = chunk_printf(out, "EC%d", bits); |
| break; |
| case TLSEXT_signature_dsa: |
| len = chunk_printf(out, "DSA%d", bits); |
| break; |
| default: |
| return 0; |
| } |
| if (len < 0) |
| return 0; |
| return 1; |
| } |
| |
| /* Extract a serial from a cert, and copy it to a chunk. |
| * Returns 1 if serial is found and copied, 0 if no serial found and |
| * -1 if output is not large enough. |
| */ |
| int ssl_sock_get_serial(X509 *crt, struct buffer *out) |
| { |
| ASN1_INTEGER *serial; |
| |
| serial = X509_get_serialNumber(crt); |
| if (!serial) |
| return 0; |
| |
| if (out->size < serial->length) |
| return -1; |
| |
| memcpy(out->area, serial->data, serial->length); |
| out->data = serial->length; |
| return 1; |
| } |
| |
| /* Extract a cert to der, and copy it to a chunk. |
| * Returns 1 if the cert is found and copied, 0 on der conversion failure |
| * and -1 if the output is not large enough. |
| */ |
| int ssl_sock_crt2der(X509 *crt, struct buffer *out) |
| { |
| int len; |
| unsigned char *p = (unsigned char *) out->area; |
| |
| len = i2d_X509(crt, NULL); |
| if (len <= 0) |
| return 1; |
| |
| if (out->size < len) |
| return -1; |
| |
| i2d_X509(crt, &p); |
| out->data = len; |
| return 1; |
| } |
| |
| |
| /* Copy Date in ASN1_UTCTIME format in struct buffer out. |
| * Returns 1 if serial is found and copied, 0 if no valid time found |
| * and -1 if output is not large enough. |
| */ |
| int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out) |
| { |
| if (tm->type == V_ASN1_GENERALIZEDTIME) { |
| ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm; |
| |
| if (gentm->length < 12) |
| return 0; |
| if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30) |
| return 0; |
| if (out->size < gentm->length-2) |
| return -1; |
| |
| memcpy(out->area, gentm->data+2, gentm->length-2); |
| out->data = gentm->length-2; |
| return 1; |
| } |
| else if (tm->type == V_ASN1_UTCTIME) { |
| ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm; |
| |
| if (utctm->length < 10) |
| return 0; |
| if (utctm->data[0] >= 0x35) |
| return 0; |
| if (out->size < utctm->length) |
| return -1; |
| |
| memcpy(out->area, utctm->data, utctm->length); |
| out->data = utctm->length; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Extract an entry from a X509_NAME and copy its value to an output chunk. |
| * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough. |
| */ |
| int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, |
| struct buffer *out) |
| { |
| X509_NAME_ENTRY *ne; |
| ASN1_OBJECT *obj; |
| ASN1_STRING *data; |
| const unsigned char *data_ptr; |
| int data_len; |
| int i, j, n; |
| int cur = 0; |
| const char *s; |
| char tmp[128]; |
| int name_count; |
| |
| name_count = X509_NAME_entry_count(a); |
| |
| out->data = 0; |
| for (i = 0; i < name_count; i++) { |
| if (pos < 0) |
| j = (name_count-1) - i; |
| else |
| j = i; |
| |
| ne = X509_NAME_get_entry(a, j); |
| obj = X509_NAME_ENTRY_get_object(ne); |
| data = X509_NAME_ENTRY_get_data(ne); |
| data_ptr = ASN1_STRING_get0_data(data); |
| data_len = ASN1_STRING_length(data); |
| n = OBJ_obj2nid(obj); |
| if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| s = tmp; |
| } |
| |
| if (chunk_strcasecmp(entry, s) != 0) |
| continue; |
| |
| if (pos < 0) |
| cur--; |
| else |
| cur++; |
| |
| if (cur != pos) |
| continue; |
| |
| if (data_len > out->size) |
| return -1; |
| |
| memcpy(out->area, data_ptr, data_len); |
| out->data = data_len; |
| return 1; |
| } |
| |
| return 0; |
| |
| } |
| |
| /* |
| * Extract the DN in the specified format from the X509_NAME and copy result to a chunk. |
| * Currently supports rfc2253 for returning LDAP V3 DNs. |
| * Returns 1 if dn entries exist, 0 if no dn entry was found. |
| */ |
| int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out) |
| { |
| BIO *bio = NULL; |
| int ret = 0; |
| int data_len = 0; |
| |
| if (chunk_strcmp(format, "rfc2253") == 0) { |
| bio = BIO_new(BIO_s_mem()); |
| if (bio == NULL) |
| goto out; |
| |
| if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0) |
| goto out; |
| |
| if ((data_len = BIO_read(bio, out->area, out->size)) <= 0) |
| goto out; |
| |
| out->data = data_len; |
| |
| ret = 1; |
| } |
| out: |
| if (bio) |
| BIO_free(bio); |
| return ret; |
| } |
| |
| /* Extract and format full DN from a X509_NAME and copy result into a chunk |
| * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. |
| */ |
| int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out) |
| { |
| X509_NAME_ENTRY *ne; |
| ASN1_OBJECT *obj; |
| ASN1_STRING *data; |
| const unsigned char *data_ptr; |
| int data_len; |
| int i, n, ln; |
| int l = 0; |
| const char *s; |
| char *p; |
| char tmp[128]; |
| int name_count; |
| |
| |
| name_count = X509_NAME_entry_count(a); |
| |
| out->data = 0; |
| p = out->area; |
| for (i = 0; i < name_count; i++) { |
| ne = X509_NAME_get_entry(a, i); |
| obj = X509_NAME_ENTRY_get_object(ne); |
| data = X509_NAME_ENTRY_get_data(ne); |
| data_ptr = ASN1_STRING_get0_data(data); |
| data_len = ASN1_STRING_length(data); |
| n = OBJ_obj2nid(obj); |
| if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { |
| i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); |
| s = tmp; |
| } |
| ln = strlen(s); |
| |
| l += 1 + ln + 1 + data_len; |
| if (l > out->size) |
| return -1; |
| out->data = l; |
| |
| *(p++)='/'; |
| memcpy(p, s, ln); |
| p += ln; |
| *(p++)='='; |
| memcpy(p, data_ptr, data_len); |
| p += data_len; |
| } |
| |
| if (!out->data) |
| return 0; |
| |
| return 1; |
| } |
| |
| |
| extern int ssl_client_crt_ref_index; |
| |
| /* |
| * This function fetches the SSL certificate for a specific connection (either |
| * client certificate or server certificate depending on the cert_peer |
| * parameter). |
| * When trying to get the peer certificate from the server side, we first try to |
| * use the dedicated SSL_get_peer_certificate function, but we fall back to |
| * trying to get the client certificate reference that might have been stored in |
| * the SSL structure's ex_data during the verification process. |
| * Returns NULL in case of failure. |
| */ |
| X509* ssl_sock_get_peer_certificate(SSL *ssl) |
| { |
| X509* cert; |
| |
| cert = SSL_get_peer_certificate(ssl); |
| /* Get the client certificate reference stored in the SSL |
| * structure's ex_data during the verification process. */ |
| if (!cert) { |
| cert = SSL_get_ex_data(ssl, ssl_client_crt_ref_index); |
| if (cert) |
| X509_up_ref(cert); |
| } |
| |
| return cert; |
| } |
| |
| /* |
| * Take an OpenSSL version in text format and return a numeric openssl version |
| * Return 0 if it failed to parse the version |
| * |
| * https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_VERSION_NUMBER.html |
| * |
| * MNNFFPPS: major minor fix patch status |
| * |
| * The status nibble has one of the values 0 for development, 1 to e for betas |
| * 1 to 14, and f for release. |
| * |
| * for example |
| * |
| * 0x0090821f 0.9.8zh |
| * 0x1000215f 1.0.2u |
| * 0x30000000 3.0.0-alpha17 |
| * 0x30000002 3.0.0-beta2 |
| * 0x3000000e 3.0.0-beta14 |
| * 0x3000000f 3.0.0 |
| */ |
| unsigned int openssl_version_parser(const char *version) |
| { |
| unsigned int numversion; |
| unsigned int major = 0, minor = 0, fix = 0, patch = 0, status = 0; |
| char *p, *end; |
| |
| p = (char *)version; |
| |
| if (!p || !*p) |
| return 0; |
| |
| major = strtol(p, &end, 10); |
| if (*end != '.' || major > 0xf) |
| goto error; |
| p = end + 1; |
| |
| minor = strtol(p, &end, 10); |
| if (*end != '.' || minor > 0xff) |
| goto error; |
| p = end + 1; |
| |
| fix = strtol(p, &end, 10); |
| if (fix > 0xff) |
| goto error; |
| p = end; |
| |
| if (!*p) { |
| /* end of the string, that's a release */ |
| status = 0xf; |
| } else if (*p == '-') { |
| /* after the hyphen, only the beta will increment the status |
| * counter, all others versions will be considered as "dev" and |
| * does not increment anything */ |
| p++; |
| |
| if (!strncmp(p, "beta", 4)) { |
| p += 4; |
| status = strtol(p, &end, 10); |
| if (status > 14) |
| goto error; |
| } |
| } else { |
| /* that's a patch release */ |
| patch = 1; |
| |
| /* add the value of each letter */ |
| while (*p) { |
| patch += (*p & ~0x20) - 'A'; |
| p++; |
| } |
| status = 0xf; |
| } |
| |
| end: |
| numversion = ((major & 0xf) << 28) | ((minor & 0xff) << 20) | ((fix & 0xff) << 12) | ((patch & 0xff) << 4) | (status & 0xf); |
| return numversion; |
| |
| error: |
| return 0; |
| |
| } |
| |
| /* Exclude GREASE (RFC8701) values from input buffer */ |
| void exclude_tls_grease(char *input, int len, struct buffer *output) |
| { |
| int ptr = 0; |
| |
| while (ptr < len - 1) { |
| if (input[ptr] != input[ptr+1] || (input[ptr] & 0x0f) != 0x0a) { |
| if (output->data <= output->size - 2) { |
| memcpy(output->area + output->data, input + ptr, 2); |
| output->data += 2; |
| } else |
| break; |
| } |
| ptr += 2; |
| } |
| if (output->size - output->data > 0 && len - ptr > 0) |
| output->area[output->data++] = input[ptr]; |
| } |
| |
| /* |
| * The following generates an array <x509_v_codes> in which the X509_V_ERR_* |
| * codes are populated with there string equivalent. Depending on the version |
| * of the SSL library, some code does not exist, these will be populated as |
| * "-1" in the array. |
| * |
| * The list was taken from |
| * https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| * and must be updated when new constant are introduced. |
| */ |
| |
| /* manual atoi() that only works on the first 10 chars of input (they must all be there) */ |
| #undef _S |
| #define _S(x) ((x[0]-'0')*1000000000 + \ |
| (x[1]-'0')*100000000 + \ |
| (x[2]-'0')*10000000 + \ |
| (x[3]-'0')*1000000 + \ |
| (x[4]-'0')*100000 + \ |
| (x[5]-'0')*10000 + \ |
| (x[6]-'0')*1000 + \ |
| (x[7]-'0')*100 + \ |
| (x[8]-'0')*10 + \ |
| (x[9]-'0')*1 + \ |
| 0) |
| |
| /* always prepends the sufficient number of leading zeroes to have 10 chars */ |
| #undef _R |
| #define _R(x) (!x[0] ? _S("0000000000" x) : \ |
| !x[1] ? _S("000000000" x) : \ |
| !x[2] ? _S("00000000" x) : \ |
| !x[3] ? _S("0000000" x) : \ |
| !x[4] ? _S("000000" x) : \ |
| !x[5] ? _S("00000" x) : \ |
| !x[6] ? _S("0000" x) : \ |
| !x[7] ? _S("000" x) : \ |
| !x[8] ? _S("00" x) : \ |
| !x[9] ? _S("0" x) : \ |
| _S("" x)) |
| |
| /* returns the value for an integer-defined macro, otherwise -1 |
| * The extraneous series of "\0" is there to shut up stupid clang which wants to |
| * evaluate the expression in false branches. |
| */ |
| #undef _Q |
| #define _Q(x) ((#x[0] >= '0' && #x[0] <= '9') ? _R(#x "\0\0\0\0\0\0\0\0\0\0") : -1) |
| #undef V |
| #define V(x) { .code = _Q(x), .string = #x } |
| |
| static const struct x509_v_codes { |
| int code; |
| const char *string; |
| } x509_v_codes[] = { |
| V(X509_V_OK), |
| V(X509_V_ERR_UNSPECIFIED), |
| V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT), |
| V(X509_V_ERR_UNABLE_TO_GET_CRL), |
| V(X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE), |
| V(X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE), |
| V(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY), |
| V(X509_V_ERR_CERT_SIGNATURE_FAILURE), |
| V(X509_V_ERR_CRL_SIGNATURE_FAILURE), |
| V(X509_V_ERR_CERT_NOT_YET_VALID), |
| V(X509_V_ERR_CERT_HAS_EXPIRED), |
| V(X509_V_ERR_CRL_NOT_YET_VALID), |
| V(X509_V_ERR_CRL_HAS_EXPIRED), |
| V(X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD), |
| V(X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD), |
| V(X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD), |
| V(X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD), |
| V(X509_V_ERR_OUT_OF_MEM), |
| V(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT), |
| V(X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN), |
| V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY), |
| V(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE), |
| V(X509_V_ERR_CERT_CHAIN_TOO_LONG), |
| V(X509_V_ERR_CERT_REVOKED), |
| V(X509_V_ERR_NO_ISSUER_PUBLIC_KEY), |
| V(X509_V_ERR_PATH_LENGTH_EXCEEDED), |
| V(X509_V_ERR_INVALID_PURPOSE), |
| V(X509_V_ERR_CERT_UNTRUSTED), |
| V(X509_V_ERR_CERT_REJECTED), |
| V(X509_V_ERR_SUBJECT_ISSUER_MISMATCH), |
| V(X509_V_ERR_AKID_SKID_MISMATCH), |
| V(X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH), |
| V(X509_V_ERR_KEYUSAGE_NO_CERTSIGN), |
| V(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER), |
| V(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION), |
| V(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN), |
| V(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION), |
| V(X509_V_ERR_INVALID_NON_CA), |
| V(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED), |
| V(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE), |
| V(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED), |
| V(X509_V_ERR_INVALID_EXTENSION), |
| V(X509_V_ERR_INVALID_POLICY_EXTENSION), |
| V(X509_V_ERR_NO_EXPLICIT_POLICY), |
| V(X509_V_ERR_DIFFERENT_CRL_SCOPE), |
| V(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE), |
| V(X509_V_ERR_UNNESTED_RESOURCE), |
| V(X509_V_ERR_PERMITTED_VIOLATION), |
| V(X509_V_ERR_EXCLUDED_VIOLATION), |
| V(X509_V_ERR_SUBTREE_MINMAX), |
| V(X509_V_ERR_APPLICATION_VERIFICATION), |
| V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE), |
| V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX), |
| V(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX), |
| V(X509_V_ERR_CRL_PATH_VALIDATION_ERROR), |
| V(X509_V_ERR_PATH_LOOP), |
| V(X509_V_ERR_SUITE_B_INVALID_VERSION), |
| V(X509_V_ERR_SUITE_B_INVALID_ALGORITHM), |
| V(X509_V_ERR_SUITE_B_INVALID_CURVE), |
| V(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM), |
| V(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED), |
| V(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256), |
| V(X509_V_ERR_HOSTNAME_MISMATCH), |
| V(X509_V_ERR_EMAIL_MISMATCH), |
| V(X509_V_ERR_IP_ADDRESS_MISMATCH), |
| V(X509_V_ERR_DANE_NO_MATCH), |
| V(X509_V_ERR_EE_KEY_TOO_SMALL), |
| V(X509_V_ERR_CA_KEY_TOO_SMALL), |
| V(X509_V_ERR_CA_MD_TOO_WEAK), |
| V(X509_V_ERR_INVALID_CALL), |
| V(X509_V_ERR_STORE_LOOKUP), |
| V(X509_V_ERR_NO_VALID_SCTS), |
| V(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION), |
| V(X509_V_ERR_OCSP_VERIFY_NEEDED), |
| V(X509_V_ERR_OCSP_VERIFY_FAILED), |
| V(X509_V_ERR_OCSP_CERT_UNKNOWN), |
| V(X509_V_ERR_UNSUPPORTED_SIGNATURE_ALGORITHM), |
| V(X509_V_ERR_SIGNATURE_ALGORITHM_MISMATCH), |
| V(X509_V_ERR_SIGNATURE_ALGORITHM_INCONSISTENCY), |
| V(X509_V_ERR_INVALID_CA), |
| V(X509_V_ERR_PATHLEN_INVALID_FOR_NON_CA), |
| V(X509_V_ERR_PATHLEN_WITHOUT_KU_KEY_CERT_SIGN), |
| V(X509_V_ERR_KU_KEY_CERT_SIGN_INVALID_FOR_NON_CA), |
| V(X509_V_ERR_ISSUER_NAME_EMPTY), |
| V(X509_V_ERR_SUBJECT_NAME_EMPTY), |
| V(X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER), |
| V(X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER), |
| V(X509_V_ERR_EMPTY_SUBJECT_ALT_NAME), |
| V(X509_V_ERR_EMPTY_SUBJECT_SAN_NOT_CRITICAL), |
| V(X509_V_ERR_CA_BCONS_NOT_CRITICAL), |
| V(X509_V_ERR_AUTHORITY_KEY_IDENTIFIER_CRITICAL), |
| V(X509_V_ERR_SUBJECT_KEY_IDENTIFIER_CRITICAL), |
| V(X509_V_ERR_CA_CERT_MISSING_KEY_USAGE), |
| V(X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3), |
| V(X509_V_ERR_EC_KEY_EXPLICIT_PARAMS), |
| { 0, NULL }, |
| }; |
| |
| /* |
| * Return the X509_V_ERR code corresponding to the name of the constant. |
| * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| * If not found, return -1 |
| */ |
| int x509_v_err_str_to_int(const char *str) |
| { |
| int i; |
| |
| for (i = 0; x509_v_codes[i].string; i++) { |
| if (strcmp(str, x509_v_codes[i].string) == 0) { |
| return x509_v_codes[i].code; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * Return the constant name corresponding to the X509_V_ERR code |
| * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in |
| * If not found, return NULL; |
| */ |
| const char *x509_v_err_int_to_str(int code) |
| { |
| int i; |
| |
| if (code == -1) |
| return NULL; |
| |
| for (i = 0; x509_v_codes[i].string; i++) { |
| if (x509_v_codes[i].code == code) { |
| return x509_v_codes[i].string; |
| } |
| } |
| return NULL; |
| } |