3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-10-28 07:39:23 +01:00
iwd/src/eap-tls-common.c
James Prestwood bea1d22a5c eap-tls-common: allow embedded PEMs in settings
Refactoring was required to allow for embedded certs. The existing
eap_tls_state object was changed to hold the cert types (l_queue,
l_certchain, l_key) rather than the file path, since there may not
actually be separate PEM files.

Care was taken to properly manage the memory of these objects.
Since the TLS object takes ownership when setting auth data or the
CA certs all error cases must be handled properly to free these
objects after they are loaded and in addition they must be set to
NULL so that the cleanup doesn't double free them.

If everything goes to plan, we load all the PEMs in settings_load,
provide these objects to the TLS APIs, and then NULL out the
pointers (TLS now owns this memory). If anything fails between
settings_load and l_tls_start we must free these objects.

A special format must be used to indicate that a PEM is embedded
inside the settings file. First, the l_settings format should be
followed for the PEM itself, e.g.

[@pem@my_ca_cert]
<CA Cert data>

This PEM can then be referenced by "embed:my_ca_cert", e.g.

EAP-TLS-CACert=embed:my_ca_cert

Any other value not starting with "embed:" will be treated as a file
path.
2019-10-07 11:39:30 -05:00

1205 lines
28 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <ell/ell.h>
#include "src/missing.h"
#include "src/eap.h"
#include "src/eap-private.h"
#include "src/eap-tls-common.h"
struct databuf {
uint8_t *data;
size_t len;
size_t capacity;
};
static struct databuf *databuf_new(size_t capacity)
{
struct databuf *databuf;
if (!capacity)
return NULL;
databuf = l_new(struct databuf, 1);
databuf->data = l_malloc(capacity);
databuf->capacity = capacity;
return databuf;
}
static void databuf_append(struct databuf *databuf, const uint8_t *data,
size_t data_len)
{
size_t new_len;
if (!databuf)
return;
new_len = databuf->len + data_len;
if (new_len > databuf->capacity) {
databuf->capacity = new_len * 2;
databuf->data = l_realloc(databuf->data, databuf->capacity);
}
memcpy(databuf->data + databuf->len, data, data_len);
databuf->len = new_len;
}
static void databuf_free(struct databuf *databuf)
{
if (!databuf)
return;
l_free(databuf->data);
l_free(databuf);
}
#define EAP_TLS_PDU_MAX_LEN 65536
#define EAP_TLS_HEADER_LEN 6
#define EAP_TLS_HEADER_OCTET_FLAGS 5
#define EAP_TLS_HEADER_OCTET_FRAG_LEN 6
enum eap_tls_flag {
/* Reserved = 0x00, */
EAP_TLS_FLAG_S = 0x20,
EAP_TLS_FLAG_M = 0x40,
EAP_TLS_FLAG_L = 0x80,
};
struct eap_tls_state {
enum eap_tls_version version_negotiated;
struct l_tls *tunnel;
bool method_completed:1;
bool phase2_failed:1;
struct databuf *plain_buf;
struct databuf *tx_pdu_buf;
struct databuf *rx_pdu_buf;
size_t tx_frag_offset;
size_t tx_frag_last_len;
bool expecting_frag_ack:1;
struct l_queue *ca_cert;
struct l_certchain *client_cert;
struct l_key *client_key;
char **domain_mask;
const struct eap_tls_variant_ops *variant_ops;
void *variant_data;
};
static void __eap_tls_common_state_reset(struct eap_tls_state *eap_tls)
{
eap_tls->version_negotiated = EAP_TLS_VERSION_NOT_NEGOTIATED;
eap_tls->method_completed = false;
eap_tls->phase2_failed = false;
eap_tls->expecting_frag_ack = false;
if (eap_tls->tunnel) {
l_tls_free(eap_tls->tunnel);
eap_tls->tunnel = NULL;
}
eap_tls->tx_frag_offset = 0;
eap_tls->tx_frag_last_len = 0;
if (eap_tls->plain_buf) {
databuf_free(eap_tls->plain_buf);
eap_tls->plain_buf = NULL;
}
if (eap_tls->tx_pdu_buf) {
databuf_free(eap_tls->tx_pdu_buf);
eap_tls->tx_pdu_buf = NULL;
}
if (eap_tls->rx_pdu_buf) {
databuf_free(eap_tls->rx_pdu_buf);
eap_tls->rx_pdu_buf = NULL;
}
}
static void __eap_tls_common_state_free(struct eap_tls_state *eap_tls)
{
if (eap_tls->ca_cert)
l_queue_destroy(eap_tls->ca_cert,
(l_queue_destroy_func_t)l_cert_free);
if (eap_tls->client_cert)
l_certchain_free(eap_tls->client_cert);
if (eap_tls->client_key)
l_key_free(eap_tls->client_key);
l_strv_free(eap_tls->domain_mask);
l_free(eap_tls);
}
bool eap_tls_common_state_reset(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
__eap_tls_common_state_reset(eap_tls);
if (eap_tls->variant_ops->reset)
eap_tls->variant_ops->reset(eap_tls->variant_data);
return true;
}
void eap_tls_common_state_free(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
__eap_tls_common_state_reset(eap_tls);
eap_set_data(eap, NULL);
if (eap_tls->variant_ops->destroy)
eap_tls->variant_ops->destroy(eap_tls->variant_data);
__eap_tls_common_state_free(eap_tls);
}
static void eap_tls_tunnel_debug(const char *str, void *user_data)
{
struct eap_state *eap = user_data;
l_info("%s: %s", eap_get_method_name(eap), str);
}
static void eap_tls_tunnel_data_send(const uint8_t *data, size_t data_len,
void *user_data)
{
struct eap_state *eap = user_data;
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (!eap_tls->tx_pdu_buf)
eap_tls->tx_pdu_buf = databuf_new(data_len);
databuf_append(eap_tls->tx_pdu_buf, data, data_len);
}
static void eap_tls_tunnel_data_received(const uint8_t *data, size_t data_len,
void *user_data)
{
struct eap_state *eap = user_data;
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (!eap_tls->plain_buf)
eap_tls->plain_buf = databuf_new(data_len);
databuf_append(eap_tls->plain_buf, data, data_len);
}
static void eap_tls_tunnel_ready(const char *peer_identity, void *user_data)
{
struct eap_state *eap = user_data;
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (eap_tls->ca_cert && !peer_identity) {
l_error("%s: TLS did not verify AP identity",
eap_get_method_name(eap));
eap_method_error(eap);
return;
}
/*
* Since authenticator may not send us EAP-Success/EAP-Failure
* in cleartext for the outer EAP method, we reinforce
* the completion with a timer.
*/
eap_start_complete_timeout(eap);
if (!eap_tls->variant_ops->tunnel_ready)
return;
if (!eap_tls->variant_ops->tunnel_ready(eap, peer_identity))
l_tls_close(eap_tls->tunnel);
}
static void eap_tls_tunnel_disconnected(enum l_tls_alert_desc reason,
bool remote, void *user_data)
{
struct eap_state *eap = user_data;
struct eap_tls_state *eap_tls = eap_get_data(eap);
l_info("%s: Tunnel has disconnected with alert: %s",
eap_get_method_name(eap), l_tls_alert_to_str(reason));
eap_tls->method_completed = true;
}
static bool eap_tls_validate_version(struct eap_state *eap,
uint8_t flags_version)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
enum eap_tls_version version_proposed =
flags_version & EAP_TLS_VERSION_MASK;
if (eap_tls->version_negotiated == version_proposed)
return true;
if (!(flags_version & EAP_TLS_FLAG_S) ||
eap_tls->version_negotiated !=
EAP_TLS_VERSION_NOT_NEGOTIATED)
return false;
if (version_proposed < eap_tls->variant_ops->version_max_supported)
eap_tls->version_negotiated = version_proposed;
else
eap_tls->version_negotiated =
eap_tls->variant_ops->version_max_supported;
return true;
}
static void eap_tls_send_fragment(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
size_t mtu = eap_get_mtu(eap);
uint8_t buf[mtu];
size_t len = eap_tls->tx_pdu_buf->len - eap_tls->tx_frag_offset;
size_t header_len = EAP_TLS_HEADER_LEN;
uint8_t position = 0;
if (eap_get_method_type(eap) == EAP_TYPE_EXPANDED) {
header_len += 7;
position += 7;
}
buf[EAP_TLS_HEADER_OCTET_FLAGS + position] =
eap_tls->version_negotiated;
if (len > mtu - EAP_TLS_HEADER_LEN - position) {
len = mtu - EAP_TLS_HEADER_LEN - position;
buf[EAP_TLS_HEADER_OCTET_FLAGS + position] |= EAP_TLS_FLAG_M;
eap_tls->expecting_frag_ack = true;
}
if (!eap_tls->tx_frag_offset) {
buf[EAP_TLS_HEADER_OCTET_FLAGS + position] |= EAP_TLS_FLAG_L;
l_put_be32(eap_tls->tx_pdu_buf->len,
&buf[EAP_TLS_HEADER_OCTET_FRAG_LEN + position]);
len -= 4;
header_len += 4;
}
memcpy(buf + header_len,
eap_tls->tx_pdu_buf->data + eap_tls->tx_frag_offset, len);
eap_send_response(eap, eap_get_method_type(eap), buf, header_len + len);
eap_tls->tx_frag_last_len = len;
}
static void eap_tls_send_response(struct eap_state *eap,
const uint8_t *pdu, size_t pdu_len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
size_t msg_len = EAP_TLS_HEADER_LEN + pdu_len;
if (msg_len <= eap_get_mtu(eap)) {
uint8_t *buf;
uint8_t extra = 0;
if (eap_get_method_type(eap) == EAP_TYPE_EXPANDED) {
extra += 7;
msg_len += 7;
}
buf = l_malloc(msg_len);
buf[EAP_TLS_HEADER_OCTET_FLAGS + extra] =
eap_tls->version_negotiated;
memcpy(buf + EAP_TLS_HEADER_LEN + extra, pdu, pdu_len);
eap_send_response(eap, eap_get_method_type(eap), buf, msg_len);
l_free(buf);
return;
}
eap_tls->tx_frag_offset = 0;
eap_tls_send_fragment(eap);
}
void eap_tls_common_send_empty_response(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
uint8_t buf[EAP_TLS_HEADER_LEN + 7];
uint8_t position = 0;
if (eap_get_method_type(eap) == EAP_TYPE_EXPANDED)
position += 7;
buf[EAP_TLS_HEADER_OCTET_FLAGS + position] = eap_tls->version_negotiated;
eap_send_response(eap, eap_get_method_type(eap), buf,
EAP_TLS_HEADER_LEN + position);
}
static int eap_tls_init_request_assembly(struct eap_state *eap,
const uint8_t *pkt, size_t len,
uint8_t flags)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
size_t tls_msg_len;
if (eap_tls->rx_pdu_buf) {
/*
* EAP-TLS: RFC 5216 Section 3.1
*
* The L bit (length included) is set to indicate the presence
* of the four-octet TLS Message Length field, and MUST be set
* for the first fragment of a fragmented TLS message or set of
* messages.
*/
l_debug("%s: Server has set the L bit in the fragment other "
"than the first of a fragmented TLS message.",
eap_get_method_name(eap));
return 0;
}
if (len < 4)
return -EINVAL;
tls_msg_len = l_get_be32(pkt);
len -= 4;
if (!tls_msg_len || tls_msg_len > EAP_TLS_PDU_MAX_LEN) {
l_warn("%s: Fragmented pkt size is outside of alowed"
" boundaries [1, %u]", eap_get_method_name(eap),
EAP_TLS_PDU_MAX_LEN);
return -EINVAL;
}
if (tls_msg_len == len) {
/*
* EAP-TLS: RFC 5216 Section 3.1
*
* The L bit (length included) is set to indicate the presence
* of the four-octet TLS Message Length field, and MUST be set
* for the first fragment of a fragmented TLS message or set of
* messages.
*
* EAP-TTLSv0: RFC 5281, Section 9.2.2:
* "Unfragmented messages MAY have the L bit set and include
* the length of the message (though this information is
* redundant)."
*
* Some of the PEAP server implementations set the L flag along
* with redundant TLS Message Length field for the un-fragmented
* packets.
*/
l_debug("%s: Server has set the redundant TLS Message Length "
"field for the un-fragmented packet.",
eap_get_method_name(eap));
return -ENOMSG;
}
if (tls_msg_len < len) {
l_warn("%s: Fragmented pkt size is smaller than the received "
"packet.", eap_get_method_name(eap));
return -EINVAL;
}
eap_tls->rx_pdu_buf = databuf_new(tls_msg_len);
if (!(flags & EAP_TLS_FLAG_M)) {
/*
* EAP-TLS: RFC 5216 Section 3.1
*
* The M bit (more fragments) is set on all but the last
* fragment.
*
* Note: Some of the EAP-TLS based server implementations break
* the protocol and do not set the M flag for the first packet
* during the fragmented transmission. To stay compatible with
* such devices, we have relaxed this requirement.
*/
l_debug("%s: Server has failed to set the M flag in the first"
" packet of the fragmented transmission.",
eap_get_method_name(eap));
return -EAGAIN;
}
return 0;
}
static void eap_tls_send_fragmented_request_ack(struct eap_state *eap)
{
eap_tls_common_send_empty_response(eap);
}
static bool eap_tls_handle_fragmented_response_ack(struct eap_state *eap,
size_t len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (len)
return false;
if (!eap_tls->tx_frag_last_len)
return false;
eap_tls->tx_frag_offset += eap_tls->tx_frag_last_len;
eap_tls->tx_frag_last_len = 0;
eap_tls->expecting_frag_ack = false;
eap_tls_send_fragment(eap);
return true;
}
static int eap_tls_handle_fragmented_request(struct eap_state *eap,
const uint8_t *pkt,
size_t len,
uint8_t flags_version)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
int r = 0;
if (flags_version & EAP_TLS_FLAG_L) {
r = eap_tls_init_request_assembly(eap, pkt, len, flags_version);
if (r && r != -EAGAIN)
return r;
pkt += 4;
len -= 4;
}
if (!eap_tls->rx_pdu_buf)
return -EINVAL;
if (eap_tls->rx_pdu_buf->capacity < eap_tls->rx_pdu_buf->len + len) {
l_error("%s: Request fragment pkt size mismatch.",
eap_get_method_name(eap));
return -EINVAL;
}
databuf_append(eap_tls->rx_pdu_buf, pkt, len);
if (flags_version & EAP_TLS_FLAG_M)
return -EAGAIN;
return r;
}
static bool eap_tls_tunnel_init(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (eap_tls->tunnel)
return false;
eap_tls->tunnel = l_tls_new(false, eap_tls_tunnel_data_received,
eap_tls_tunnel_data_send,
eap_tls_tunnel_ready,
eap_tls_tunnel_disconnected,
eap);
if (!eap_tls->tunnel) {
l_error("%s: Failed to create a TLS instance.",
eap_get_method_name(eap));
return false;
}
if (getenv("IWD_TLS_DEBUG"))
l_tls_set_debug(eap_tls->tunnel, eap_tls_tunnel_debug, eap,
NULL);
if (eap_tls->client_cert || eap_tls->client_key) {
if (!l_tls_set_auth_data(eap_tls->tunnel, eap_tls->client_cert,
eap_tls->client_key)) {
l_certchain_free(eap_tls->client_cert);
eap_tls->client_cert = NULL;
l_key_free(eap_tls->client_key);
eap_tls->client_key = NULL;
l_error("%s: Failed to set auth data.",
eap_get_method_name(eap));
return false;
}
eap_tls->client_cert = NULL;
eap_tls->client_key = NULL;
}
if (eap_tls->ca_cert) {
if (!l_tls_set_cacert(eap_tls->tunnel, eap_tls->ca_cert)) {
l_queue_destroy(eap_tls->ca_cert,
(l_queue_destroy_func_t)l_cert_free);
eap_tls->ca_cert = NULL;
l_error("%s: Error settings CA certificates.",
eap_get_method_name(eap));
return false;
}
eap_tls->ca_cert = NULL;
}
if (eap_tls->domain_mask)
l_tls_set_domain_mask(eap_tls->tunnel, eap_tls->domain_mask);
if (!l_tls_start(eap_tls->tunnel)) {
l_error("%s: Failed to start the TLS client",
eap_get_method_name(eap));
return false;
}
return true;
}
static void eap_tls_handle_phase2_payload(struct eap_state *eap,
const uint8_t *pkt,
size_t pkt_len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
if (!eap_tls->variant_ops->tunnel_handle_request)
return;
if (!eap_tls->variant_ops->tunnel_handle_request(eap, pkt, pkt_len))
/*
* The tunneled packet payload that violates the protocol or
* fails a method-specific integrity check result in tunnel
* shutdown.
*/
l_tls_close(eap_tls->tunnel);
}
void eap_tls_common_handle_request(struct eap_state *eap,
const uint8_t *pkt, size_t len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
uint8_t flags_version;
if (eap_tls->method_completed)
return;
if (len < 1) {
l_error("%s: Request packet is too short.",
eap_get_method_name(eap));
goto error;
}
flags_version = pkt[0];
if (!eap_tls_validate_version(eap, flags_version)) {
l_error("%s: Version negotiation has failed.",
eap_get_method_name(eap));
goto error;
}
pkt += 1;
len -= 1;
if (eap_tls->expecting_frag_ack) {
if (!eap_tls_handle_fragmented_response_ack(eap, len))
goto error;
return;
}
if (flags_version & EAP_TLS_FLAG_L || eap_tls->rx_pdu_buf) {
int r = eap_tls_handle_fragmented_request(eap, pkt, len,
flags_version);
if (r == -EAGAIN) {
/* Expecting more fragments. */
eap_tls_send_fragmented_request_ack(eap);
return;
}
if (r == -ENOMSG) {
/*
* Redundant usage of the L flag, no packet reassembly
* is required.
*/
pkt += 4;
len -= 4;
goto proceed;
}
if (r < 0)
goto error;
if (eap_tls->rx_pdu_buf->capacity != eap_tls->rx_pdu_buf->len) {
l_error("%s: Request fragment packet size mismatch",
eap_get_method_name(eap));
goto error;
}
pkt = eap_tls->rx_pdu_buf->data;
len = eap_tls->rx_pdu_buf->len;
}
proceed:
if (eap_tls->tx_pdu_buf) {
/*
* tx_pdu_buf is used for the re-transmission and needs to be
* cleared on a new request.
*/
databuf_free(eap_tls->tx_pdu_buf);
eap_tls->tx_pdu_buf = NULL;
}
if (flags_version & EAP_TLS_FLAG_S) {
if (!eap_tls_tunnel_init(eap))
goto error;
}
if (len)
l_tls_handle_rx(eap_tls->tunnel, pkt, len);
if (eap_tls->plain_buf) {
/*
* An existence of the plain_buf indicates that the TLS tunnel
* has been established and Phase 2 payload was transmitted
* through it.
*/
eap_tls_handle_phase2_payload(eap, eap_tls->plain_buf->data,
eap_tls->plain_buf->len);
databuf_free(eap_tls->plain_buf);
eap_tls->plain_buf = NULL;
}
if (eap_tls->rx_pdu_buf) {
databuf_free(eap_tls->rx_pdu_buf);
eap_tls->rx_pdu_buf = NULL;
}
if (!eap_tls->tx_pdu_buf) {
if (eap_tls->phase2_failed)
goto error;
return;
}
eap_tls_send_response(eap, eap_tls->tx_pdu_buf->data,
eap_tls->tx_pdu_buf->len);
if (eap_tls->phase2_failed)
goto error;
return;
error:
eap_method_error(eap);
}
void eap_tls_common_handle_retransmit(struct eap_state *eap,
const uint8_t *pkt, size_t len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
uint8_t flags_version;
if (len < 1) {
l_error("%s: Request packet is too short.",
eap_get_method_name(eap));
goto error;
}
flags_version = pkt[0];
if (!eap_tls_validate_version(eap, flags_version)) {
l_error("%s: Version negotiation has failed.",
eap_get_method_name(eap));
goto error;
}
if (flags_version & EAP_TLS_FLAG_M) {
if (!eap_tls->rx_pdu_buf)
goto error;
eap_tls_send_fragmented_request_ack(eap);
return;
}
if (!eap_tls->tx_pdu_buf || !eap_tls->tx_pdu_buf->data ||
!eap_tls->tx_pdu_buf->len)
goto error;
if (EAP_TLS_HEADER_LEN + eap_tls->tx_pdu_buf->len > eap_get_mtu(eap))
eap_tls_send_fragment(eap);
else
eap_tls_send_response(eap, eap_tls->tx_pdu_buf->data,
eap_tls->tx_pdu_buf->len);
return;
error:
eap_method_error(eap);
}
static const char *load_embedded_pem(struct l_settings *settings,
const char *name)
{
const char *pem;
const char *type;
pem = l_settings_get_embedded_value(settings, name + 6, &type);
if (!pem)
return NULL;
if (strcmp(type, "pem"))
return NULL;
return pem;
}
static bool is_embedded(const char *str)
{
if (!str)
return false;
if (strlen(str) < 6)
return false;
if (!strncmp("embed:", str, 6))
return true;
return false;
}
static struct l_queue *eap_tls_load_ca_cert(struct l_settings *settings,
const char *value)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_certificate_list(value);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_certificate_list_from_data(pem, strlen(pem));
}
static struct l_certchain *eap_tls_load_client_cert(struct l_settings *settings,
const char *value)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_certificate_chain(value);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_certificate_chain_from_data(pem, strlen(pem));
}
static struct l_key *eap_tls_load_priv_key(struct l_settings *settings,
const char *value, const char *passphrase,
bool *is_encrypted)
{
const char *pem;
if (!is_embedded(value))
return l_pem_load_private_key(value, passphrase, is_encrypted);
pem = load_embedded_pem(settings, value);
if (!pem)
return NULL;
return l_pem_load_private_key_from_data(pem, strlen(pem),
passphrase, is_encrypted);
}
int eap_tls_common_settings_check(struct l_settings *settings,
struct l_queue *secrets,
const char *prefix,
struct l_queue **out_missing)
{
char setting_key[72];
char client_cert_setting[72];
char passphrase_setting[72];
struct l_queue *cacerts = NULL;
struct l_certchain *cert = NULL;
struct l_key *priv_key = NULL;
bool is_encrypted, is_public;
int ret;
const char *error_str;
size_t size;
ssize_t result;
uint8_t *encrypted, *decrypted;
struct l_key *pub_key;
const char *domain_mask_str;
L_AUTO_FREE_VAR(char *, value);
L_AUTO_FREE_VAR(char *, client_cert) = NULL;
L_AUTO_FREE_VAR(char *, passphrase) = NULL;
snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
cacerts = eap_tls_load_ca_cert(settings, value);
if (!cacerts) {
l_error("Failed to load %s", value);
return -EIO;
}
}
snprintf(client_cert_setting, sizeof(client_cert_setting),
"%sClientCert", prefix);
client_cert = l_settings_get_string(settings, "Security",
client_cert_setting);
if (client_cert) {
cert = eap_tls_load_client_cert(settings, client_cert);
if (!cert) {
l_error("Failed to load %s", client_cert);
ret = -EIO;
goto done;
}
if (!l_certchain_verify(cert, cacerts, &error_str)) {
if (cacerts)
l_error("Certificate chain %s is not trusted "
"by any CA in %s or fails verification"
": %s", client_cert, value, error_str);
else
l_error("Certificate chain %s fails "
"verification: %s",
client_cert, error_str);
ret = -EINVAL;
goto done;
}
}
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientKey", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value && !client_cert) {
l_error("%s present but no client certificate (%s)",
setting_key, client_cert_setting);
ret = -ENOENT;
goto done;
} else if (!value && client_cert) {
l_error("%s present but no client private key (%s)",
client_cert_setting, setting_key);
ret = -ENOENT;
goto done;
}
snprintf(passphrase_setting, sizeof(passphrase_setting),
"%sClientKeyPassphrase", prefix);
passphrase = l_settings_get_string(settings, "Security",
passphrase_setting);
if (!passphrase) {
const struct eap_secret_info *secret;
secret = l_queue_find(secrets, eap_secret_info_match,
passphrase_setting);
if (secret)
passphrase = l_strdup(secret->value);
}
if (!value) {
if (passphrase) {
l_error("%s present but no client private key"
" value set (%s)", passphrase_setting,
setting_key);
ret = -ENOENT;
goto done;
}
ret = 0;
goto done;
}
priv_key = eap_tls_load_priv_key(settings, value, passphrase,
&is_encrypted);
if (!priv_key) {
if (!is_encrypted) {
l_error("Error loading client private key %s", value);
ret = -EIO;
goto done;
}
if (passphrase) {
l_error("Error loading encrypted client private key %s",
value);
ret = -EACCES;
goto done;
}
/*
* We've got an encrypted key and passphrase was not saved
* in the network settings, need to request the passphrase.
*/
eap_append_secret(out_missing,
EAP_SECRET_LOCAL_PKEY_PASSPHRASE,
passphrase_setting, NULL, value,
EAP_CACHE_TEMPORARY);
ret = 0;
goto done;
}
if (passphrase && !is_encrypted) {
l_error("%s present but client private key %s is not encrypted",
passphrase_setting, value);
ret = -ENOENT;
goto done;
}
if (!l_key_get_info(priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
&size, &is_public) || is_public) {
l_error("%s is not a private key or l_key_get_info fails",
value);
ret = -EINVAL;
goto done;
}
size /= 8;
encrypted = alloca(size);
decrypted = alloca(size);
pub_key = l_cert_get_pubkey(l_certchain_get_leaf(cert));
if (!pub_key) {
l_error("l_cert_get_pubkey fails for %s", client_cert);
ret = -EIO;
goto done;
}
result = l_key_encrypt(pub_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
"", encrypted, 1, size);
l_key_free(pub_key);
if (result != (ssize_t) size) {
l_error("l_key_encrypt fails with %s: %s", client_cert,
strerror(-result));
ret = result;
goto done;
}
result = l_key_decrypt(priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE,
encrypted, decrypted, size, size);
if (result < 0) {
l_error("l_key_decrypt fails with %s: %s", value,
strerror(-result));
ret = result;
goto done;
}
if (result != 1 || decrypted[0] != 0) {
l_error("Private key %s does not match certificate %s", value,
client_cert);
ret = -EINVAL;
goto done;
}
/*
* Require CACert if ServerDomainMask is present. If the server
* certificate is not being checked against any trusted certificates
* there's no point validating its contents and we wouldn't even
* receive the subject DN from ell, because it may be freely made up.
*/
snprintf(setting_key, sizeof(setting_key), "%sServerDomainMask",
prefix);
domain_mask_str = l_settings_get_value(settings, "Security",
setting_key);
if (domain_mask_str && !cacerts) {
l_error("%s was set but no CA Certificates given", setting_key);
ret = -EINVAL;
goto done;
}
ret = 0;
done:
l_queue_destroy(cacerts,
(l_queue_destroy_func_t) l_cert_free);
l_certchain_free(cert);
l_key_free(priv_key);
if (passphrase)
explicit_bzero(passphrase, strlen(passphrase));
return ret;
}
bool eap_tls_common_settings_load(struct eap_state *eap,
struct l_settings *settings, const char *prefix,
const struct eap_tls_variant_ops *variant_ops,
void *variant_data)
{
struct eap_tls_state *eap_tls;
char setting_key[72];
char *domain_mask_str;
L_AUTO_FREE_VAR(char *, value) = NULL;
L_AUTO_FREE_VAR(char *, passphrase) = NULL;
eap_tls = l_new(struct eap_tls_state, 1);
eap_tls->version_negotiated = EAP_TLS_VERSION_NOT_NEGOTIATED;
eap_tls->variant_ops = variant_ops;
eap_tls->variant_data = variant_data;
snprintf(setting_key, sizeof(setting_key), "%sCACert", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->ca_cert = eap_tls_load_ca_cert(settings, value);
if (!eap_tls->ca_cert) {
l_error("Could not load CACert %s", value);
goto load_error;
}
}
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientCert", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->client_cert = eap_tls_load_client_cert(settings,
value);
if (!eap_tls->ca_cert) {
l_error("Could not load ClientCert %s", value);
goto load_error;
}
}
l_free(value);
snprintf(setting_key, sizeof(setting_key), "%sClientKeyPassphrase",
prefix);
passphrase = l_settings_get_string(settings, "Security", setting_key);
snprintf(setting_key, sizeof(setting_key), "%sClientKey", prefix);
value = l_settings_get_string(settings, "Security", setting_key);
if (value) {
eap_tls->client_key = eap_tls_load_priv_key(settings, value,
passphrase,
NULL);
if (!eap_tls->client_key) {
l_error("Could not load ClientKey %s", value);
goto load_error;
}
}
snprintf(setting_key, sizeof(setting_key), "%sServerDomainMask",
prefix);
domain_mask_str = l_settings_get_string(settings, "Security",
setting_key);
if (domain_mask_str) {
eap_tls->domain_mask = l_strsplit(domain_mask_str, ';');
l_free(domain_mask_str);
}
eap_set_data(eap, eap_tls);
return true;
load_error:
__eap_tls_common_state_free(eap_tls);
return false;
}
void eap_tls_common_set_completed(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
eap_tls->method_completed = true;
}
void eap_tls_common_set_phase2_failed(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
eap_tls->phase2_failed = true;
}
enum eap_tls_version eap_tls_common_get_negotiated_version(
struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
return eap_tls->version_negotiated;
}
void *eap_tls_common_get_variant_data(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
return eap_tls->variant_data;
}
bool eap_tls_common_tunnel_prf_get_bytes(struct eap_state *eap,
bool use_master_secret,
const char *label, uint8_t *buf,
size_t buf_len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
return l_tls_prf_get_bytes(eap_tls->tunnel, use_master_secret,
label, buf, buf_len);
}
void eap_tls_common_tunnel_send(struct eap_state *eap, const uint8_t *data,
size_t data_len)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
l_tls_write(eap_tls->tunnel, data, data_len);
}
void eap_tls_common_tunnel_close(struct eap_state *eap)
{
struct eap_tls_state *eap_tls = eap_get_data(eap);
l_tls_close(eap_tls->tunnel);
}