Parses K (key), M (mac), C (class/channels), and V (version) tokens
into a new structure dpp_uri_info. H/I are not parsed since there
currently isn't any use for them.
---
src/dpp-util.c | 250 +++++++++++++++++++++++++++++++++++++++++++++++++
src/dpp-util.h | 13 +++
2 files changed, 263 insertions(+)
v6
* Set errno to 0 prior to calling strtoul
* check for version 1 or 2 as ascii rather than using strtoul
diff --git a/src/dpp-util.c b/src/dpp-util.c
index 5199d0ae..cd627e2a 100644
--- a/src/dpp-util.c
+++ b/src/dpp-util.c
@@ -866,3 +866,253 @@ struct l_ecc_point *dpp_point_from_asn1(const uint8_t *asn1, size_t
len)
return l_ecc_point_from_data(curve, key_data[1],
key_data + 2, elen - 2);
}
+
+/*
+ * Advances 'p' to the next character 'sep' plus one. strchr can be
trusted to
+ * find the next character, but we do need to check that the next character + 1
+ * isn't the NULL terminator, i.e. that data actually exists past this point.
+ */
+#define TOKEN_NEXT(p, sep) \
+({ \
+ const char *_next = strchr((p), (sep)); \
+ if (_next) { \
+ if (*(_next + 1) == '\0') \
+ _next = NULL; \
+ else \
+ _next++; \
+ } \
+ _next; \
+})
+
+/*
+ * Finds the length of the current token (characters until next 'sep'). If no
+ * 'sep' is found zero is returned.
+ */
+#define TOKEN_LEN(p, sep) \
+({ \
+ const char *_next = strchr((p), (sep)); \
+ if (!_next) \
+ _next = (p); \
+ (_next - (p)); \
+})
+
+/*
+ * Ensures 'p' points to something resembling a single character followed by
+ * ':' followed by at least one non-null byte of data. This allows the parse
+ * loop to safely advance the pointer to each tokens data (pos + 2)
+ */
+#define TOKEN_OK(p) \
+ ((p) && (p)[0] != '\0' && (p)[1] == ':' &&
(p)[2] != '\0') \
+
+static struct scan_freq_set *dpp_parse_class_and_channel(const char *token,
+ unsigned int len)
+{
+ const char *pos = token;
+ char *end;
+ struct scan_freq_set *freqs = scan_freq_set_new();
+
+ /* Checking for <operclass>/<channel>,<operclass>/<channel>,...
*/
+ for (; pos && pos < token + len; pos = TOKEN_NEXT(pos, ',')) {
+ unsigned long r;
+ uint8_t channel;
+ uint8_t oper_class;
+ uint32_t freq;
+
+ /* strtoul accepts minus and plus signs before value */
+ if (*pos == '-' || *pos == '+')
+ goto free_set;
+
+ /* to check uint8_t overflow */
+ errno = 0;
+ r = oper_class = strtoul(pos, &end, 10);
+
+ if (errno == ERANGE || errno == EINVAL)
+ goto free_set;
+ /*
+ * Did strtoul not advance pointer, not reach the next
+ * token, or overflow?
+ */
+ if (end == pos || *end != '/' || r != oper_class)
+ goto free_set;
+
+ pos = end + 1;
+
+ if (*pos == '-' || *pos == '+')
+ goto free_set;
+
+ errno = 0;
+ r = channel = strtoul(pos, &end, 10);
+
+ if (errno == ERANGE || errno == EINVAL)
+ goto free_set;
+ /*
+ * Same verification as above, but also checks either for
+ * another pair (,) or end of this token (;)
+ */
+ if (end == pos || (*end != ',' && *end != ';') || r !=
channel)
+ goto free_set;
+
+ freq = oci_to_frequency(oper_class, channel);
+ if (!freq)
+ goto free_set;
+
+ scan_freq_set_add(freqs, freq);
+ }
+
+ if (token + len != end)
+ goto free_set;
+
+ if (scan_freq_set_isempty(freqs)) {
+free_set:
+ scan_freq_set_free(freqs);
+ return NULL;
+ }
+
+ return freqs;
+}
+
+static int dpp_parse_mac(const char *str, unsigned int len, uint8_t *mac_out)
+{
+ uint8_t mac[6];
+ unsigned int i;
+
+ if (len != 12)
+ return -EINVAL;
+
+ for (i = 0; i < 12; i += 2) {
+ if (!l_ascii_isxdigit(str[i]))
+ return -EINVAL;
+
+ if (!l_ascii_isxdigit(str[i + 1]))
+ return -EINVAL;
+ }
+
+ if (sscanf(str, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ &mac[0], &mac[1], &mac[2],
+ &mac[3], &mac[4], &mac[5]) != 6)
+ return -EINVAL;
+
+ if (!util_is_valid_sta_address(mac))
+ return -EINVAL;
+
+ memcpy(mac_out, mac, 6);
+
+ return 0;
+}
+
+static int dpp_parse_version(const char *str, unsigned int len,
+ uint8_t *version_out)
+{
+ if (len != 1)
+ return -EINVAL;
+
+ if (str[0] != '1' && str[0] != '2')
+ return -EINVAL;
+
+ *version_out = str[0] - '0';
+
+ return 0;
+}
+
+static struct l_ecc_point *dpp_parse_key(const char *str, unsigned int len)
+{
+ _auto_(l_free) uint8_t *decoded = NULL;
+ size_t decoded_len;
+
+ decoded = l_base64_decode(str, len, &decoded_len);
+ if (!decoded)
+ return NULL;
+
+ return dpp_point_from_asn1(decoded, decoded_len);
+}
+
+/*
+ * Parse a bootstrapping URI. This parses the tokens defined in the Easy Connect
+ * spec, and verifies they are the correct syntax. Some values have extra
+ * verification:
+ * - The bootstrapping key is base64 decoded and converted to an l_ecc_point
+ * - The operating class and channels are checked against the OCI table.
+ * - The version is checked to be either 1 or 2, as defined by the spec.
+ * - The MAC is verified to be a valid station address.
+ */
+struct dpp_uri_info *dpp_parse_uri(const char *uri)
+{
+ struct dpp_uri_info *info;
+ const char *pos = uri;
+ const char *end = uri + strlen(uri) - 1;
+ int ret = 0;
+
+ if (!l_str_has_prefix(pos, "DPP:"))
+ return NULL;
+
+ info = l_new(struct dpp_uri_info, 1);
+
+ pos += 4;
+
+ /* EasyConnect 5.2.1 - Bootstrapping information format */
+ for (; TOKEN_OK(pos); pos = TOKEN_NEXT(pos, ';')) {
+ unsigned int len = TOKEN_LEN(pos + 2, ';');
+
+ if (!len)
+ goto free_info;
+
+ switch (*pos) {
+ case 'C':
+ info->freqs = dpp_parse_class_and_channel(pos + 2, len);
+ if (!info->freqs)
+ goto free_info;
+ break;
+ case 'M':
+ ret = dpp_parse_mac(pos + 2, len, info->mac);
+ if (ret < 0)
+ goto free_info;
+ break;
+ case 'V':
+ ret = dpp_parse_version(pos + 2, len, &info->version);
+ if (ret < 0)
+ goto free_info;
+ break;
+ case 'K':
+ info->boot_public = dpp_parse_key(pos + 2, len);
+ if (!info->boot_public)
+ goto free_info;
+ break;
+ case 'H':
+ case 'I':
+ break;
+ default:
+ goto free_info;
+ }
+ }
+
+ /* Extra data found after last token */
+ if (pos != end)
+ goto free_info;
+
+ /* The public bootstrapping key is the only required token */
+ if (!info->boot_public)
+ goto free_info;
+
+ return info;
+
+free_info:
+ dpp_free_uri_info(info);
+ return NULL;
+}
+
+void dpp_free_uri_info(struct dpp_uri_info *info)
+{
+ if (info->freqs)
+ scan_freq_set_free(info->freqs);
+
+ if (info->boot_public)
+ l_ecc_point_free(info->boot_public);
+
+ if (info->information)
+ l_free(info->information);
+
+ if (info->host)
+ l_free(info->host);
+
+ l_free(info);
+}
diff --git a/src/dpp-util.h b/src/dpp-util.h
index 82535ff8..a3ddd452 100644
--- a/src/dpp-util.h
+++ b/src/dpp-util.h
@@ -22,6 +22,16 @@
struct l_ecc_point;
struct l_ecc_scalar;
enum ie_rsn_akm_suite;
+struct scan_freq_set;
+
+struct dpp_uri_info {
+ struct scan_freq_set *freqs;
+ struct l_ecc_point *boot_public;
+ uint8_t mac[6];
+ char *information;
+ uint8_t version;
+ char *host;
+};
enum dpp_frame_type {
DPP_FRAME_AUTHENTICATION_REQUEST = 0,
@@ -168,3 +178,6 @@ bool dpp_derive_ke(const uint8_t *i_nonce, const uint8_t *r_nonce,
uint8_t *dpp_point_to_asn1(const struct l_ecc_point *p, size_t *len_out);
struct l_ecc_point *dpp_point_from_asn1(const uint8_t *asn1, size_t len);
+
+struct dpp_uri_info *dpp_parse_uri(const char *uri);
+void dpp_free_uri_info(struct dpp_uri_info *info);
--
2.34.1