/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #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 "parse-helpers.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" #include "unaligned.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 ReceiveAssociation* macsec_receive_association_free(ReceiveAssociation *c) { if (!c) return NULL; if (c->macsec && c->section) ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section); config_section_free(c->section); security_association_clear(&c->sa); return mfree(c); } DEFINE_SECTION_CLEANUP_FUNCTIONS(ReceiveAssociation, macsec_receive_association_free); static int macsec_receive_association_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveAssociation **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; _cleanup_(macsec_receive_association_freep) ReceiveAssociation *c = NULL; int r; assert(s); assert(ret); assert(filename); assert(section_line > 0); r = 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_put(&s->receive_associations_by_section, &config_section_hash_ops, c->section, c); if (r < 0) return r; *ret = TAKE_PTR(c); return 0; } static ReceiveChannel* macsec_receive_channel_free(ReceiveChannel *c) { if (!c) return NULL; 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); } config_section_free(c->section); return mfree(c); } DEFINE_SECTION_CLEANUP_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_(config_section_freep) ConfigSection *n = NULL; _cleanup_(macsec_receive_channel_freep) ReceiveChannel *c = NULL; int r; assert(s); assert(ret); assert(filename); assert(section_line > 0); r = 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_put(&s->receive_channels_by_section, &config_section_hash_ops, c->section, c); if (r < 0) return r; *ret = TAKE_PTR(c); return 0; } static TransmitAssociation* macsec_transmit_association_free(TransmitAssociation *a) { if (!a) return NULL; if (a->macsec && a->section) ordered_hashmap_remove(a->macsec->transmit_associations_by_section, a->section); config_section_free(a->section); security_association_clear(&a->sa); return mfree(a); } DEFINE_SECTION_CLEANUP_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free); static int macsec_transmit_association_new_static(MACsec *s, const char *filename, unsigned section_line, TransmitAssociation **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; _cleanup_(macsec_transmit_association_freep) TransmitAssociation *a = NULL; int r; assert(s); assert(ret); assert(filename); assert(section_line > 0); r = 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_put(&s->transmit_associations_by_section, &config_section_hash_ops, a->section, a); if (r < 0) return r; *ret = TAKE_PTR(a); return 0; } static int netdev_macsec_create_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); assert(netdev->manager); r = sd_genl_message_new(netdev->manager->genl, MACSEC_GENL_NAME, command, &m); if (r < 0) return r; r = sd_netlink_message_append_u32(m, MACSEC_ATTR_IFINDEX, netdev->ifindex); if (r < 0) return r; *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 r; r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci->as_uint64); if (r < 0) return r; r = sd_netlink_message_close_container(m); if (r < 0) return r; 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 r; r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_AN, a->association_number); if (r < 0) return r; if (a->packet_number > 0) { r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->packet_number); if (r < 0) return r; } 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 r; r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEY, a->key, a->key_len); if (r < 0) return r; } if (a->activate >= 0) { r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_ACTIVE, a->activate); if (r < 0) return r; } r = sd_netlink_message_close_container(m); if (r < 0) return r; 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 it without changing parameters"); else if (r < 0) { log_netdev_warning_errno(netdev, r, "Failed to add receive secure association: %m"); netdev_enter_failed(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); if (!netdev_is_managed(netdev)) return 0; /* Already detached, due to e.g. reloading .netdev files. */ r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_RXSA, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); r = netdev_macsec_fill_message_sci(netdev, &a->sci, m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); 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) { assert(c); assert(c->macsec); NetDev *netdev = ASSERT_PTR(NETDEV(c->macsec)); int r; 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 it without changing parameters"); else if (r < 0) { log_netdev_warning_errno(netdev, r, "Failed to add receive secure channel: %m"); netdev_enter_failed(netdev); return 1; } log_netdev_debug(netdev, "Receive channel is configured"); for (unsigned 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_enter_failed(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); if (!netdev_is_managed(netdev)) return 0; /* Already detached, due to e.g. reloading .netdev files. */ r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_RXSC, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); r = netdev_macsec_fill_message_sci(netdev, &c->sci, m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); 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 it without changing parameters"); else if (r < 0) { log_netdev_warning_errno(netdev, r, "Failed to add transmit secure association: %m"); netdev_enter_failed(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); if (!netdev_is_managed(netdev)) return 0; /* Already detached, due to e.g. reloading .netdev files. */ r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_TXSA, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); 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) { MACsec *s = MACSEC(netdev); TransmitAssociation *a; ReceiveChannel *c; int r; 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) { assert(m); MACsec *v = MACSEC(netdev); int r; if (v->encrypt >= 0) { r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt); if (r < 0) return r; } r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCODING_SA, v->encoding_an); if (r < 0) return r; /* The properties below cannot be updated, and the kernel refuses the whole request if one of the * following attributes is set for an existing interface. */ if (netdev->ifindex > 0) return 0; if (v->port > 0) { r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port); if (r < 0) return r; } /* Currently not supported by networkd, but IFLA_MACSEC_CIPHER_SUITE, IFLA_MACSEC_ICV_LEN, and * IFLA_MACSEC_SCI can neither set for an existing interface. */ return 0; } 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) { assert(filename); assert(section); assert(lvalue); assert(rvalue); assert(data); MACsec *s = ASSERT_PTR(userdata); _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; uint16_t port; void *dest; int r; /* 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) { assert(filename); assert(section); assert(lvalue); assert(rvalue); assert(data); MACsec *s = ASSERT_PTR(userdata); _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; int r; 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 = parse_ether_addr(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) { assert(filename); assert(section); assert(lvalue); assert(rvalue); assert(data); MACsec *s = ASSERT_PTR(userdata); _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; uint32_t val, *dest; int r; 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, SIZE_MAX, /* secure = */ 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|PATH_CHECK_NON_API_VFS, 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, &p, &l); if (r == -ENOMEM) return log_oom(); if (r < 0) { 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, "Specified KeyId= is larger then the allowed maximum (%zu > %i), 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, 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; r = parse_tristate(rvalue, dest); 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; } 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_tristate(rvalue, &a->sa.use_for_encoding); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s= setting. Ignoring assignment: %s", lvalue, rvalue); return 0; } 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; r = read_full_file_full( AT_FDCWD, sa->key_file, UINT64_MAX, MACSEC_KEYID_LEN, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET | READ_FULL_FILE_FAIL_WHEN_LARGER, 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 != MACSEC_KEYID_LEN) 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_put(&c->macsec->receive_channels, &uint64_hash_ops, &c->sci.as_uint64, c); if (r == -ENOMEM) return log_oom(); 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_put(&a->macsec->receive_channels, &uint64_hash_ops, &new_channel->sci.as_uint64, new_channel); if (r == -ENOMEM) return log_oom(); 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) { assert(filename); MACsec *v = MACSEC(netdev); TransmitAssociation *a; ReceiveAssociation *n; ReceiveChannel *c; uint8_t an, encoding_an; bool use_for_encoding; int r; 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 = MACSEC(netdev); v->encrypt = -1; } static void macsec_done(NetDev *netdev) { MACsec *v = MACSEC(netdev); ordered_hashmap_free_with_destructor(v->receive_channels, macsec_receive_channel_free); ordered_hashmap_free_with_destructor(v->receive_channels_by_section, macsec_receive_channel_free); ordered_hashmap_free_with_destructor(v->transmit_associations_by_section, macsec_transmit_association_free); ordered_hashmap_free_with_destructor(v->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, .iftype = ARPHRD_ETHER, .generate_mac = true, };