Wireless Daemon for Linux
 help / color / mirror / Atom feed
From: James Prestwood <prestwoj at gmail.com>
To: iwd at lists.01.org
Subject: [PATCH 03/12] dpp-util: add URI parsing
Date: Tue, 18 Jan 2022 13:25:03 -0800	[thread overview]
Message-ID: <20220118212512.2017977-3-prestwoj@gmail.com> (raw)
In-Reply-To: 20220118212512.2017977-1-prestwoj@gmail.com

[-- Attachment #1: Type: text/plain, Size: 6687 bytes --]

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 | 217 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/dpp-util.h |  13 +++
 2 files changed, 230 insertions(+)

diff --git a/src/dpp-util.c b/src/dpp-util.c
index 81a97f97..ed2fc038 100644
--- a/src/dpp-util.c
+++ b/src/dpp-util.c
@@ -865,3 +865,220 @@ 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)
+{
+	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_NEXT(pos, ',')) {
+		uint8_t channel;
+		uint8_t oper_class = strtol(pos, &end, 10);
+
+		if (!end || end == pos || (end && *end != '/'))
+			goto free_set;
+
+		pos = end + 1;
+		channel = strtol(pos, &end, 10);
+
+		/* Either another pair (,) or end of this token (;) */
+		if (!end || end == pos || (*end != ',' && *end != ';'))
+			goto free_set;
+
+		scan_freq_set_add(freqs, oci_to_frequency(oper_class, channel));
+	}
+
+	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, uint8_t *mac_out)
+{
+	uint8_t mac[6];
+	unsigned int i;
+
+	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, uint8_t *version_out)
+{
+	char *end;
+	uint8_t version;
+	size_t len = TOKEN_LEN(str, ';');
+
+	if (len != 1)
+		return -EINVAL;
+
+	version = strtol(str, &end, 10);
+
+	if (version != 1 && version != 2)
+		return -EINVAL;
+
+	*version_out = version;
+
+	return 0;
+}
+
+static struct l_ecc_point *dpp_parse_key(const char *str)
+{
+	_auto_(l_free) uint8_t *decoded = NULL;
+	size_t decoded_len;
+	size_t len = TOKEN_LEN(str, ';');
+
+	if (!len)
+		return NULL;
+
+	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 (strncmp(pos, "DPP:", 4))
+		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, ';')) {
+		switch (*pos) {
+		case 'C':
+			info->freqs = dpp_parse_class_and_channel(pos + 2);
+			if (!info->freqs)
+				goto free_info;
+			break;
+		case 'M':
+			ret = dpp_parse_mac(pos + 2, info->mac);
+			if (ret < 0)
+				goto free_info;
+			break;
+		case 'V':
+			ret = dpp_parse_version(pos + 2, &info->version);
+			if (ret < 0)
+				goto free_info;
+			break;
+		case 'K':
+			info->boot_public = dpp_parse_key(pos + 2);
+			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.31.1

                 reply	other threads:[~2022-01-18 21:25 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220118212512.2017977-3-prestwoj@gmail.com \
    --to=iwd@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox