Systemd/src/network/netdev/macsec.c

1252 lines
42 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <linux/if_macsec.h>
#include <linux/genetlink.h>
#include "conf-parser.h"
#include "fileio.h"
#include "hashmap.h"
#include "hexdecoct.h"
#include "macsec.h"
#include "memory-util.h"
#include "netlink-util.h"
#include "networkd-manager.h"
#include "path-util.h"
#include "socket-util.h"
#include "string-table.h"
#include "string-util.h"
#include "util.h"
static void security_association_clear(SecurityAssociation *sa) {
if (!sa)
return;
explicit_bzero_safe(sa->key, sa->key_len);
free(sa->key);
free(sa->key_file);
}
static void security_association_init(SecurityAssociation *sa) {
assert(sa);
sa->activate = -1;
sa->use_for_encoding = -1;
}
static void macsec_receive_association_free(ReceiveAssociation *c) {
if (!c)
return;
if (c->macsec && c->section)
ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section);
network_config_section_free(c->section);
security_association_clear(&c->sa);
free(c);
}
DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveAssociation, macsec_receive_association_free);
static int macsec_receive_association_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveAssociation **ret) {
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
_cleanup_(macsec_receive_association_freep) ReceiveAssociation *c = NULL;
int r;
assert(s);
assert(ret);
assert(filename);
assert(section_line > 0);
r = network_config_section_new(filename, section_line, &n);
if (r < 0)
return r;
c = ordered_hashmap_get(s->receive_associations_by_section, n);
if (c) {
*ret = TAKE_PTR(c);
return 0;
}
c = new(ReceiveAssociation, 1);
if (!c)
return -ENOMEM;
*c = (ReceiveAssociation) {
.macsec = s,
.section = TAKE_PTR(n),
};
security_association_init(&c->sa);
r = ordered_hashmap_ensure_allocated(&s->receive_associations_by_section, &network_config_hash_ops);
if (r < 0)
return r;
r = ordered_hashmap_put(s->receive_associations_by_section, c->section, c);
if (r < 0)
return r;
*ret = TAKE_PTR(c);
return 0;
}
static void macsec_receive_channel_free(ReceiveChannel *c) {
if (!c)
return;
if (c->macsec) {
if (c->sci.as_uint64 > 0)
ordered_hashmap_remove_value(c->macsec->receive_channels, &c->sci.as_uint64, c);
if (c->section)
ordered_hashmap_remove(c->macsec->receive_channels_by_section, c->section);
}
network_config_section_free(c->section);
free(c);
}
DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveChannel, macsec_receive_channel_free);
static int macsec_receive_channel_new(MACsec *s, uint64_t sci, ReceiveChannel **ret) {
ReceiveChannel *c;
assert(s);
c = new(ReceiveChannel, 1);
if (!c)
return -ENOMEM;
*c = (ReceiveChannel) {
.macsec = s,
.sci.as_uint64 = sci,
};
*ret = c;
return 0;
}
static int macsec_receive_channel_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveChannel **ret) {
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
_cleanup_(macsec_receive_channel_freep) ReceiveChannel *c = NULL;
int r;
assert(s);
assert(ret);
assert(filename);
assert(section_line > 0);
r = network_config_section_new(filename, section_line, &n);
if (r < 0)
return r;
c = ordered_hashmap_get(s->receive_channels_by_section, n);
if (c) {
*ret = TAKE_PTR(c);
return 0;
}
r = macsec_receive_channel_new(s, 0, &c);
if (r < 0)
return r;
c->section = TAKE_PTR(n);
r = ordered_hashmap_ensure_allocated(&s->receive_channels_by_section, &network_config_hash_ops);
if (r < 0)
return r;
r = ordered_hashmap_put(s->receive_channels_by_section, c->section, c);
if (r < 0)
return r;
*ret = TAKE_PTR(c);
return 0;
}
static void macsec_transmit_association_free(TransmitAssociation *a) {
if (!a)
return;
if (a->macsec && a->section)
ordered_hashmap_remove(a->macsec->transmit_associations_by_section, a->section);
network_config_section_free(a->section);
security_association_clear(&a->sa);
free(a);
}
DEFINE_NETWORK_SECTION_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free);
static int macsec_transmit_association_new_static(MACsec *s, const char *filename, unsigned section_line, TransmitAssociation **ret) {
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
_cleanup_(macsec_transmit_association_freep) TransmitAssociation *a = NULL;
int r;
assert(s);
assert(ret);
assert(filename);
assert(section_line > 0);
r = network_config_section_new(filename, section_line, &n);
if (r < 0)
return r;
a = ordered_hashmap_get(s->transmit_associations_by_section, n);
if (a) {
*ret = TAKE_PTR(a);
return 0;
}
a = new(TransmitAssociation, 1);
if (!a)
return -ENOMEM;
*a = (TransmitAssociation) {
.macsec = s,
.section = TAKE_PTR(n),
};
security_association_init(&a->sa);
r = ordered_hashmap_ensure_allocated(&s->transmit_associations_by_section, &network_config_hash_ops);
if (r < 0)
return r;
r = ordered_hashmap_put(s->transmit_associations_by_section, a->section, a);
if (r < 0)
return r;
*ret = TAKE_PTR(a);
return 0;
}
static int netdev_macsec_fill_message(NetDev *netdev, int command, sd_netlink_message **ret) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(netdev);
assert(netdev->ifindex > 0);
r = sd_genl_message_new(netdev->manager->genl, SD_GENL_MACSEC, command, &m);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to create generic netlink message: %m");
r = sd_netlink_message_append_u32(m, MACSEC_ATTR_IFINDEX, netdev->ifindex);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_IFINDEX attribute: %m");
*ret = TAKE_PTR(m);
return 0;
}
static int netdev_macsec_fill_message_sci(NetDev *netdev, MACsecSCI *sci, sd_netlink_message *m) {
int r;
assert(netdev);
assert(m);
assert(sci);
r = sd_netlink_message_open_container(m, MACSEC_ATTR_RXSC_CONFIG);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m");
r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci->as_uint64);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_RXSC_ATTR_SCI attribute: %m");
r = sd_netlink_message_close_container(m);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m");
return 0;
}
static int netdev_macsec_fill_message_sa(NetDev *netdev, SecurityAssociation *a, sd_netlink_message *m) {
int r;
assert(netdev);
assert(a);
assert(m);
r = sd_netlink_message_open_container(m, MACSEC_ATTR_SA_CONFIG);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m");
r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_AN, a->association_number);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_AN attribute: %m");
if (a->packet_number > 0) {
r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->packet_number);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_PN attribute: %m");
}
if (a->key_len > 0) {
r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEYID, a->key_id, MACSEC_KEYID_LEN);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEYID attribute: %m");
r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEY, a->key, a->key_len);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEY attribute: %m");
}
if (a->activate >= 0) {
r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_ACTIVE, a->activate);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_ACTIVE attribute: %m");
}
r = sd_netlink_message_close_container(m);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m");
return 0;
}
static int macsec_receive_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
int r;
assert(netdev);
assert(netdev->state != _NETDEV_STATE_INVALID);
r = sd_netlink_message_get_errno(m);
if (r == -EEXIST)
log_netdev_info(netdev,
"MACsec receive secure association exists, "
"using existing without changing its parameters");
else if (r < 0) {
log_netdev_warning_errno(netdev, r,
"Failed to add receive secure association: %m");
netdev_drop(netdev);
return 1;
}
log_netdev_debug(netdev, "Receive secure association is configured");
return 1;
}
static int netdev_macsec_configure_receive_association(NetDev *netdev, ReceiveAssociation *a) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(netdev);
assert(a);
r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSA, &m);
if (r < 0)
return r;
r = netdev_macsec_fill_message_sa(netdev, &a->sa, m);
if (r < 0)
return r;
r = netdev_macsec_fill_message_sci(netdev, &a->sci, m);
if (r < 0)
return r;
r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_association_handler,
netdev_destroy_callback, netdev);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to configure receive secure association: %m");
netdev_ref(netdev);
return 0;
}
static int macsec_receive_channel_handler(sd_netlink *rtnl, sd_netlink_message *m, ReceiveChannel *c) {
NetDev *netdev;
unsigned i;
int r;
assert(c);
assert(c->macsec);
netdev = NETDEV(c->macsec);
assert(netdev->state != _NETDEV_STATE_INVALID);
r = sd_netlink_message_get_errno(m);
if (r == -EEXIST)
log_netdev_debug(netdev,
"MACsec receive channel exists, "
"using existing without changing its parameters");
else if (r < 0) {
log_netdev_warning_errno(netdev, r,
"Failed to add receive secure channel: %m");
netdev_drop(netdev);
return 1;
}
log_netdev_debug(netdev, "Receive channel is configured");
for (i = 0; i < c->n_rxsa; i++) {
r = netdev_macsec_configure_receive_association(netdev, c->rxsa[i]);
if (r < 0) {
log_netdev_warning_errno(netdev, r,
"Failed to configure receive security association: %m");
netdev_drop(netdev);
return 1;
}
}
return 1;
}
static void receive_channel_destroy_callback(ReceiveChannel *c) {
assert(c);
assert(c->macsec);
netdev_unref(NETDEV(c->macsec));
}
static int netdev_macsec_configure_receive_channel(NetDev *netdev, ReceiveChannel *c) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(netdev);
assert(c);
r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSC, &m);
if (r < 0)
return r;
r = netdev_macsec_fill_message_sci(netdev, &c->sci, m);
if (r < 0)
return r;
r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_channel_handler,
receive_channel_destroy_callback, c);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to configure receive channel: %m");
netdev_ref(netdev);
return 0;
}
static int macsec_transmit_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
int r;
assert(netdev);
assert(netdev->state != _NETDEV_STATE_INVALID);
r = sd_netlink_message_get_errno(m);
if (r == -EEXIST)
log_netdev_info(netdev,
"MACsec transmit secure association exists, "
"using existing without changing its parameters");
else if (r < 0) {
log_netdev_warning_errno(netdev, r,
"Failed to add transmit secure association: %m");
netdev_drop(netdev);
return 1;
}
log_netdev_debug(netdev, "Transmit secure association is configured");
return 1;
}
static int netdev_macsec_configure_transmit_association(NetDev *netdev, TransmitAssociation *a) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(netdev);
assert(a);
r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_TXSA, &m);
if (r < 0)
return r;
r = netdev_macsec_fill_message_sa(netdev, &a->sa, m);
if (r < 0)
return r;
r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_transmit_association_handler,
netdev_destroy_callback, netdev);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Failed to configure transmit secure association: %m");
netdev_ref(netdev);
return 0;
}
static int netdev_macsec_configure(NetDev *netdev, Link *link, sd_netlink_message *m) {
TransmitAssociation *a;
ReceiveChannel *c;
MACsec *s;
int r;
assert(netdev);
s = MACSEC(netdev);
assert(s);
ORDERED_HASHMAP_FOREACH(a, s->transmit_associations_by_section) {
r = netdev_macsec_configure_transmit_association(netdev, a);
if (r < 0)
return r;
}
ORDERED_HASHMAP_FOREACH(c, s->receive_channels) {
r = netdev_macsec_configure_receive_channel(netdev, c);
if (r < 0)
return r;
}
return 0;
}
static int netdev_macsec_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
MACsec *v;
int r;
assert(netdev);
assert(m);
v = MACSEC(netdev);
if (v->port > 0) {
r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_PORT attribute: %m");
}
if (v->encrypt >= 0) {
r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCRYPT attribute: %m");
}
r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCODING_SA, v->encoding_an);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCODING_SA attribute: %m");
return r;
}
int config_parse_macsec_port(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
_cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL;
MACsec *s = userdata;
uint16_t port;
void *dest;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
/* This parses port used to make Secure Channel Identifier (SCI) */
if (streq(section, "MACsec"))
dest = &s->port;
else if (streq(section, "MACsecReceiveChannel")) {
r = macsec_receive_channel_new_static(s, filename, section_line, &c);
if (r < 0)
return log_oom();
dest = &c->sci.port;
} else {
assert(streq(section, "MACsecReceiveAssociation"));
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
dest = &b->sci.port;
}
r = parse_ip_port(rvalue, &port);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse port '%s' for secure channel identifier. Ignoring assignment: %m",
rvalue);
return 0;
}
unaligned_write_be16(dest, port);
TAKE_PTR(b);
TAKE_PTR(c);
return 0;
}
int config_parse_macsec_hw_address(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
_cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL;
MACsec *s = userdata;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "MACsecReceiveChannel"))
r = macsec_receive_channel_new_static(s, filename, section_line, &c);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
r = ether_addr_from_string(rvalue, b ? &b->sci.mac : &c->sci.mac);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse MAC address for secure channel identifier. "
"Ignoring assignment: %s", rvalue);
return 0;
}
TAKE_PTR(b);
TAKE_PTR(c);
return 0;
}
int config_parse_macsec_packet_number(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
MACsec *s = userdata;
uint32_t val, *dest;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "MACsecTransmitAssociation"))
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
dest = a ? &a->sa.packet_number : &b->sa.packet_number;
r = safe_atou32(rvalue, &val);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse packet number. Ignoring assignment: %s", rvalue);
return 0;
}
if (streq(section, "MACsecTransmitAssociation") && val == 0) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid packet number. Ignoring assignment: %s", rvalue);
return 0;
}
*dest = val;
TAKE_PTR(a);
TAKE_PTR(b);
return 0;
}
int config_parse_macsec_key(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
_cleanup_(erase_and_freep) void *p = NULL;
MACsec *s = userdata;
SecurityAssociation *dest;
size_t l;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
(void) warn_file_is_world_accessible(filename, NULL, unit, line);
if (streq(section, "MACsecTransmitAssociation"))
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
dest = a ? &a->sa : &b->sa;
r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m");
return 0;
}
if (l != 16) {
/* See DEFAULT_SAK_LEN in drivers/net/macsec.c */
log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid key length (%zu). Ignoring assignment", l);
return 0;
}
explicit_bzero_safe(dest->key, dest->key_len);
free_and_replace(dest->key, p);
dest->key_len = l;
TAKE_PTR(a);
TAKE_PTR(b);
return 0;
}
int config_parse_macsec_key_file(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
_cleanup_free_ char *path = NULL;
MACsec *s = userdata;
char **dest;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "MACsecTransmitAssociation"))
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
dest = a ? &a->sa.key_file : &b->sa.key_file;
if (isempty(rvalue)) {
*dest = mfree(*dest);
return 0;
}
path = strdup(rvalue);
if (!path)
return log_oom();
if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0)
return 0;
free_and_replace(*dest, path);
TAKE_PTR(a);
TAKE_PTR(b);
return 0;
}
int config_parse_macsec_key_id(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
_cleanup_free_ void *p = NULL;
MACsec *s = userdata;
uint8_t *dest;
size_t l;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "MACsecTransmitAssociation"))
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
r = unhexmem(rvalue, strlen(rvalue), &p, &l);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
2020-09-10 06:43:47 +02:00
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse KeyId=%s, ignoring assignment: %m", rvalue);
return 0;
}
if (l > MACSEC_KEYID_LEN) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
2020-09-10 06:43:47 +02:00
"Specified KeyId= is larger then the allowed maximum (%zu > %u), ignoring: %s",
l, MACSEC_KEYID_LEN, rvalue);
return 0;
}
dest = a ? a->sa.key_id : b->sa.key_id;
memcpy_safe(dest, p, l);
memzero(dest + l, MACSEC_KEYID_LEN - l);
TAKE_PTR(a);
TAKE_PTR(b);
return 0;
}
int config_parse_macsec_sa_activate(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
_cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
MACsec *s = userdata;
int *dest;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(section, "MACsecTransmitAssociation"))
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
else
r = macsec_receive_association_new_static(s, filename, section_line, &b);
if (r < 0)
return log_oom();
dest = a ? &a->sa.activate : &b->sa.activate;
if (isempty(rvalue))
r = -1;
else {
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse activation mode of %s security association. "
"Ignoring assignment: %s",
streq(section, "MACsecTransmitAssociation") ? "transmit" : "receive",
rvalue);
return 0;
}
}
*dest = r;
TAKE_PTR(a);
TAKE_PTR(b);
return 0;
}
int config_parse_macsec_use_for_encoding(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
MACsec *s = userdata;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = macsec_transmit_association_new_static(s, filename, section_line, &a);
if (r < 0)
return log_oom();
if (isempty(rvalue)) {
a->sa.use_for_encoding = -1;
TAKE_PTR(a);
return 0;
}
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse %s= setting. Ignoring assignment: %s",
lvalue, rvalue);
return 0;
}
a->sa.use_for_encoding = r;
if (a->sa.use_for_encoding > 0)
a->sa.activate = true;
TAKE_PTR(a);
return 0;
}
static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) {
_cleanup_(erase_and_freep) uint8_t *key = NULL;
size_t key_len;
int r;
assert(netdev);
assert(sa);
if (!sa->key_file)
return 0;
(void) warn_file_is_world_accessible(sa->key_file, NULL, NULL, 0);
r = read_full_file_full(
AT_FDCWD, sa->key_file, UINT64_MAX, SIZE_MAX,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
fileio: beef up READ_FULL_FILE_CONNECT_SOCKET to allow setting sender socket name This beefs up the READ_FULL_FILE_CONNECT_SOCKET logic of read_full_file_full() a bit: when used a sender socket name may be specified. If specified as NULL behaviour is as before: the client socket name is picked by the kernel. But if specified as non-NULL the client can pick a socket name to use when connecting. This is useful to communicate a minimal amount of metainformation from client to server, outside of the transport payload. Specifically, these beefs up the service credential logic to pass an abstract AF_UNIX socket name as client socket name when connecting via READ_FULL_FILE_CONNECT_SOCKET, that includes the requesting unit name and the eventual credential name. This allows servers implementing the trivial credential socket logic to distinguish clients: via a simple getpeername() it can be determined which unit is requesting a credential, and which credential specifically. Example: with this patch in place, in a unit file "waldo.service" a configuration line like the following: LoadCredential=foo:/run/quux/creds.sock will result in a connection to the AF_UNIX socket /run/quux/creds.sock, originating from an abstract namespace AF_UNIX socket: @$RANDOM/unit/waldo.service/foo (The $RANDOM is replaced by some randomized string. This is included in the socket name order to avoid namespace squatting issues: the abstract socket namespace is open to unprivileged users after all, and care needs to be taken not to use guessable names) The services listening on the /run/quux/creds.sock socket may thus easily retrieve the name of the unit the credential is requested for plus the credential name, via a simpler getpeername(), discarding the random preifx and the /unit/ string. This logic uses "/" as separator between the fields, since both unit names and credential names appear in the file system, and thus are designed to use "/" as outer separators. Given that it's a good safe choice to use as separators here, too avoid any conflicts. This is a minimal patch only: the new logic is used only for the unit file credential logic. For other places where we use READ_FULL_FILE_CONNECT_SOCKET it is probably a good idea to use this scheme too, but this should be done carefully in later patches, since the socket names become API that way, and we should determine the right amount of info to pass over.
2020-11-02 12:07:51 +01:00
NULL, (char **) &key, &key_len);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"Failed to read key from '%s', ignoring: %m",
sa->key_file);
if (key_len != 16)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"Invalid key length (%zu bytes), ignoring: %m", key_len);
explicit_bzero_safe(sa->key, sa->key_len);
free_and_replace(sa->key, key);
sa->key_len = key_len;
return 0;
}
static int macsec_receive_channel_verify(ReceiveChannel *c) {
NetDev *netdev;
int r;
assert(c);
assert(c->macsec);
netdev = NETDEV(c->macsec);
if (section_is_invalid(c->section))
return -EINVAL;
if (ether_addr_is_null(&c->sci.mac))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec receive channel without MAC address configured. "
"Ignoring [MACsecReceiveChannel] section from line %u",
c->section->filename, c->section->line);
if (c->sci.port == 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec receive channel without port configured. "
"Ignoring [MACsecReceiveChannel] section from line %u",
c->section->filename, c->section->line);
r = ordered_hashmap_ensure_allocated(&c->macsec->receive_channels, &uint64_hash_ops);
if (r < 0)
return log_oom();
r = ordered_hashmap_put(c->macsec->receive_channels, &c->sci.as_uint64, c);
if (r == -EEXIST)
return log_netdev_error_errno(netdev, r,
"%s: Multiple [MACsecReceiveChannel] sections have same SCI, "
"Ignoring [MACsecReceiveChannel] section from line %u",
c->section->filename, c->section->line);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: Failed to store [MACsecReceiveChannel] section at hashmap, "
"Ignoring [MACsecReceiveChannel] section from line %u",
c->section->filename, c->section->line);
return 0;
}
static int macsec_transmit_association_verify(TransmitAssociation *t) {
NetDev *netdev;
int r;
assert(t);
assert(t->macsec);
netdev = NETDEV(t->macsec);
if (section_is_invalid(t->section))
return -EINVAL;
if (t->sa.packet_number == 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec transmit secure association without PacketNumber= configured. "
"Ignoring [MACsecTransmitAssociation] section from line %u",
t->section->filename, t->section->line);
r = macsec_read_key_file(netdev, &t->sa);
if (r < 0)
return r;
if (t->sa.key_len <= 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec transmit secure association without key configured. "
"Ignoring [MACsecTransmitAssociation] section from line %u",
t->section->filename, t->section->line);
return 0;
}
static int macsec_receive_association_verify(ReceiveAssociation *a) {
ReceiveChannel *c;
NetDev *netdev;
int r;
assert(a);
assert(a->macsec);
netdev = NETDEV(a->macsec);
if (section_is_invalid(a->section))
return -EINVAL;
r = macsec_read_key_file(netdev, &a->sa);
if (r < 0)
return r;
if (a->sa.key_len <= 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec receive secure association without key configured. "
"Ignoring [MACsecReceiveAssociation] section from line %u",
a->section->filename, a->section->line);
if (ether_addr_is_null(&a->sci.mac))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec receive secure association without MAC address configured. "
"Ignoring [MACsecReceiveAssociation] section from line %u",
a->section->filename, a->section->line);
if (a->sci.port == 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"%s: MACsec receive secure association without port configured. "
"Ignoring [MACsecReceiveAssociation] section from line %u",
a->section->filename, a->section->line);
c = ordered_hashmap_get(a->macsec->receive_channels, &a->sci.as_uint64);
if (!c) {
_cleanup_(macsec_receive_channel_freep) ReceiveChannel *new_channel = NULL;
r = macsec_receive_channel_new(a->macsec, a->sci.as_uint64, &new_channel);
if (r < 0)
return log_oom();
r = ordered_hashmap_ensure_allocated(&a->macsec->receive_channels, &uint64_hash_ops);
if (r < 0)
return log_oom();
r = ordered_hashmap_put(a->macsec->receive_channels, &new_channel->sci.as_uint64, new_channel);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"%s: Failed to store receive channel at hashmap, "
"Ignoring [MACsecReceiveAssociation] section from line %u",
a->section->filename, a->section->line);
c = TAKE_PTR(new_channel);
}
if (c->n_rxsa >= MACSEC_MAX_ASSOCIATION_NUMBER)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(ERANGE),
"%s: Too many [MACsecReceiveAssociation] sections for the same receive channel, "
"Ignoring [MACsecReceiveAssociation] section from line %u",
a->section->filename, a->section->line);
a->sa.association_number = c->n_rxsa;
c->rxsa[c->n_rxsa++] = a;
return 0;
}
static int netdev_macsec_verify(NetDev *netdev, const char *filename) {
MACsec *v = MACSEC(netdev);
TransmitAssociation *a;
ReceiveAssociation *n;
ReceiveChannel *c;
uint8_t an, encoding_an;
bool use_for_encoding;
int r;
assert(netdev);
assert(v);
assert(filename);
ORDERED_HASHMAP_FOREACH(c, v->receive_channels_by_section) {
r = macsec_receive_channel_verify(c);
if (r < 0)
macsec_receive_channel_free(c);
}
an = 0;
use_for_encoding = false;
encoding_an = 0;
ORDERED_HASHMAP_FOREACH(a, v->transmit_associations_by_section) {
r = macsec_transmit_association_verify(a);
if (r < 0) {
macsec_transmit_association_free(a);
continue;
}
if (an >= MACSEC_MAX_ASSOCIATION_NUMBER) {
log_netdev_error(netdev,
"%s: Too many [MACsecTransmitAssociation] sections configured. "
"Ignoring [MACsecTransmitAssociation] section from line %u",
a->section->filename, a->section->line);
macsec_transmit_association_free(a);
continue;
}
a->sa.association_number = an++;
if (a->sa.use_for_encoding > 0) {
if (use_for_encoding) {
log_netdev_warning(netdev,
"%s: Multiple security associations are set to be used for transmit channel."
"Disabling UseForEncoding= in [MACsecTransmitAssociation] section from line %u",
a->section->filename, a->section->line);
a->sa.use_for_encoding = false;
} else {
encoding_an = a->sa.association_number;
use_for_encoding = true;
}
}
}
assert(encoding_an < MACSEC_MAX_ASSOCIATION_NUMBER);
v->encoding_an = encoding_an;
ORDERED_HASHMAP_FOREACH(n, v->receive_associations_by_section) {
r = macsec_receive_association_verify(n);
if (r < 0)
macsec_receive_association_free(n);
}
return 0;
}
static void macsec_init(NetDev *netdev) {
MACsec *v;
assert(netdev);
v = MACSEC(netdev);
assert(v);
v->encrypt = -1;
}
static void macsec_done(NetDev *netdev) {
MACsec *t;
assert(netdev);
t = MACSEC(netdev);
assert(t);
ordered_hashmap_free_with_destructor(t->receive_channels, macsec_receive_channel_free);
ordered_hashmap_free_with_destructor(t->receive_channels_by_section, macsec_receive_channel_free);
ordered_hashmap_free_with_destructor(t->transmit_associations_by_section, macsec_transmit_association_free);
ordered_hashmap_free_with_destructor(t->receive_associations_by_section, macsec_receive_association_free);
}
const NetDevVTable macsec_vtable = {
.object_size = sizeof(MACsec),
.init = macsec_init,
.sections = NETDEV_COMMON_SECTIONS "MACsec\0MACsecReceiveChannel\0MACsecTransmitAssociation\0MACsecReceiveAssociation\0",
.fill_message_create = netdev_macsec_fill_message_create,
.post_create = netdev_macsec_configure,
.done = macsec_done,
.create_type = NETDEV_CREATE_STACKED,
.config_verify = netdev_macsec_verify,
.generate_mac = true,
};