wireshark/epan/secrets.c

665 lines
20 KiB
C
Raw Normal View History

/* secrets.c
* Secrets management and processing.
* Copyright 2018, Peter Wu <peter@lekensteyn.nl>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
Refactor our logging and extend the wslog API Experience has shown that: 1. The current logging methods are not very reliable or practical. A logging bitmask makes little sense as the user-facing interface (who would want debug but not crtical messages for example?); it's computer-friendly and user-unfriendly. More importantly the console log level preference is initialized too late in the startup process to be used for the logging subsystem and that fact raises a number of annoying and hard-to-fix usability issues. 2. Coding around G_MESSAGES_DEBUG to comply with our log level mask and not clobber the user's settings or not create unexpected log misses is unworkable and generally follows the principle of most surprise. The fact that G_MESSAGES_DEBUG="all" can leak to other programs using GLib is also annoying. 3. The non-structured GLib logging API is very opinionated and lacks configurability beyond replacing the log handler. 4. Windows GUI has some special code to attach to a console, but it would be nice to abstract away the rest under a single interface. 5. Using this logger seems to be noticeably faster. Deprecate the console log level preference and extend our API to implement a log handler in wsutil/wslog.h to provide easy-to-use, flexible and dependable logging during all execution phases. Log levels have a hierarchy, from most verbose to least verbose (debug to error). When a given level is set everything above that is also enabled. The log level can be set with an environment variable or a command line option (parsed as soon as possible but still later than the environment). The default log level is "message". Dissector logging is not included because it is not clear what log domain they should use. An explosion to thousands of domains is not desirable and putting everything in a single domain is probably too coarse and noisy. For now I think it makes sense to let them do their own thing using g_log_default_handler() and continue using the G_MESSAGES_DEBUG mechanism with specific domains for each individual dissector. In the future a mechanism may be added to selectively enable these domains at runtime while trying to avoid the problems introduced by G_MESSAGES_DEBUG.
2021-06-08 01:46:52 +00:00
#define WS_LOG_DOMAIN LOG_DOMAIN_EPAN
#include "secrets.h"
#include <wiretap/wtap.h>
#include <wsutil/glib-compat.h>
2021-06-14 23:06:02 +00:00
#include <wsutil/wslog.h>
#include <string.h>
#ifdef HAVE_LIBGNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/abstract.h>
# include <gcrypt.h>
# include <wsutil/rsa.h>
# include <epan/uat.h>
# include <wsutil/report_message.h>
# include <wsutil/file_util.h>
# include <errno.h>
#endif /* HAVE_LIBGNUTLS */
2022-08-20 00:23:14 +00:00
#ifdef _WIN32
#include <windows.h>
#endif
/** Maps guint32 secrets_type -> secrets_block_callback_t. */
static GHashTable *secrets_callbacks;
#ifdef HAVE_LIBGNUTLS
/** Maps public key IDs (cert_key_id_t) -> gnutls_privkey_t. */
static GHashTable *rsa_privkeys;
typedef struct {
char *uri; /**< User-supplied PKCS #11 URI for token or RSA private key file. */
char *password; /**< User-supplied PKCS #11 PIN or RSA private key file password. */
} rsa_privkey_record_t;
static uat_t *rsa_privkeys_uat;
static rsa_privkey_record_t *uat_rsa_privkeys;
static guint uat_num_rsa_privkeys;
static void register_rsa_uats(void);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_GNUTLS_PKCS11
/** PINs for PKCS #11 keys in rsa_privkeys. Must be cleared after rsa_privkeys. */
static GSList *rsa_privkeys_pkcs11_pins;
typedef struct {
char *library_path; /**< PKCS #11 library path. */
} pkcs11_lib_record_t;
static uat_t *pkcs11_libs_uat;
static pkcs11_lib_record_t *uat_pkcs11_libs;
static guint uat_num_pkcs11_libs;
#endif /* HAVE_GNUTLS_PKCS11 */
void
secrets_init(void)
{
secrets_callbacks = g_hash_table_new(g_direct_hash, g_direct_equal);
#ifdef HAVE_LIBGNUTLS
rsa_privkeys = privkey_hash_table_new();
register_rsa_uats();
#endif /* HAVE_LIBGNUTLS */
}
void
secrets_cleanup(void)
{
g_hash_table_destroy(secrets_callbacks);
secrets_callbacks = NULL;
#ifdef HAVE_LIBGNUTLS
g_hash_table_destroy(rsa_privkeys);
rsa_privkeys = NULL;
#ifdef HAVE_GNUTLS_PKCS11
g_slist_free_full(rsa_privkeys_pkcs11_pins, g_free);
rsa_privkeys_pkcs11_pins = NULL;
#endif /* HAVE_GNUTLS_PKCS11 */
#endif /* HAVE_LIBGNUTLS */
}
void
secrets_register_type(guint32 secrets_type, secrets_block_callback_t cb)
{
g_hash_table_insert(secrets_callbacks, GUINT_TO_POINTER(secrets_type), (gpointer)cb);
}
void
secrets_wtap_callback(guint32 secrets_type, const void *secrets, guint size)
{
secrets_block_callback_t cb = (secrets_block_callback_t)g_hash_table_lookup(
secrets_callbacks, GUINT_TO_POINTER(secrets_type));
if (cb) {
cb(secrets, size);
}
}
#ifdef HAVE_LIBGNUTLS
static guint
key_id_hash(gconstpointer key)
{
const cert_key_id_t *key_id = (const cert_key_id_t *)key;
const guint32 *dw = (const guint32 *)key_id->key_id;
/* The public key' SHA-1 hash (which maps to a private key) has a uniform
* distribution, hence simply xor'ing them should be sufficient. */
return dw[0] ^ dw[1] ^ dw[2] ^ dw[3] ^ dw[4];
}
static gboolean
key_id_equal(gconstpointer a, gconstpointer b)
{
const cert_key_id_t *key_id_a = (const cert_key_id_t *)a;
const cert_key_id_t *key_id_b = (const cert_key_id_t *)b;
return !memcmp(key_id_a, key_id_b, sizeof(*key_id_a));
}
GHashTable *
privkey_hash_table_new(void)
{
return g_hash_table_new_full(key_id_hash, key_id_equal, g_free, (GDestroyNotify)gnutls_privkey_deinit);
}
static void
rsa_privkey_add(const cert_key_id_t *key_id, gnutls_privkey_t pkey)
{
void *ht_key = g_memdup2(key_id->key_id, sizeof(cert_key_id_t));
const guint32 *dw = (const guint32 *)key_id->key_id;
g_hash_table_insert(rsa_privkeys, ht_key, pkey);
2021-06-14 23:06:02 +00:00
ws_debug("Adding RSA private, Key ID %08x%08x%08x%08x%08x", g_htonl(dw[0]),
g_htonl(dw[1]), g_htonl(dw[2]), g_htonl(dw[3]), g_htonl(dw[4]));
}
#ifdef HAVE_GNUTLS_PKCS11
/** Provides a fixed PIN to the caller (or failure if the fixed PIN is NULL). */
static int
set_pin_callback(void *userdata, int attempt _U_,
const char *token_url _U_, const char *token_label _U_,
unsigned int flags, char *pin, size_t pin_max)
{
const char *fixed_pin = (const char *)userdata;
size_t fixed_pin_len = fixed_pin ? strlen(fixed_pin) : 0;
/* Fail if the PIN was not provided, wrong or too long. */
if (!fixed_pin || (flags & GNUTLS_PIN_WRONG) || fixed_pin_len >= pin_max) {
return GNUTLS_E_PKCS11_PIN_ERROR;
}
memcpy(pin, fixed_pin, fixed_pin_len + 1);
return 0;
}
static GSList *
get_pkcs11_token_uris(void)
{
GSList *tokens = NULL;
for (unsigned i = 0; ; i++) {
char *uri = NULL;
int flags;
int ret = gnutls_pkcs11_token_get_url(i, GNUTLS_PKCS11_URL_GENERIC, &uri);
if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
break;
}
if (ret < 0) {
2021-06-14 23:06:02 +00:00
ws_debug("Failed to query token %u: %s\n", i, gnutls_strerror(ret));
break;
}
ret = gnutls_pkcs11_token_get_flags(uri, &flags);
if (ret < 0) {
2021-06-14 23:06:02 +00:00
ws_debug("Failed to query token flags for %s: %s\n", uri, gnutls_strerror(ret));
gnutls_free(uri);
continue;
}
// The "Trust module" is useless for decryption, so do not return it.
if ((flags & GNUTLS_PKCS11_TOKEN_TRUSTED)) {
gnutls_free(uri);
continue;
}
tokens = g_slist_prepend(tokens, g_strdup(uri));
gnutls_free(uri);
}
tokens = g_slist_reverse(tokens);
return tokens;
}
static gboolean
verify_pkcs11_token(const char *token_uri, const char *pin, gboolean *pin_needed, char **error)
{
gnutls_pkcs11_obj_t *list = NULL;
unsigned int nlist = 0;
int ret;
/* Set PIN via a global callback since import_url can prompt for one. */
gnutls_pkcs11_set_pin_function(set_pin_callback, (void *)pin);
/* This should ask for a PIN if needed. If no PIN is given,
* GNUTLS_E_PKCS11_PIN_ERROR (-303) is returned. */
ret = gnutls_pkcs11_obj_list_import_url4(&list, &nlist, token_uri,
GNUTLS_PKCS11_OBJ_FLAG_PRIVKEY|GNUTLS_PKCS11_OBJ_FLAG_LOGIN);
if (ret == 0) {
/* Do not care about the objects, we just wanted to know whether the
* token and PIN were valid. */
for (unsigned i = 0; i < nlist; i++) {
gnutls_pkcs11_obj_deinit(list[i]);
}
gnutls_free(list);
}
/* Forget about the PIN. */
gnutls_pkcs11_set_pin_function(NULL, NULL);
if (pin_needed) {
*pin_needed = ret == GNUTLS_E_PKCS11_PIN_ERROR;
}
if (ret != 0) {
if (error) {
*error = g_strdup(gnutls_strerror(ret));
}
return FALSE;
}
return TRUE;
}
/**
* Load private RSA keys from a PKCS #11 token. Returns zero on success and a
* negative error code on failure.
*/
static int
pkcs11_load_keys_from_token(const char *token_uri, const char *pin, char **err)
{
gnutls_pkcs11_obj_t *list = NULL;
unsigned int nlist = 0;
int ret;
/* An empty/NULL PIN means that none is necessary. */
char *fixed_pin = pin && pin[0] ? g_strdup(pin) : NULL;
gboolean pin_in_use = FALSE;
/* Set PIN via a global callback since import_url can prompt for one. */
gnutls_pkcs11_set_pin_function(set_pin_callback, fixed_pin);
/* This might already result in callback for the PIN. */
ret = gnutls_pkcs11_obj_list_import_url4(&list, &nlist, token_uri,
GNUTLS_PKCS11_OBJ_FLAG_PRIVKEY|GNUTLS_PKCS11_OBJ_FLAG_LOGIN);
if (ret < 0) {
*err = ws_strdup_printf("Failed to iterate through objects for %s: %s", token_uri, gnutls_strerror(ret));
goto cleanup;
}
for (unsigned j = 0; j < nlist; j++) {
char *obj_uri = NULL;
gnutls_privkey_t privkey = NULL;
gnutls_pubkey_t pubkey = NULL;
cert_key_id_t key_id;
size_t size;
if (gnutls_pkcs11_obj_get_type(list[j]) != GNUTLS_PKCS11_OBJ_PRIVKEY) {
/* Should not happen since we requested private keys only. */
goto cont;
}
ret = gnutls_pkcs11_obj_export_url(list[j], GNUTLS_PKCS11_URL_GENERIC, &obj_uri);
if (ret < 0) {
/* Should not happen either if the object is valid. */
goto cont;
}
ret = gnutls_privkey_init(&privkey);
if (ret < 0) {
/* Out of memory? */
goto cont;
}
/* Set the PIN to be used during decryption. */
gnutls_privkey_set_pin_function(privkey, set_pin_callback, fixed_pin);
/* Can prompt for PIN. Can also invoke the token function set by
* gnutls_pkcs11_set_token_function (if not set, it will just fail
* immediately rather than retrying). */
ret = gnutls_privkey_import_url(privkey, obj_uri, 0);
if (ret < 0) {
/* Bad PIN or some other system error? */
2021-06-14 23:06:02 +00:00
ws_debug("Failed to import private key %s: %s", obj_uri, gnutls_strerror(ret));
goto cont;
}
if (gnutls_privkey_get_pk_algorithm(privkey, NULL) != GNUTLS_PK_RSA) {
2021-06-14 23:06:02 +00:00
ws_debug("Skipping private key %s, not RSA.", obj_uri);
goto cont;
}
ret = gnutls_pubkey_init(&pubkey);
if (ret < 0) {
/* Out of memory? */
goto cont;
}
/* This requires GnuTLS 3.4.0 and will fail on older versions. */
ret = gnutls_pubkey_import_privkey(pubkey, privkey, 0, 0);
if (ret < 0) {
2021-06-14 23:06:02 +00:00
ws_debug("Failed to import public key %s: %s", obj_uri, gnutls_strerror(ret));
goto cont;
}
size = sizeof(key_id);
ret = gnutls_pubkey_get_key_id(pubkey, GNUTLS_KEYID_USE_SHA1, key_id.key_id, &size);
if (ret < 0 || size != sizeof(key_id)) {
2021-06-14 23:06:02 +00:00
ws_debug("Failed to calculate Key ID for %s: %s", obj_uri, gnutls_strerror(ret));
goto cont;
}
/* Remember the private key. */
rsa_privkey_add(&key_id, privkey);
privkey = NULL;
pin_in_use = TRUE;
cont:
gnutls_privkey_deinit(privkey);
gnutls_pubkey_deinit(pubkey);
gnutls_free(obj_uri);
gnutls_pkcs11_obj_deinit(list[j]);
}
gnutls_free(list);
if (pin_in_use) {
/* Remember PINs such they can be freed later. */
rsa_privkeys_pkcs11_pins = g_slist_prepend(rsa_privkeys_pkcs11_pins, fixed_pin);
fixed_pin = NULL;
}
ret = 0;
cleanup:
/* Forget about the PIN. */
gnutls_pkcs11_set_pin_function(NULL, NULL);
g_free(fixed_pin);
return ret;
}
/** Load all libraries specified in a UAT. */
static void
uat_pkcs11_libs_load_all(void)
{
int ret;
GString *err = NULL;
for (guint i = 0; i < uat_num_pkcs11_libs; i++) {
const pkcs11_lib_record_t *rec = &uat_pkcs11_libs[i];
const char *libname = rec->library_path;
2022-05-25 10:51:55 +00:00
#ifdef _MSC_VER
// Work around a bug in p11-kit < 0.23.16 on Windows
HMODULE provider_lib = LoadLibraryA(libname);
if (! provider_lib || ! GetProcAddress(provider_lib, "C_GetFunctionList")) {
ret = GNUTLS_E_PKCS11_LOAD_ERROR;
} else {
#endif
/* Note: should return success for already loaded libraries. */
ret = gnutls_pkcs11_add_provider(libname, NULL);
2022-05-25 10:51:55 +00:00
#ifdef _MSC_VER
}
if (provider_lib) {
FreeLibrary(provider_lib);
}
#endif
if (ret) {
if (!err) {
err = g_string_new("Error loading PKCS #11 libraries:");
}
g_string_append_printf(err, "\n%s: %s", libname, gnutls_strerror(ret));
}
}
if (err) {
report_failure("%s", err->str);
g_string_free(err, TRUE);
}
}
UAT_FILENAME_CB_DEF(pkcs11_libs_uats, library_path, pkcs11_lib_record_t)
static void *
uat_pkcs11_lib_copy_str_cb(void *dest, const void *source, size_t len _U_)
{
pkcs11_lib_record_t *d = (pkcs11_lib_record_t *)dest;
const pkcs11_lib_record_t *s = (const pkcs11_lib_record_t *)source;
d->library_path = g_strdup(s->library_path);
return dest;
}
static void
uat_pkcs11_lib_free_str_cb(void *record)
{
pkcs11_lib_record_t *rec = (pkcs11_lib_record_t *)record;
g_free(rec->library_path);
}
#endif /* HAVE_GNUTLS_PKCS11 */
UAT_FILENAME_CB_DEF(rsa_privkeys_uats, uri, rsa_privkey_record_t)
UAT_CSTRING_CB_DEF(rsa_privkeys_uats, password, rsa_privkey_record_t)
static void *
uat_rsa_privkey_copy_str_cb(void *dest, const void *source, size_t len _U_)
{
rsa_privkey_record_t *d = (rsa_privkey_record_t *)dest;
const rsa_privkey_record_t *s = (const rsa_privkey_record_t *)source;
d->uri = g_strdup(s->uri);
d->password = g_strdup(s->password);
return dest;
}
static void
uat_rsa_privkey_free_str_cb(void *record)
{
rsa_privkey_record_t *rec = (rsa_privkey_record_t *)record;
g_free(rec->uri);
g_free(rec->password);
}
static void
load_rsa_keyfile(const char *filename, const char *password, gboolean save_key, char **err)
{
gnutls_x509_privkey_t x509_priv_key;
gnutls_privkey_t privkey = NULL;
char *errmsg = NULL;
int ret;
cert_key_id_t key_id;
size_t size = sizeof(key_id);
FILE *fp = ws_fopen(filename, "rb");
if (!fp) {
*err = ws_strdup_printf("Error loading RSA key file %s: %s", filename, g_strerror(errno));
return;
}
if (!password || !password[0]) {
x509_priv_key = rsa_load_pem_key(fp, &errmsg);
} else {
/* Assume encrypted PKCS #12 container. */
x509_priv_key = rsa_load_pkcs12(fp, password, &errmsg);
}
fclose(fp);
if (!x509_priv_key) {
*err = ws_strdup_printf("Error loading RSA key file %s: %s", filename, errmsg);
g_free(errmsg);
return;
}
gnutls_privkey_init(&privkey);
ret = gnutls_privkey_import_x509(privkey, x509_priv_key,
GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE|GNUTLS_PRIVKEY_IMPORT_COPY);
if (ret < 0) {
*err = ws_strdup_printf("Error importing private key %s: %s", filename, gnutls_strerror(ret));
goto end;
}
ret = gnutls_x509_privkey_get_key_id(x509_priv_key, GNUTLS_KEYID_USE_SHA1, key_id.key_id, &size);
if (ret < 0 || size != sizeof(key_id)) {
*err = ws_strdup_printf("Error calculating Key ID for %s: %s", filename, gnutls_strerror(ret));
goto end;
}
/* Remember the private key. */
if (save_key) {
rsa_privkey_add(&key_id, privkey);
privkey = NULL;
}
end:
gnutls_x509_privkey_deinit(x509_priv_key);
gnutls_privkey_deinit(privkey);
}
static void
uat_rsa_privkeys_post_update(void)
{
/* Clear previous keys. */
g_hash_table_remove_all(rsa_privkeys);
#ifdef HAVE_GNUTLS_PKCS11
g_slist_free_full(rsa_privkeys_pkcs11_pins, g_free);
rsa_privkeys_pkcs11_pins = NULL;
#endif /* HAVE_GNUTLS_PKCS11 */
GString *errors = NULL;
for (guint i = 0; i < uat_num_rsa_privkeys; i++) {
const rsa_privkey_record_t *rec = &uat_rsa_privkeys[i];
const char *token_uri = rec->uri;
char *err = NULL;
if (g_str_has_prefix(token_uri, "pkcs11:")) {
#ifdef HAVE_GNUTLS_PKCS11
pkcs11_load_keys_from_token(token_uri, rec->password, &err);
#endif /* HAVE_GNUTLS_PKCS11 */
} else {
load_rsa_keyfile(token_uri, rec->password, TRUE, &err);
}
if (err) {
if (!errors) {
errors = g_string_new("Error processing rsa_privkeys:");
}
g_string_append_c(errors, '\n');
g_string_append(errors, err);
g_free(err);
}
}
if (errors) {
report_failure("%s", errors->str);
g_string_free(errors, TRUE);
}
}
GSList *
secrets_get_available_keys(void)
{
GSList *keys = NULL;
#ifdef HAVE_GNUTLS_PKCS11
keys = g_slist_concat(keys, get_pkcs11_token_uris());
#endif
return keys;
}
gboolean
secrets_verify_key(const char *uri, const char *password, gboolean *need_password, char **error)
{
if (need_password) {
*need_password = FALSE;
}
if (error) {
*error = NULL;
}
if (g_str_has_prefix(uri, "pkcs11:")) {
#ifdef HAVE_GNUTLS_PKCS11
return verify_pkcs11_token(uri, password, need_password, error);
#else
if (error) {
*error = g_strdup("PKCS #11 support is not available in this build");
}
return FALSE;
#endif
} else if (g_file_test(uri, G_FILE_TEST_IS_REGULAR)) {
gchar *err = NULL;
load_rsa_keyfile(uri, password, FALSE, &err);
if (need_password) {
// Assume that failure to load the key is due to password errors.
// This might not be correct, but fixing this needs more changes.
*need_password = err != NULL;
}
if (err) {
if (error) {
*error = err;
} else {
g_free(err);
}
return FALSE;
}
return TRUE;
} else {
if (error) {
*error = g_strdup("Unsupported key URI or path");
}
return FALSE;
}
}
/**
* Register the UAT definitions such that settings can be loaded from file.
* Note: relies on uat_load_all to invoke the post_update_cb in order of
* registration below such that libraries are loaded *before* keys are read.
*/
static void
register_rsa_uats(void)
{
#ifdef HAVE_GNUTLS_PKCS11
static uat_field_t uat_pkcs11_libs_fields[] = {
UAT_FLD_FILENAME_OTHER(pkcs11_libs_uats, library_path, "Library Path", NULL, "PKCS #11 provider library file"),
UAT_END_FIELDS
};
pkcs11_libs_uat = uat_new("PKCS #11 Provider Libraries",
sizeof(pkcs11_lib_record_t),
"pkcs11_libs", /* filename */
FALSE, /* from_profile */
&uat_pkcs11_libs, /* data_ptr */
&uat_num_pkcs11_libs, /* numitems_ptr */
0, /* does not directly affect dissection */
NULL, /* Help section (currently a wiki page) */
uat_pkcs11_lib_copy_str_cb, /* copy_cb */
NULL, /* update_cb */
uat_pkcs11_lib_free_str_cb, /* free_cb */
uat_pkcs11_libs_load_all, /* post_update_cb */
NULL, /* reset_cb */
uat_pkcs11_libs_fields);
#endif /* HAVE_GNUTLS_PKCS11 */
static uat_field_t uat_rsa_privkeys_fields[] = {
UAT_FLD_FILENAME_OTHER(rsa_privkeys_uats, uri, "Keyfile or Token URI", NULL, "RSA Key File or PKCS #11 URI for token"),
UAT_FLD_FILENAME_OTHER(rsa_privkeys_uats, password, "Password", NULL, "RSA Key File password or PKCS #11 Token PIN"),
UAT_END_FIELDS
};
rsa_privkeys_uat = uat_new("RSA Private Keys",
sizeof(rsa_privkey_record_t),
"rsa_keys", /* filename */
FALSE, /* from_profile */
&uat_rsa_privkeys, /* data_ptr */
&uat_num_rsa_privkeys, /* numitems_ptr */
0, /* does not directly affect dissection */
NULL, /* Help section (currently a wiki page) */
uat_rsa_privkey_copy_str_cb, /* copy_cb */
NULL, /* update_cb */
uat_rsa_privkey_free_str_cb, /* free_cb */
uat_rsa_privkeys_post_update, /* post_update_cb */
NULL, /* reset_cb */
uat_rsa_privkeys_fields);
}
int
secrets_rsa_decrypt(const cert_key_id_t *key_id, const guint8 *encr, int encr_len, guint8 **out, int *out_len)
{
gboolean ret;
gnutls_datum_t ciphertext = { (guchar *)encr, encr_len };
gnutls_datum_t plain = { 0 };
gnutls_privkey_t pkey = (gnutls_privkey_t)g_hash_table_lookup(rsa_privkeys, key_id->key_id);
if (!pkey) {
return GNUTLS_E_NO_CERTIFICATE_FOUND;
}
ret = gnutls_privkey_decrypt_data(pkey, 0, &ciphertext, &plain);
if (ret == 0) {
*out = (guint8 *)g_memdup2(plain.data, plain.size);
*out_len = plain.size;
gnutls_free(plain.data);
}
return ret;
}
#endif /* HAVE_LIBGNUTLS */
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/