odhcp6c: implement RKAP: Reconfiguration Key Authentication Protocol
authorNicolas BESNARD <[email protected]>
Fri, 31 Jan 2025 13:44:14 +0000 (13:44 +0000)
committerÁlvaro Fernández Rojas <[email protected]>
Mon, 3 Nov 2025 15:21:24 +0000 (16:21 +0100)
Problem: odhcp6c doesn't support authentication protocol other than
the Configuration Token protocol (RFC 3118).

https://www.ietf.org/archive/id/draft-ietf-dhc-rfc8415bis-12.html#name-reconfiguration-key-authent

Solution: implement Reconfiguration Key Authentication Protocol (RKAP)
and add the option to disable all authentication protocols (will discard
all reconfigure messages received).

Signed-off-by: Nicolas BESNARD <[email protected]>
Signed-off-by: Paul Donald <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/106
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
README
src/config.c
src/config.h
src/dhcpv6.c
src/odhcp6c.h
src/ubus.c

diff --git a/README b/README
index 1afed15fe30d8794313eaa96cfeb99253e25bf06..e8a725a3252920675d63ec75a52af37d4b3a5b66 100644 (file)
--- a/README
+++ b/README
@@ -127,3 +127,23 @@ The following RPC methods are available:
                - *req_addresses* (string{try|force|none}) : Request addresses 
                - *req_prefixes* (int) : Request Prefixes (0 = auto)
                - *stateful_only* (bool) : Discard advertisements without any address or prefix proposed
+               - *irt_default* (int) : Default information refresh time (expressed in seconds)
+               - *irt_min* (int) : Minimum information refresh time (expressed in seconds)
+               - *rand_factor* (int) : Randomization factor for retransmission timeout
+               - *msg_solicit* (table) : Retransmission settings for SOLICIT
+               - *msg_request* (table) : Retransmission settings for REQUEST
+               - *msg_renew* (table) : Retransmission settings for RENEW
+               - *msg_rebind* (table) : Retransmission settings for REBIND
+               - *msg_release* (table) : Retransmission settings for RELEASE
+               - *msg_decline* (table) : Retransmission settings for DECLINE
+               - *msg_inforeq* (table) : Retransmission settings for INFORMATION-REQUEST
+               - *auth_protocol* (string) : Authentication protocol to be used ("None","ConfigurationToken", "ReconfigureKeyAuthentication")
+               - *auth_token* (string) : Authentication token to be used when AuthenticationProtocol is set to ConfigurationToken
+* *renew()* : Force transmission of RENEW/INFORMATION-REQUEST messages
+* *release()* : Force transmission of RELEASE message and start new cycle
+
+Input arguments for retransmission settings :
+       - *delay_max* (int) : Maximum delay of first message (expressed in seconds)
+       - *timeout_init* (int) : Initial message timeout (expressed in seconds)
+       - *timeout_max* (int) : Initial message timeout (expressed in seconds)
+       - *rc_max* (int) : Maximum message retry attempts
\ No newline at end of file
index b0ba835bfc9338402768e741a1f6f2edc340738b..dc07d2cc100509aacb11718c31b720eeb3906186 100644 (file)
@@ -108,6 +108,9 @@ void config_dhcp_reset(void) {
        config_dhcp.irt_default = DHCPV6_IRT_DEFAULT;
        config_dhcp.irt_min = DHCPV6_IRT_MIN;
        config_dhcp.rand_factor = DHCPV6_RAND_FACTOR;
+       config_dhcp.auth_protocol = AUTH_PROT_RKAP;
+       free(config_dhcp.auth_token);
+       config_dhcp.auth_token = NULL;
 }
 
 void config_set_release(bool enable) {
@@ -294,6 +297,30 @@ bool config_set_rand_factor(unsigned int value)
        return true;
 }
 
+bool config_set_auth_protocol(const char* protocol)
+{
+       if (!strcmp(protocol, "None")) {
+               config_dhcp.auth_protocol = AUTH_PROT_NONE;
+       } else if (!strcmp(protocol, "ConfigurationToken")) {
+               config_dhcp.auth_protocol = AUTH_PROT_TOKEN;
+       } else if (!strcmp(protocol, "ReconfigureKeyAuthentication")) {
+               config_dhcp.auth_protocol = AUTH_PROT_RKAP;
+       } else {
+               syslog(LOG_ERR, "Invalid Authentication protocol");
+               return false;
+       }
+
+       return true;
+}
+
+bool config_set_auth_token(const char* token)
+{
+       free(config_dhcp.auth_token);
+
+       config_dhcp.auth_token = strdup(token);
+       return config_dhcp.auth_token != NULL; 
+}
+
 static int config_parse_opt_u8(const char *src, uint8_t **dst)
 {
        int len = strlen(src);
index f0edade16f135acdad469e348a085d4b55018d78..8356360eecd79dd5e797c539476eae68e09fe35e 100644 (file)
@@ -97,6 +97,8 @@ struct config_dhcp {
        uint32_t irt_default;
        uint32_t irt_min;
        uint16_t rand_factor;
+       enum odhcp6c_auth_protocol auth_protocol;
+       char* auth_token;
 };
 
 struct config_dhcp *config_dhcp_get(void);
@@ -121,6 +123,8 @@ bool config_set_rtx_rc_max(enum config_dhcp_msg msg, unsigned int value);
 bool config_set_irt_default(unsigned int value);
 bool config_set_irt_min(unsigned int value);
 bool config_set_rand_factor(unsigned int value);
+bool config_set_auth_protocol(const char* protocol);
+bool config_set_auth_token(const char* token);
 
 int config_add_opt(const uint16_t code, const uint8_t *data, const uint16_t len);
 int config_parse_opt_data(const char *data, uint8_t **dst, const unsigned int type, const bool array);
index d97a732ad78b70a195b069a37ec7835a3e91427c..2640b776c62a5cc016090a0e0b4507b2600bae9b 100644 (file)
@@ -251,7 +251,8 @@ static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
 static enum dhcpv6_state dhcpv6_state = DHCPV6_INIT;
 static int dhcpv6_state_timeout = 0;
 
-// Reconfigure key
+// Authentication options
+static enum odhcp6c_auth_protocol auth_protocol = AUTH_PROT_RKAP;
 static uint8_t reconf_key[16];
 
 // client options
@@ -554,6 +555,7 @@ int init_dhcpv6(const char *ifname)
        na_mode = config_dhcp->ia_na_mode;
        pd_mode = config_dhcp->ia_pd_mode;
        stateful_only_mode = config_dhcp->stateful_only_mode;
+       auth_protocol = config_dhcp->auth_protocol;
 
        sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
        if (sock < 0)
@@ -1040,7 +1042,7 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                rcmsg = DHCPV6_MSG_UNKNOWN;
        uint16_t otype, olen = UINT16_MAX;
        bool clientid_ok = false, serverid_ok = false, rcauth_ok = false,
-               ia_present = false, options_valid = true;
+               auth_present = false, ia_present = false, options_valid = true;
 
        size_t client_id_len, server_id_len;
        void *client_id = odhcp6c_get_state(STATE_CLIENT_ID, &client_id_len);
@@ -1056,40 +1058,57 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                                                &odata[-4], server_id, server_id_len);
                        else
                                serverid_ok = true;
-               } else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
-                               sizeof(struct dhcpv6_auth_reconfigure)) {
-                       struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
-                       if (r->protocol != 3 || r->algorithm != 1 || r->reconf_type != 2)
+               } else if (otype == DHCPV6_OPT_AUTH) {
+                       struct dhcpv6_auth *r = (void*)&odata[-4];
+                       if (auth_present) {
+                               options_valid = false;          
                                continue;
+                       }
 
-                       md5_ctx_t md5;
-                       uint8_t serverhash[16], secretbytes[64];
-                       uint32_t hash[4];
-                       memcpy(serverhash, r->key, sizeof(serverhash));
-                       memset(r->key, 0, sizeof(r->key));
+                       auth_present = true;
+                       if (auth_protocol == AUTH_PROT_RKAP) {
+                               struct dhcpv6_auth_reconfigure *rkap = (void*)r->data;
+                               if (r->protocol != AUTH_PROT_RKAP || r->algorithm != AUTH_ALG_HMACMD5 || r->len != 28 || rkap->reconf_type != RKAP_TYPE_HMACMD5)
+                                       continue;
 
-                       memset(secretbytes, 0, sizeof(secretbytes));
-                       memcpy(secretbytes, reconf_key, sizeof(reconf_key));
+                               md5_ctx_t md5;
+                               uint8_t serverhash[16], secretbytes[64];
+                               uint32_t hash[4];
+                               memcpy(serverhash, rkap->key, sizeof(serverhash));
+                               memset(rkap->key, 0, sizeof(rkap->key));
 
-                       for (size_t i = 0; i < sizeof(secretbytes); ++i)
-                               secretbytes[i] ^= 0x36;
+                               memset(secretbytes, 0, sizeof(secretbytes));
+                               memcpy(secretbytes, reconf_key, sizeof(reconf_key));
 
-                       md5_begin(&md5);
-                       md5_hash(secretbytes, sizeof(secretbytes), &md5);
-                       md5_hash(buf, len, &md5);
-                       md5_end(hash, &md5);
+                               for (size_t i = 0; i < sizeof(secretbytes); ++i)
+                                       secretbytes[i] ^= 0x36;
 
-                       for (size_t i = 0; i < sizeof(secretbytes); ++i) {
-                               secretbytes[i] ^= 0x36;
-                               secretbytes[i] ^= 0x5c;
-                       }
+                               md5_begin(&md5);
+                               md5_hash(secretbytes, sizeof(secretbytes), &md5);
+                               md5_hash(buf, len, &md5);
+                               md5_end(hash, &md5);
+
+                               for (size_t i = 0; i < sizeof(secretbytes); ++i) {
+                                       secretbytes[i] ^= 0x36;
+                                       secretbytes[i] ^= 0x5c;
+                               }
+
+                               md5_begin(&md5);
+                               md5_hash(secretbytes, sizeof(secretbytes), &md5);
+                               md5_hash(hash, 16, &md5);
+                               md5_end(hash, &md5);
 
-                       md5_begin(&md5);
-                       md5_hash(secretbytes, sizeof(secretbytes), &md5);
-                       md5_hash(hash, 16, &md5);
-                       md5_end(hash, &md5);
+                               rcauth_ok = !memcmp(hash, serverhash, sizeof(hash));
+                       } else if (auth_protocol == AUTH_PROT_TOKEN) {
+                               if (r->protocol != AUTH_PROT_TOKEN || r->algorithm != AUTH_ALG_TOKEN || r->len < 12)
+                                       continue;
+
+                               uint16_t token_len = r->len - 11;
+                               if (config_dhcp->auth_token == NULL || strlen(config_dhcp->auth_token) != token_len)
+                                       continue;
 
-                       rcauth_ok = !memcmp(hash, serverhash, sizeof(hash));
+                               rcauth_ok = !memcmp(r->data, config_dhcp->auth_token, token_len);
+                       }
                } else if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
                        rcmsg = odata[0];
                } else if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)) {
@@ -1419,12 +1438,12 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                        else if (otype == DHCPV6_OPT_INFO_REFRESH && olen >= 4) {
                                refresh = ntohl_unaligned(odata);
                        } else if (otype == DHCPV6_OPT_AUTH) {
-                               if (olen == -4 + sizeof(struct dhcpv6_auth_reconfigure)) {
-                                       struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
-                                       if (r->protocol == 3 && r->algorithm == 1 &&
-                                                       r->reconf_type == 1)
-                                               memcpy(reconf_key, r->key, sizeof(r->key));
-                               }
+                                       struct dhcpv6_auth *r = (void*)&odata[-4];
+                                       if (auth_protocol == AUTH_PROT_RKAP) {
+                                               struct dhcpv6_auth_reconfigure *rkap = (void*)r->data;
+                                               if (r->protocol == AUTH_PROT_RKAP || r->algorithm == AUTH_ALG_HMACMD5 || r->len == 28 || rkap->reconf_type == RKAP_TYPE_KEY)
+                                                       memcpy(reconf_key, rkap->key, sizeof(rkap->key));
+                                       }
                        } else if (otype == DHCPV6_OPT_AFTR_NAME && olen > 3) {
                                size_t cur_len;
                                odhcp6c_get_state(STATE_AFTR_NAME, &cur_len);
@@ -1951,6 +1970,7 @@ int dhcpv6_promote_server_cand(void)
        odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
        odhcp6c_add_state(STATE_SERVER_ID, cand->duid, cand->duid_len);
        accept_reconfig = cand->wants_reconfigure;
+       memset(reconf_key, 0, sizeof(reconf_key));
 
        if (cand->ia_na_len) {
                odhcp6c_add_state(STATE_IA_NA, cand->ia_na, cand->ia_na_len);
index b117e8f22fee3cce6bf1bce12dcb851b9f1991fa..a3d9b547da6d39ab8820f63f5b908d4bb8f17b42 100644 (file)
@@ -279,13 +279,17 @@ struct dhcpv6_duid {
        uint8_t data[128];
 } _packed;
 
-struct dhcpv6_auth_reconfigure {
+struct dhcpv6_auth {
        uint16_t type;
        uint16_t len;
        uint8_t protocol;
        uint8_t algorithm;
        uint8_t rdm;
        uint64_t replay;
+       uint8_t data[];
+} _packed;
+
+struct dhcpv6_auth_reconfigure {
        uint8_t reconf_type;
        uint8_t key[16];
 } _packed;
@@ -414,6 +418,26 @@ enum odhcp6c_ia_mode {
        IA_MODE_FORCE,
 };
 
+enum odhcp6c_auth_protocol {
+       AUTH_PROT_NONE = -1,
+       /* RFC3118 */
+       AUTH_PROT_TOKEN = 0,
+       /* draft-ietf-dhc-rfc8415bis-12 */
+       AUTH_PROT_RKAP = 3,
+};
+
+enum odhcp6c_auth_algorithm {
+       /* RFC3118 */
+       AUTH_ALG_TOKEN = 0,
+       /* draft-ietf-dhc-rfc8415bis-12 */
+       AUTH_ALG_HMACMD5 = 1
+};
+
+enum odhcp6c_rkap_type {
+       /* draft-ietf-dhc-rfc8415bis-12 */
+       RKAP_TYPE_KEY = 1,
+       RKAP_TYPE_HMACMD5 = 2,
+};
 
 struct odhcp6c_entry {
        struct in6_addr router;
index 257322076f3df6299e6e42709c19abcd940dd763..7f53ca53187b6ab7d1b1c42f9b187b1353f8132c 100644 (file)
@@ -122,6 +122,8 @@ enum {
        RECONFIGURE_DHCP_ATTR_IRT_DEFAULT,
        RECONFIGURE_DHCP_ATTR_IRT_MIN,
        RECONFIGURE_DHCP_ATTR_RAND_FACTOR,
+       RECONFIGURE_DHCP_ATTR_AUTH_PROTO,
+       RECONFIGURE_DHCP_ATTR_AUTH_TOKEN,
        RECONFIGURE_DHCP_ATTR_MAX,
 };
 
@@ -166,6 +168,8 @@ static const struct blobmsg_policy reconfigure_dhcp_policy[RECONFIGURE_DHCP_ATTR
        [RECONFIGURE_DHCP_ATTR_IRT_DEFAULT] = { .name = "irt_default", .type = BLOBMSG_TYPE_INT32},
        [RECONFIGURE_DHCP_ATTR_IRT_MIN] = { .name = "irt_min", .type = BLOBMSG_TYPE_INT32},
        [RECONFIGURE_DHCP_ATTR_RAND_FACTOR] = { .name = "rand_factor", .type = BLOBMSG_TYPE_INT32},
+       [RECONFIGURE_DHCP_ATTR_AUTH_PROTO] = { .name = "auth_protocol", .type = BLOBMSG_TYPE_STRING},
+       [RECONFIGURE_DHCP_ATTR_AUTH_TOKEN] = { .name = "auth_token", .type = BLOBMSG_TYPE_STRING},
 };
 
 static struct ubus_method odhcp6c_object_methods[] = {
@@ -905,6 +909,26 @@ static int ubus_handle_reconfigure_dhcp(_unused struct ubus_context *ctx, _unuse
                valid_args = true;
        }
 
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_AUTH_PROTO])) {
+               string = blobmsg_get_string(cur);
+
+               if (string == NULL || !config_set_auth_protocol(string))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_AUTH_TOKEN])) {
+               string = blobmsg_get_string(cur);
+
+               if (string == NULL || !config_set_auth_token(string))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
        if (need_reinit)
                raise(SIGUSR2);