370 lines
12 KiB
C++
370 lines
12 KiB
C++
#include <ssl_stream.h>
|
|
#include <tfe_utils.h>
|
|
#include <MESA/maat.h>
|
|
#include <cjson/cJSON.h>
|
|
#include <openssl/ssl.h>
|
|
#include <assert.h>
|
|
#include <tfe_resource.h>
|
|
#include <tfe_cmsg.h>
|
|
|
|
struct ssl_policy_enforcer
|
|
{
|
|
struct maat *maat;
|
|
};
|
|
|
|
struct decryption_param
|
|
{
|
|
uuid_t uuid;
|
|
int ref_cnt;
|
|
int bypass_ev_cert;
|
|
int bypass_ct_cert;
|
|
int bypass_mutual_auth;
|
|
int bypass_pinning;
|
|
int bypass_uninstall_cert_traffic;
|
|
int bypass_protocol_errors;
|
|
int no_verify_cn;
|
|
int no_verify_issuer;
|
|
int no_verify_self_signed;
|
|
int no_verify_expry_date;
|
|
int block_fake_cert;
|
|
int ssl_min_version;
|
|
int ssl_max_version;
|
|
int allow_http2;
|
|
int mirror_client_version;
|
|
};
|
|
|
|
static void profile_param_dup_cb(const char *table_name, void **to, void **from, long argl, void *argp)
|
|
{
|
|
struct decryption_param *param = (struct decryption_param *)*from;
|
|
if (param)
|
|
{
|
|
__sync_add_and_fetch(&(param->ref_cnt), 1);
|
|
*to = param;
|
|
}
|
|
else
|
|
{
|
|
*to = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void profile_param_free_cb(const char *table_name, void **ad, long argl, void *argp)
|
|
{
|
|
struct decryption_param *param = (struct decryption_param *)*ad;
|
|
if (param == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((__sync_sub_and_fetch(¶m->ref_cnt, 1) == 0))
|
|
{
|
|
char uuid_str[UUID_STRING_SIZE];
|
|
uuid_unparse(param->uuid, uuid_str);
|
|
TFE_LOG_INFO(g_default_logger, "Del decryption profile: %s", uuid_str);
|
|
|
|
free(param);
|
|
*ad = NULL;
|
|
}
|
|
}
|
|
|
|
static void profile_param_free(struct decryption_param *param)
|
|
{
|
|
profile_param_free_cb(NULL, (void **)¶m, 0, NULL);
|
|
}
|
|
|
|
static void profile_param_new_cb(const char *table_name, const char *key, const char *table_line, void **ad, long argl, void *argp)
|
|
{
|
|
int ret = 0;
|
|
cJSON *json = NULL;
|
|
cJSON *object = NULL;
|
|
cJSON *exclusions = NULL;
|
|
cJSON *cert_verify = NULL;
|
|
cJSON *approach = NULL;
|
|
cJSON *ssl_ver = NULL;
|
|
cJSON *item = NULL;
|
|
struct decryption_param *param = NULL;
|
|
|
|
char *json_str = strdup(table_line);
|
|
json = cJSON_Parse(json_str);
|
|
if (json == NULL)
|
|
{
|
|
TFE_LOG_ERROR(g_default_logger, "Invalid decryption parameter: (invalid json format) %s", table_line);
|
|
goto error_out;
|
|
}
|
|
|
|
param = ALLOC(struct decryption_param, 1);
|
|
param->ref_cnt = 1;
|
|
param->bypass_mutual_auth = 1;
|
|
param->bypass_pinning = 1;
|
|
param->mirror_client_version = 1;
|
|
uuid_parse(key, param->uuid);
|
|
|
|
object = cJSON_GetObjectItem(json, "decryption");
|
|
if (!object || !cJSON_IsObject(object))
|
|
{
|
|
TFE_LOG_ERROR(g_default_logger, "Invalid decryption parameter: (invalid decryption param) %s", table_line);
|
|
goto error_out;
|
|
}
|
|
|
|
exclusions = cJSON_GetObjectItem(object, "dynamic_bypass");
|
|
if (exclusions)
|
|
{
|
|
item = cJSON_GetObjectItem(exclusions, "ev_cert");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_ev_cert = item->valueint;
|
|
}
|
|
item = cJSON_GetObjectItem(exclusions, "cert_transparency");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_ct_cert = item->valueint;
|
|
}
|
|
item = cJSON_GetObjectItem(exclusions, "mutual_authentication");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_mutual_auth = item->valueint;
|
|
}
|
|
item = cJSON_GetObjectItem(exclusions, "cert_pinning");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_pinning = item->valueint;
|
|
}
|
|
item = cJSON_GetObjectItem(exclusions, "protocol_errors");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_protocol_errors = item->valueint;
|
|
}
|
|
item = cJSON_GetObjectItem(exclusions, "trusted_root_cert_is_not_installed_on_client");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->bypass_uninstall_cert_traffic = item->valueint;
|
|
}
|
|
}
|
|
ssl_ver = cJSON_GetObjectItem(object, "protocol_version");
|
|
if (ssl_ver)
|
|
{
|
|
item = cJSON_GetObjectItem(ssl_ver, "mirror_client");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->mirror_client_version = item->valueint;
|
|
}
|
|
if (!param->mirror_client_version)
|
|
{
|
|
item = cJSON_GetObjectItem(ssl_ver, "min");
|
|
if (item && item->type == cJSON_String)
|
|
{
|
|
param->ssl_min_version = sslver_str2num(item->valuestring);
|
|
}
|
|
item = cJSON_GetObjectItem(ssl_ver, "max");
|
|
if (item && item->type == cJSON_String)
|
|
{
|
|
param->ssl_max_version = sslver_str2num(item->valuestring);
|
|
}
|
|
if (param->ssl_min_version < 0 || param->ssl_max_version < 0)
|
|
{
|
|
param->mirror_client_version = 1;
|
|
TFE_LOG_ERROR(g_default_logger, "Invalid intercept parameter: ssl version = %s", item->valuestring);
|
|
}
|
|
}
|
|
item = cJSON_GetObjectItem(ssl_ver, "allow_http2");
|
|
if (item && item->type == cJSON_Number)
|
|
{
|
|
param->allow_http2 = item->valueint;
|
|
}
|
|
}
|
|
cert_verify = cJSON_GetObjectItem(object, "certificate_checks");
|
|
if (cert_verify)
|
|
{
|
|
approach = cJSON_GetObjectItem(cert_verify, "approach");
|
|
if (approach)
|
|
{
|
|
item = cJSON_GetObjectItem(approach, "cn");
|
|
if (item && item->type == cJSON_Number && item->valueint == 0)
|
|
{
|
|
param->no_verify_cn = 1;
|
|
}
|
|
item = cJSON_GetObjectItem(approach, "issuer");
|
|
if (item && item->type == cJSON_Number && item->valueint == 0)
|
|
{
|
|
param->no_verify_issuer = 1;
|
|
}
|
|
item = cJSON_GetObjectItem(approach, "self-signed");
|
|
if (item && item->type == cJSON_Number && item->valueint == 0)
|
|
{
|
|
param->no_verify_self_signed = 1;
|
|
}
|
|
item = cJSON_GetObjectItem(approach, "expiration");
|
|
if (item && item->type == cJSON_Number && item->valueint == 0)
|
|
{
|
|
param->no_verify_expry_date = 1;
|
|
}
|
|
}
|
|
item = cJSON_GetObjectItem(cert_verify, "fail_action");
|
|
if (item && item->type == cJSON_String)
|
|
{
|
|
if (0 == strcasecmp(item->valuestring, "Fail-Close"))
|
|
{
|
|
param->block_fake_cert = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
*ad = param;
|
|
TFE_LOG_INFO(g_default_logger, "Add decryption profile: %s", key);
|
|
|
|
cJSON_Delete(json);
|
|
free(json_str);
|
|
|
|
error_out:
|
|
if (json)
|
|
{
|
|
cJSON_Delete(json);
|
|
}
|
|
if (json_str)
|
|
{
|
|
free(json_str);
|
|
}
|
|
if (param)
|
|
{
|
|
free(param);
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct ssl_policy_enforcer *ssl_policy_enforcer_create()
|
|
{
|
|
UNUSED int ret = 0;
|
|
struct ssl_policy_enforcer *enforcer = ALLOC(struct ssl_policy_enforcer, 1);
|
|
enforcer->maat = tfe_get_maat_handle();
|
|
ret = maat_plugin_table_ex_schema_register(enforcer->maat,
|
|
"PXY_PROFILE_DECRYPTION",
|
|
profile_param_new_cb,
|
|
profile_param_free_cb,
|
|
profile_param_dup_cb,
|
|
0,
|
|
enforcer);
|
|
assert(ret == 0);
|
|
return enforcer;
|
|
}
|
|
enum ssl_stream_action ssl_policy_enforce(struct ssl_stream *upstream, void *u_para)
|
|
{
|
|
UNUSED struct ssl_policy_enforcer *enforcer = (struct ssl_policy_enforcer *)u_para;
|
|
enum ssl_stream_action action = SSL_ACTION_PASSTHROUGH;
|
|
UNUSED int ret = 0;
|
|
char sni[512];
|
|
char addr_string[512];
|
|
char rule_uuid_str[UUID_STRING_SIZE];
|
|
char decrypted_uuid_str[UUID_STRING_SIZE];
|
|
|
|
uuid_t rule_uuid;
|
|
uuid_t decrypted_uuid;
|
|
uuid_t trusted_keyring_uuid;
|
|
uuid_t untrusted_keyring_uuid;
|
|
ssl_stream_get_policy_id(upstream, &rule_uuid);
|
|
ssl_stream_get_decrypted_profile_id(upstream, &decrypted_uuid);
|
|
ssl_stream_get_trusted_keyring_profile_id(upstream, &trusted_keyring_uuid);
|
|
ssl_stream_get_untrusted_keyring_profile_id(upstream, &untrusted_keyring_uuid);
|
|
|
|
ssl_stream_get_string_opt(upstream, SSL_STREAM_OPT_SNI, sni, sizeof(sni));
|
|
ssl_stream_get_string_opt(upstream, SSL_STREAM_OPT_ADDR, addr_string, sizeof(addr_string));
|
|
|
|
uuid_unparse(rule_uuid, rule_uuid_str);
|
|
uuid_unparse(decrypted_uuid, decrypted_uuid_str);
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s", addr_string, sni, rule_uuid_str);
|
|
|
|
struct decryption_param *profile_param = (struct decryption_param *)maat_plugin_table_get_ex_data(enforcer->maat, "PXY_PROFILE_DECRYPTION", (const char *)&decrypted_uuid, sizeof(uuid_t));
|
|
if (profile_param == NULL)
|
|
{
|
|
TFE_LOG_INFO(g_default_logger, "Failed to get decryption parameter of profile %s.", decrypted_uuid_str);
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Invalid Decryption Param");
|
|
return SSL_ACTION_PASSTHROUGH;
|
|
}
|
|
int pinning_staus = 0, is_ev = 0, is_ct = 0, is_mauth = 0, has_error = 0, ja3_pinning_status = 0;
|
|
if (!profile_param->mirror_client_version)
|
|
{
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_PROTOCOL_MIN_VERSION, profile_param->ssl_min_version);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_PROTOCOL_MAX_VERSION, profile_param->ssl_max_version);
|
|
assert(ret == 0);
|
|
}
|
|
if (profile_param->allow_http2)
|
|
{
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_ENABLE_ALPN, 1);
|
|
assert(ret == 0);
|
|
}
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_COMMON_NAME, profile_param->no_verify_cn);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_ISSUER, profile_param->no_verify_issuer);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_SELF_SIGNED, profile_param->no_verify_self_signed);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_EXPIRY_DATE, profile_param->no_verify_expry_date);
|
|
assert(ret == 0);
|
|
if (profile_param->block_fake_cert)
|
|
{
|
|
ret = ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_BLOCK_FAKE_CERT, 1);
|
|
assert(ret == 0);
|
|
}
|
|
ret = ssl_stream_set_uuid_opt(upstream, SSL_STREAM_OPT_KEYRING_FOR_TRUSTED, &trusted_keyring_uuid);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_set_uuid_opt(upstream, SSL_STREAM_OPT_KEYRING_FOR_UNTRUSTED, &untrusted_keyring_uuid);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_PINNING_STATUS, &pinning_staus);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_JA3_PINNING_STATUS, &ja3_pinning_status);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_EV_CERT, &is_ev);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_MUTUAL_AUTH, &is_mauth);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_CT_CERT, &is_ct);
|
|
assert(ret == 0);
|
|
ret = ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_HAS_PROTOCOL_ERRORS, &has_error);
|
|
assert(ret == 0);
|
|
|
|
if (pinning_staus == 1 && ja3_pinning_status == JA3_PINNING_STATUS_NOT_PINNING && profile_param->bypass_uninstall_cert_traffic)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Not Installed");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to Certificate Not Installed", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else if ((pinning_staus == 1 || ja3_pinning_status == JA3_PINNING_STATUS_IS_PINNING) && ja3_pinning_status != JA3_PINNING_STATUS_NOT_PINNING && profile_param->bypass_pinning)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Pinning");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to Certificate Pinning", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else if (is_mauth && profile_param->bypass_mutual_auth)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Mutual Authentication");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to Mutual Authentication", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else if (is_ev && profile_param->bypass_ev_cert)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "EV Certificate");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to EV Certificate", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else if (is_ct && profile_param->bypass_ct_cert)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Transparency");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to Certificate Transparency", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else if (has_error && profile_param->bypass_protocol_errors)
|
|
{
|
|
action = SSL_ACTION_PASSTHROUGH;
|
|
ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Protocol Errors");
|
|
TFE_LOG_DEBUG(g_default_logger, "%s %s enforce policy %s, action PASSTHROUGH due to Protocol Errors", addr_string, sni, rule_uuid_str);
|
|
}
|
|
else
|
|
{
|
|
action = SSL_ACTION_INTERCEPT;
|
|
}
|
|
|
|
profile_param_free(profile_param);
|
|
profile_param = NULL;
|
|
return action;
|
|
}
|