[PATCH 1/8] tls: Implement l_tls_set_domain_mask
by Andrew Zaborowski
Allow user to set a mask that the a peer certificate's subject CN
has to match.
---
ell/ell.sym | 1 +
ell/tls-private.h | 1 +
ell/tls.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++
ell/tls.h | 2 +
4 files changed, 147 insertions(+)
diff --git a/ell/ell.sym b/ell/ell.sym
index 9bf0934..fe0ff1f 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -441,6 +441,7 @@ global:
l_tls_set_cacert;
l_tls_set_auth_data;
l_tls_set_version_range;
+ l_tls_set_domain_mask;
l_tls_alert_to_str;
l_tls_set_debug;
/* uintset */
diff --git a/ell/tls-private.h b/ell/tls-private.h
index c1de257..908c622 100644
--- a/ell/tls-private.h
+++ b/ell/tls-private.h
@@ -212,6 +212,7 @@ struct l_tls {
struct l_certchain *cert;
struct l_key *priv_key;
size_t priv_key_size;
+ char **subject_mask;
struct tls_cipher_suite **cipher_suite_pref_list;
diff --git a/ell/tls.c b/ell/tls.c
index b1fec9f..3fe2ff5 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -629,6 +629,112 @@ static const struct tls_hash_algorithm *tls_set_prf_hmac(struct l_tls *tls)
return NULL;
}
+static bool tls_domain_match_mask(const char *name, size_t name_len,
+ const char *mask, size_t mask_len)
+{
+ bool at_start = true;
+
+ while (1) {
+ const char *name_seg_end = memchr(name, '.', name_len);
+ const char *mask_seg_end = memchr(mask, '.', mask_len);
+ size_t name_seg_len = name_seg_end ?
+ (size_t) (name_seg_end - name) : name_len;
+ size_t mask_seg_len = mask_seg_end ?
+ (size_t) (mask_seg_end - mask) : mask_len;
+
+ if (mask_seg_len == 1 && mask[0] == '*') {
+ /*
+ * A * at the beginning of the mask matches any
+ * number of labels.
+ */
+ if (at_start && name_seg_end &&
+ tls_domain_match_mask(name_seg_end + 1,
+ name_len - name_seg_len - 1,
+ mask, mask_len))
+ return true;
+
+ goto ok_next;
+ }
+
+ if (name_seg_len != mask_seg_len ||
+ memcmp(name, mask, name_seg_len))
+ return false;
+
+ok_next:
+ /* If either string ends here both must end here */
+ if (!name_seg_end || !mask_seg_end)
+ return !name_seg_end && !mask_seg_end;
+
+ at_start = false;
+ name = name_seg_end + 1;
+ name_len -= name_seg_len + 1;
+ mask = mask_seg_end + 1;
+ mask_len -= mask_seg_len + 1;
+ }
+}
+
+static const struct asn1_oid dn_common_name_oid =
+ { 3, { 0x55, 0x04, 0x03 } };
+
+static bool tls_cert_domains_match_mask(struct l_cert *cert, char **mask)
+{
+ const uint8_t *dn, *end;
+ size_t dn_size;
+ const char *cn = NULL;
+ size_t cn_len;
+
+ /*
+ * Retrieve the Common Name from the Subject DN and check if it
+ * matches. TODO: possibly also look at SubjectAltName.
+ */
+
+ dn = l_cert_get_dn(cert, &dn_size);
+ if (unlikely(!dn))
+ return false;
+
+ end = dn + dn_size;
+ while (dn < end) {
+ const uint8_t *set, *seq, *oid, *name;
+ uint8_t tag;
+ size_t len, oid_len, name_len;
+
+ set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len);
+ if (unlikely(!set || tag != ASN1_ID_SET))
+ return false;
+
+ dn = set + len;
+
+ seq = asn1_der_find_elem(set, len, 0, &tag, &len);
+ if (unlikely(!seq || tag != ASN1_ID_SEQUENCE))
+ return false;
+
+ oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len);
+ if (unlikely(!oid || tag != ASN1_ID_OID))
+ return false;
+
+ name = asn1_der_find_elem(seq, len, 1, &tag, &name_len);
+ if (unlikely(!name || (tag != ASN1_ID_PRINTABLESTRING &&
+ tag != ASN1_ID_UTF8STRING &&
+ tag != ASN1_ID_IA5STRING)))
+ continue;
+
+ if (asn1_oid_eq(&dn_common_name_oid, oid_len, oid)) {
+ cn = (const char *) name;
+ cn_len = name_len;
+ break;
+ }
+ }
+
+ if (!cn)
+ return false;
+
+ for (; *mask; mask++)
+ if (tls_domain_match_mask(cn, cn_len, *mask, strlen(*mask)))
+ return true;
+
+ return false;
+}
+
#define SWITCH_ENUM_TO_STR(val) \
case (val): \
return L_STRINGIFY(val);
@@ -1793,6 +1899,18 @@ static void tls_handle_certificate(struct l_tls *tls,
goto done;
}
+ if (tls->subject_mask && !tls_cert_domains_match_mask(leaf,
+ tls->subject_mask)) {
+ char *mask = l_strjoinv(tls->subject_mask, '|');
+
+ TLS_DISCONNECT(TLS_ALERT_BAD_CERT, 0,
+ "Peer certificate's subject domain "
+ "doesn't match %s", mask);
+ l_free(mask);
+
+ goto done;
+ }
+
/* Save the end-entity certificate and free the chain */
der = l_cert_get_der_data(leaf, &der_len);
tls->peer_cert = l_cert_new_from_der(der, der_len);
@@ -2420,6 +2538,7 @@ LIB_EXPORT void l_tls_free(struct l_tls *tls)
l_tls_set_cacert(tls, NULL);
l_tls_set_auth_data(tls, NULL, NULL, NULL);
+ l_tls_set_domain_mask(tls, NULL);
tls_reset_handshake(tls);
tls_cleanup_handshake(tls);
@@ -2766,6 +2885,30 @@ LIB_EXPORT void l_tls_set_version_range(struct l_tls *tls,
max_version : TLS_MAX_VERSION;
}
+/**
+ * l_tls_set_domain_mask:
+ * @tls: TLS object being configured
+ * @mask: NULL-terminated array of domain masks
+ *
+ * Sets a mask for domain names contained in the peer certificate
+ * (eg. the subject Common Name) to be matched against. If none of the
+ * domains match the any mask, authentication will fail. At least one
+ * domain has to match at least one mask from the list.
+ *
+ * The masks are each split into segments at the dot characters and each
+ * segment must match the corresponding label of the domain name --
+ * a domain name is a sequence of labels joined by dots. An asterisk
+ * segment in the mask matches any label. An asterisk segment at the
+ * beginning of the mask matches one or more consecutive labels from
+ * the beginning of the domain string.
+ */
+LIB_EXPORT void l_tls_set_domain_mask(struct l_tls *tls, char **mask)
+{
+ l_strv_free(tls->subject_mask);
+
+ tls->subject_mask = l_strv_copy(mask);
+}
+
LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc)
{
switch (desc) {
diff --git a/ell/tls.h b/ell/tls.h
index b0db34f..a361c37 100644
--- a/ell/tls.h
+++ b/ell/tls.h
@@ -117,6 +117,8 @@ void l_tls_set_version_range(struct l_tls *tls,
enum l_tls_version min_version,
enum l_tls_version max_version);
+void l_tls_set_domain_mask(struct l_tls *tls, char **mask);
+
const char *l_tls_alert_to_str(enum l_tls_alert_desc desc);
enum l_checksum_type;
--
2.20.1
2 years, 8 months
[PATCH 1/2] dbus: Add l_dbus_object_get_data
by Andrew Zaborowski
This function can be used by DBus method implementations that receive
an object path as a parameter to directly look up the user_data by the
path and interface name.
---
ell/dbus-private.h | 3 +++
ell/dbus-service.c | 19 +++++++++++++++++++
ell/dbus.c | 13 +++++++++++++
ell/dbus.h | 2 ++
ell/ell.sym | 1 +
5 files changed, 38 insertions(+)
diff --git a/ell/dbus-private.h b/ell/dbus-private.h
index 799c743..be62691 100644
--- a/ell/dbus-private.h
+++ b/ell/dbus-private.h
@@ -219,6 +219,9 @@ bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree,
bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree,
const char *path,
const char *interface);
+void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree,
+ const char *path,
+ const char *interface);
void _dbus_object_tree_introspect(struct _dbus_object_tree *tree,
const char *path, struct l_string *buf);
diff --git a/ell/dbus-service.c b/ell/dbus-service.c
index 531b710..a1a7a74 100644
--- a/ell/dbus-service.c
+++ b/ell/dbus-service.c
@@ -1556,6 +1556,25 @@ bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree,
return true;
}
+void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree,
+ const char *path,
+ const char *interface)
+{
+ struct object_node *object;
+ struct interface_instance *instance;
+
+ object = l_hashmap_lookup(tree->objects, path);
+ if (!object)
+ return NULL;
+
+ instance = l_queue_find(object->instances, match_interface_instance,
+ (char *) interface);
+ if (!instance)
+ return NULL;
+
+ return instance->user_data;
+}
+
static bool match_object_manager_path(const void *a, const void *b)
{
const struct object_manager *manager = a;
diff --git a/ell/dbus.c b/ell/dbus.c
index b11225f..fb44b86 100644
--- a/ell/dbus.c
+++ b/ell/dbus.c
@@ -1585,6 +1585,19 @@ LIB_EXPORT bool l_dbus_object_remove_interface(struct l_dbus *dbus,
interface);
}
+LIB_EXPORT void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object,
+ const char *interface)
+{
+ if (unlikely(!dbus))
+ return false;
+
+ if (unlikely(!dbus->tree))
+ return false;
+
+ return _dbus_object_tree_get_interface_data(dbus->tree, object,
+ interface);
+}
+
LIB_EXPORT bool l_dbus_object_manager_enable(struct l_dbus *dbus)
{
if (unlikely(!dbus))
diff --git a/ell/dbus.h b/ell/dbus.h
index d3af5d7..212c60e 100644
--- a/ell/dbus.h
+++ b/ell/dbus.h
@@ -239,6 +239,8 @@ bool l_dbus_object_add_interface(struct l_dbus *dbus, const char *object,
const char *interface, void *user_data);
bool l_dbus_object_remove_interface(struct l_dbus *dbus, const char *object,
const char *interface);
+void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object,
+ const char *interface);
bool l_dbus_object_manager_enable(struct l_dbus *dbus);
diff --git a/ell/ell.sym b/ell/ell.sym
index 0d6422f..d54a874 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -210,6 +210,7 @@ global:
l_dbus_unregister_object;
l_dbus_object_add_interface;
l_dbus_object_remove_interface;
+ l_dbus_object_get_data;
l_dbus_object_manager_enable;
l_dbus_add_disconnect_watch;
l_dbus_add_service_watch;
--
2.20.1
2 years, 8 months
[PATCH 1/2] tls: Validate peer certificate's DNSNames against mask
by Andrew Zaborowski
Also return success in the domain_mask check if any of the DNSNames in
the peer certificate's subjectAltName extension matches any of the mask
strings supplied.
---
ell/tls.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 62 insertions(+), 8 deletions(-)
diff --git a/ell/tls.c b/ell/tls.c
index cbcdfa4..d0e2a66 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -673,26 +673,80 @@ ok_next:
}
}
+static const struct asn1_oid subject_alt_name_oid =
+ { 3, { 0x55, 0x1d, 0x11 } };
static const struct asn1_oid dn_common_name_oid =
{ 3, { 0x55, 0x04, 0x03 } };
+#define SAN_DNS_NAME_ID ASN1_CONTEXT_IMPLICIT(2)
+
static bool tls_cert_domains_match_mask(struct l_cert *cert, char **mask)
{
- const uint8_t *dn, *end;
- size_t dn_size;
+ const uint8_t *san, *dn, *end;
+ size_t san_len, dn_len;
+ uint8_t san_tag;
const char *cn = NULL;
size_t cn_len;
+ char **i;
+ bool dns_name_present = false;
+
+ /*
+ * Locate SubjectAltName (RFC5280 Section 4.2.1.6) and descend into
+ * the sole SEQUENCE element, check if any DNSName matches.
+ */
+ san = cert_get_extension(cert, &subject_alt_name_oid, NULL, &san_len);
+ if (san) {
+ san = asn1_der_find_elem(san, san_len, 0, &san_tag, &san_len);
+ if (unlikely(!san || san_tag != ASN1_ID_SEQUENCE))
+ return false;
+
+ end = san + san_len;
+ while (san < end) {
+ const uint8_t *value;
+ uint8_t tag;
+ size_t len;
+
+ value = asn1_der_find_elem(san, end - san,
+ SAN_DNS_NAME_ID,
+ &tag, &len);
+ if (!value)
+ break;
+
+ /* Type is implicitly IA5STRING */
+
+ for (i = mask; *i; i++)
+ if (tls_domain_match_mask((const char *) value,
+ len, *i, strlen(*i)))
+ return true;
+
+ san = value + len;
+ dns_name_present = true;
+ }
+ }
/*
* Retrieve the Common Name from the Subject DN and check if it
- * matches. TODO: possibly also look at SubjectAltName.
+ * matches.
+ *
+ * We look at the Common Name only if no DNSNames were present in
+ * the certificate, following Wi-Fi Alliance's Hotspot 2.0
+ * Specification v3.1 section 7.3.3.2 step 2:
+ * "Verify in the AAA server certificate that the domain name from
+ * the FQDN [...] is a suffix match of the domain name in at least
+ * one of the DNSName SubjectAltName extensions. If a SubjectAltName
+ * of type DNSName is not present, then the domain name from the
+ * FQDN shall be a suffix match to the CommonName portion of the
+ * SubjectName. If neither of these conditions holds, then
+ * verification fails."
*/
+ if (unlikely(dns_name_present))
+ return false;
- dn = l_cert_get_dn(cert, &dn_size);
+ dn = l_cert_get_dn(cert, &dn_len);
if (unlikely(!dn))
return false;
- end = dn + dn_size;
+ end = dn + dn_len;
while (dn < end) {
const uint8_t *set, *seq, *oid, *name;
uint8_t tag;
@@ -725,11 +779,11 @@ static bool tls_cert_domains_match_mask(struct l_cert *cert, char **mask)
}
}
- if (!cn)
+ if (unlikely(!cn))
return false;
- for (; *mask; mask++)
- if (tls_domain_match_mask(cn, cn_len, *mask, strlen(*mask)))
+ for (i = mask; *i; i++)
+ if (tls_domain_match_mask(cn, cn_len, *i, strlen(*i)))
return true;
return false;
--
2.20.1
2 years, 8 months
[PATCH 3/8] asn1-private: Handle Context-specific tag class
by Andrew Zaborowski
In asn1_der_find_elem(_by_path) add a way to properly handle the
optional elements in x509 certificates. Until now we were lucky we
never needed to handle the parts of certificates where some
OPTIONAL/DEFAULT element was absent (such as Version in
v1 certificates) but we would be misparsing the certificates in
those cases.
In the general case an asn1 parser will need the full syntax
definition to locate an element inside the structure. To avoid adding
a lot of .asn1 files like those contained in the kernel, and a parser
compiler, assume that OPTIONAL/DEFAULT elements use Context-specific
tags which is mostly the case in the x509 syntax (this is evens noted
in ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc). This way we
can handle Univeral tags that are always present and optional elements
that use Context-specific tags and get by with the same few #defines
we had regarding the x509 structure.
---
ell/asn1-private.h | 78 +++++++++++++++++++++++++++++++++++++++-------
ell/cert.c | 20 ++++++------
ell/pkcs5.c | 1 +
3 files changed, 77 insertions(+), 22 deletions(-)
diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 71a4ae6..d03f274 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -21,6 +21,7 @@
#define ASN1_ID(class, pc, tag) (((class) << 6) | ((pc) << 5) | (tag))
#define ASN1_CLASS_UNIVERSAL 0
+#define ASN1_CLASS_CONTEXT 2
#define ASN1_ID_SEQUENCE ASN1_ID(ASN1_CLASS_UNIVERSAL, 1, 0x10)
#define ASN1_ID_SET ASN1_ID(ASN1_CLASS_UNIVERSAL, 1, 0x11)
@@ -89,14 +90,24 @@ static inline void asn1_write_definite_length(uint8_t **buf, size_t len)
*(*buf)++ = len >> (n * 8);
}
-/* Return index'th element in a DER SEQUENCE */
+#define ASN1_CONTEXT_IMPLICIT(tag) (0x1000 | (tag))
+#define ASN1_CONTEXT_EXPLICIT(tag) (0x2000 | (tag))
+
+/*
+ * Return the tag, length and value of the @index'th
+ * non-context-specific-tagged element in a DER SEQUENCE or one who's
+ * ASN1_CONTEXT_IMPLICIT(tag) matches @index or the inner element of
+ * the one who's ASN1_CONTEXT_EXPLICIT(tag) matches @index.
+ */
static inline const uint8_t *asn1_der_find_elem(const uint8_t *buf,
size_t len_in, int index,
uint8_t *tag, size_t *len_out)
{
- int tlv_len;
+ int n = 0;
while (1) {
+ int tlv_len;
+
if (len_in < 2)
return NULL;
@@ -107,9 +118,36 @@ static inline const uint8_t *asn1_der_find_elem(const uint8_t *buf,
if (tlv_len < 0 || (size_t) tlv_len > len_in)
return NULL;
- if (index-- == 0) {
- *len_out = tlv_len;
- return buf;
+ if (*tag >> 6 != ASN1_CLASS_CONTEXT) {
+ if (n++ == index) {
+ *len_out = tlv_len;
+ return buf;
+ }
+ } else if ((*tag & 0x1f) == (index & 0xfff)) {
+ /* Context-specific tag */
+ if (index & 0x1000) { /* Implicit */
+ *len_out = tlv_len;
+ return buf;
+ } else if (index & 0x2000) { /* Explicit */
+ const uint8_t *outer = buf;
+ int inner_len;
+
+ if (!(*tag & 0x20)) /* Primitive */
+ return NULL;
+
+ if (unlikely(tlv_len < 2))
+ return NULL;
+
+ *tag = *buf++;
+
+ inner_len = asn1_parse_definite_length(
+ (void *) &buf, &len_in);
+ if (outer + tlv_len != buf + inner_len)
+ return NULL;
+
+ *len_out = inner_len;
+ return buf;
+ }
}
buf += tlv_len;
@@ -122,20 +160,36 @@ static inline const uint8_t *asn1_der_find_elem_by_path(const uint8_t *buf,
size_t len_in, uint8_t tag,
size_t *len_out, ...)
{
- uint8_t elem_tag;
- int pos;
+ int index;
va_list vl;
va_start(vl, len_out);
- pos = va_arg(vl, int);
+ index = va_arg(vl, int);
+
+ while (index != -1) {
+ uint8_t elem_tag;
+ uint8_t expect_tag;
+ int prev_index = index;
+
+ buf = asn1_der_find_elem(buf, len_in, index,
+ &elem_tag, &len_in);
+ if (!buf) {
+ va_end(vl);
+ return NULL;
+ }
- while (pos != -1) {
- buf = asn1_der_find_elem(buf, len_in, pos, &elem_tag, &len_in);
+ index = va_arg(vl, int);
- pos = va_arg(vl, int);
+ if (prev_index & 0x1000)
+ expect_tag = ASN1_ID(ASN1_CLASS_CONTEXT,
+ index != -1 ? 1 :
+ ((elem_tag >> 5) & 1),
+ prev_index & 0xfff);
+ else
+ expect_tag = (index == -1) ? tag : ASN1_ID_SEQUENCE;
- if (!buf || elem_tag != (pos == -1 ? tag : ASN1_ID_SEQUENCE)) {
+ if (elem_tag != expect_tag) {
va_end(vl);
return NULL;
}
diff --git a/ell/cert.c b/ell/cert.c
index d52bfd0..950e562 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -34,20 +34,20 @@
#define X509_CERTIFICATE_POS 0
#define X509_TBSCERTIFICATE_POS 0
-#define X509_TBSCERT_VERSION_POS 0
-#define X509_TBSCERT_SERIAL_POS 1
-#define X509_TBSCERT_SIGNATURE_POS 2
+#define X509_TBSCERT_VERSION_POS ASN1_CONTEXT_EXPLICIT(0)
+#define X509_TBSCERT_SERIAL_POS 0
+#define X509_TBSCERT_SIGNATURE_POS 1
#define X509_ALGORITHM_ID_ALGORITHM_POS 0
#define X509_ALGORITHM_ID_PARAMS_POS 1
-#define X509_TBSCERT_ISSUER_DN_POS 3
-#define X509_TBSCERT_VALIDITY_POS 4
-#define X509_TBSCERT_SUBJECT_DN_POS 5
-#define X509_TBSCERT_SUBJECT_KEY_POS 6
+#define X509_TBSCERT_ISSUER_DN_POS 2
+#define X509_TBSCERT_VALIDITY_POS 3
+#define X509_TBSCERT_SUBJECT_DN_POS 4
+#define X509_TBSCERT_SUBJECT_KEY_POS 5
#define X509_SUBJECT_KEY_ALGORITHM_POS 0
#define X509_SUBJECT_KEY_VALUE_POS 1
-#define X509_TBSCERT_ISSUER_UID_POS 7
-#define X509_TBSCERT_SUBJECT_UID_POS 8
-#define X509_TBSCERT_EXTENSIONS_POS 9
+#define X509_TBSCERT_ISSUER_UID_POS ASN1_CONTEXT_IMPLICIT(1)
+#define X509_TBSCERT_SUBJECT_UID_POS ASN1_CONTEXT_IMPLICIT(2)
+#define X509_TBSCERT_EXTENSIONS_POS ASN1_CONTEXT_EXPLICIT(3)
#define X509_SIGNATURE_ALGORITHM_POS 1
#define X509_SIGNATURE_VALUE_POS 2
diff --git a/ell/pkcs5.c b/ell/pkcs5.c
index dedfc7f..f225c71 100644
--- a/ell/pkcs5.c
+++ b/ell/pkcs5.c
@@ -32,6 +32,7 @@
#include "checksum.h"
#include "cipher.h"
+#include "util.h"
#include "asn1-private.h"
#include "pkcs5.h"
#include "pkcs5-private.h"
--
2.20.1
2 years, 8 months
[PATCH 1/4] tls: Escape characters in the peer_identity string
by Andrew Zaborowski
Escape the /, = and \ characters in the strings we take from the peer
certificate's Subject DN to build the peer_identity string passed to the
ell client's ready callback to enable it to be parsed unamiguously.
---
ell/tls.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/ell/tls.c b/ell/tls.c
index 7405098..37f5b3f 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -2021,6 +2021,25 @@ static const struct dn_element_info dn_elements[] = {
{}
};
+static void tls_str_escape_append(struct l_string *out, char *str, size_t len)
+{
+ while (len--) {
+ switch (*str) {
+ case '\\':
+ case '/':
+ case '=':
+ l_string_append_c(out, '\\');
+ l_string_append_c(out, *str);
+ break;
+ default:
+ l_string_append_c(out, *str);
+ break;
+ }
+
+ str++;
+ }
+}
+
static char *tls_get_peer_identity_str(struct l_cert *cert)
{
const uint8_t *dn, *end;
@@ -2072,7 +2091,7 @@ static char *tls_get_peer_identity_str(struct l_cert *cert)
l_string_append_c(id_str, '/');
l_string_append(id_str, info->str);
l_string_append_c(id_str, '=');
- l_string_append_fixed(id_str, (char *) name, name_len);
+ tls_str_escape_append(id_str, (char *) name, name_len);
}
return l_string_unwrap(id_str);
--
2.20.1
2 years, 8 months
[PATCH 1/4] tls: Pass full Subject DN contents to "ready" callback
by Andrew Zaborowski
Until now the ready callback would receive a displayable string as
peer_identity (if peer was authenticated) which was either the Common
Name or the Organization Name extracted from the subject DN in the
peer's certificate.
In order to implement functionality similat to wpa_supplicant's
substring_match= option for EAP-TLS, we need to look specifically at
Common Name although if we have the full contents of the Subject DN
that's even better as wpa_supplicant allows configurations to match
against any element in the Subject DN, so if we do that iwd and wpa_s
config files may be easier to convert in either direction.
We could also just pass the whole certificate struct to the callback but
then we'd need to also have utilities for parsing the DN so instead we
parse it in tls.c and convert it into the same string format that is
used by wpa_s, openssl, etc. Parsing that string in user code is easier
than parsing the original DER struct.
---
ell/asn1-private.h | 1 +
ell/tls.c | 76 ++++++++++++++++++++++++++++++++--------------
2 files changed, 55 insertions(+), 22 deletions(-)
diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 53bba4c..71a4ae6 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -30,6 +30,7 @@
#define ASN1_ID_OID ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x06)
#define ASN1_ID_UTF8STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c)
#define ASN1_ID_PRINTABLESTRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13)
+#define ASN1_ID_IA5STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16)
struct asn1_oid {
uint8_t asn1_len;
diff --git a/ell/tls.c b/ell/tls.c
index 58ccd6a..c3591a8 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -44,6 +44,7 @@
#include "asn1-private.h"
#include "strv.h"
#include "missing.h"
+#include "string.h"
bool tls10_prf(const void *secret, size_t secret_len,
const char *label,
@@ -1990,17 +1991,41 @@ static void tls_handle_certificate_verify(struct l_tls *tls,
TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC);
}
-static const struct asn1_oid dn_organization_name_oid =
- { 3, { 0x55, 0x04, 0x0a } };
-static const struct asn1_oid dn_common_name_oid =
- { 3, { 0x55, 0x04, 0x03 } };
+struct dn_element_info {
+ const char *str;
+ const struct asn1_oid oid;
+};
+
+static const struct dn_element_info dn_elements[] = {
+ { "CN", { 3, { 0x55, 0x04, 0x03 } } },
+ { "SN", { 3, { 0x55, 0x04, 0x04 } } },
+ { "serialNumber", { 3, { 0x55, 0x04, 0x05 } } },
+ { "C", { 3, { 0x55, 0x04, 0x06 } } },
+ { "ST", { 3, { 0x55, 0x04, 0x07 } } },
+ { "L", { 3, { 0x55, 0x04, 0x08 } } },
+ { "street", { 3, { 0x55, 0x04, 0x09 } } },
+ { "O", { 3, { 0x55, 0x04, 0x0a } } },
+ { "OU", { 3, { 0x55, 0x04, 0x0b } } },
+ { "title", { 3, { 0x55, 0x04, 0x0c } } },
+ { "telephoneNumber", { 3, { 0x55, 0x04, 0x14 } } },
+ { "givenName", { 3, { 0x55, 0x04, 0x2a } } },
+ { "initials", { 3, { 0x55, 0x04, 0x2b } } },
+ { "emailAddress", {
+ 9,
+ { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 }
+ } },
+ { "domainComponent", {
+ 10,
+ { 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19 }
+ } },
+ {}
+};
static char *tls_get_peer_identity_str(struct l_cert *cert)
{
const uint8_t *dn, *end;
size_t dn_size;
- const uint8_t *printable_str = NULL;
- size_t printable_str_len;
+ struct l_string *id_str;
if (!cert)
return NULL;
@@ -2009,45 +2034,52 @@ static char *tls_get_peer_identity_str(struct l_cert *cert)
if (!dn)
return NULL;
+ id_str = l_string_new(200);
+
end = dn + dn_size;
while (dn < end) {
const uint8_t *set, *seq, *oid, *name;
uint8_t tag;
size_t len, oid_len, name_len;
+ const struct dn_element_info *info;
set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len);
if (!set || tag != ASN1_ID_SET)
- return NULL;
+ goto error;
dn = set + len;
seq = asn1_der_find_elem(set, len, 0, &tag, &len);
if (!seq || tag != ASN1_ID_SEQUENCE)
- return NULL;
+ goto error;
oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len);
if (!oid || tag != ASN1_ID_OID)
- return NULL;
+ goto error;
name = asn1_der_find_elem(seq, len, 1, &tag, &name_len);
if (!oid || (tag != ASN1_ID_PRINTABLESTRING &&
- tag != ASN1_ID_UTF8STRING))
+ tag != ASN1_ID_UTF8STRING &&
+ tag != ASN1_ID_IA5STRING))
continue;
- /* organizationName takes priority, commonName is second */
- if (asn1_oid_eq(&dn_organization_name_oid, oid_len, oid) ||
- (!printable_str &&
- asn1_oid_eq(&dn_common_name_oid,
- oid_len, oid))) {
- printable_str = name;
- printable_str_len = name_len;
- }
+ for (info = dn_elements; info->str; info++)
+ if (asn1_oid_eq(&info->oid, oid_len, oid))
+ break;
+ if (!info->str)
+ continue;
+
+ l_string_append_c(id_str, '/');
+ l_string_append(id_str, info->str);
+ l_string_append_c(id_str, '=');
+ l_string_append_fixed(id_str, (char *) name, name_len);
}
- if (printable_str)
- return l_strndup((char *) printable_str, printable_str_len);
- else
- return NULL;
+ return l_string_unwrap(id_str);
+
+error:
+ l_string_free(id_str);
+ return NULL;
}
static void tls_finished(struct l_tls *tls)
--
2.20.1
2 years, 9 months
[PATCH] dhcp: fix T1 timeout crash
by James Prestwood
The timeout user data was being passed as the address to the
client object, not the client object itself.
---
ell/dhcp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ell/dhcp.c b/ell/dhcp.c
index ca81311..22d903e 100644
--- a/ell/dhcp.c
+++ b/ell/dhcp.c
@@ -985,7 +985,7 @@ static void dhcp_client_rx_message(const void *data, size_t len, void *userdata)
client->timeout_lease =
l_timeout_create_ms(dhcp_fuzz_secs(client->lease->t1),
dhcp_client_t1_expired,
- &client, NULL);
+ client, NULL);
break;
case DHCP_STATE_INIT_REBOOT:
--
2.17.1
2 years, 9 months
[PATCH] dhcp-transport: fix out-of-bounds access
by James Prestwood
If len was odd the iovec was getting accessed out of bounds. 'j' needed
to be decremented after the for loop. In addition, the iov_len was not
being used to access the last byte of iov_base.
---
ell/dhcp-transport.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/ell/dhcp-transport.c b/ell/dhcp-transport.c
index b43dfbd..56db9dd 100644
--- a/ell/dhcp-transport.c
+++ b/ell/dhcp-transport.c
@@ -78,9 +78,11 @@ uint16_t _dhcp_checksumv(const struct iovec *iov, size_t iov_cnt)
sum += check[i];
}
+ j--;
+
if (len & 0x01) {
const uint8_t *odd = iov[j].iov_base;
- sum += odd[len - 1];
+ sum += odd[iov[j].iov_len - 1];
}
while (sum >> 16)
--
2.21.0
2 years, 9 months
[PATCH 1/2] tls: Pass full Subject DN contents to "ready" callback
by Andrew Zaborowski
Until now the ready callback would receive a displayable string as
peer_identity (if peer was authenticated) which was either the Common
Name or the Organization Name extracted from the subject DN in the
peer's certificate.
In order to implement functionality similat to wpa_supplicant's
substring_match= option for EAP-TLS, we need to look specifically at
Common Name although if we have the full contents of the Subject DN
that's even better as wpa_supplicant allows configurations to match
against any element in the Subject DN, so if we do that iwd and wpa_s
config files may be easier to convert in either direction.
We could also just pass the whole certificate struct to the callback but
then we'd need to also have utilities for parsing the DN so instead we
parse it in tls.c and convert it into the same string format that is
used by wpa_s, openssl, etc. Parsing that string in user code is easier
than parsing the original DER struct.
---
ell/asn1-private.h | 1 +
ell/tls.c | 76 ++++++++++++++++++++++++++++++++--------------
2 files changed, 55 insertions(+), 22 deletions(-)
diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 53bba4c..71a4ae6 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -30,6 +30,7 @@
#define ASN1_ID_OID ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x06)
#define ASN1_ID_UTF8STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c)
#define ASN1_ID_PRINTABLESTRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13)
+#define ASN1_ID_IA5STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16)
struct asn1_oid {
uint8_t asn1_len;
diff --git a/ell/tls.c b/ell/tls.c
index 89db73c..ba9f613 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -44,6 +44,7 @@
#include "asn1-private.h"
#include "strv.h"
#include "missing.h"
+#include "string.h"
bool tls10_prf(const void *secret, size_t secret_len,
const char *label,
@@ -1990,17 +1991,41 @@ static void tls_handle_certificate_verify(struct l_tls *tls,
TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC);
}
-static const struct asn1_oid dn_organization_name_oid =
- { 3, { 0x55, 0x04, 0x0a } };
-static const struct asn1_oid dn_common_name_oid =
- { 3, { 0x55, 0x04, 0x03 } };
+struct dn_element_info {
+ const char *str;
+ const struct asn1_oid oid;
+};
+
+static const struct dn_element_info dn_elements[] = {
+ { "CN", { 3, { 0x55, 0x04, 0x03 } } },
+ { "SN", { 3, { 0x55, 0x04, 0x04 } } },
+ { "serialNumber", { 3, { 0x55, 0x04, 0x05 } } },
+ { "C", { 3, { 0x55, 0x04, 0x06 } } },
+ { "ST", { 3, { 0x55, 0x04, 0x07 } } },
+ { "L", { 3, { 0x55, 0x04, 0x08 } } },
+ { "street", { 3, { 0x55, 0x04, 0x09 } } },
+ { "O", { 3, { 0x55, 0x04, 0x0a } } },
+ { "OU", { 3, { 0x55, 0x04, 0x0b } } },
+ { "title", { 3, { 0x55, 0x04, 0x0c } } },
+ { "telephoneNumber", { 3, { 0x55, 0x04, 0x14 } } },
+ { "givenName", { 3, { 0x55, 0x04, 0x2a } } },
+ { "initials", { 3, { 0x55, 0x04, 0x2b } } },
+ { "emailAddress", {
+ 9,
+ { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 }
+ } },
+ { "domainComponent", {
+ 10,
+ { 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19 }
+ } },
+ {}
+};
static char *tls_get_peer_identity_str(struct l_cert *cert)
{
const uint8_t *dn, *end;
size_t dn_size;
- const uint8_t *printable_str = NULL;
- size_t printable_str_len;
+ struct l_string *id_str;
if (!cert)
return NULL;
@@ -2009,45 +2034,52 @@ static char *tls_get_peer_identity_str(struct l_cert *cert)
if (!dn)
return NULL;
+ id_str = l_string_new(200);
+
end = dn + dn_size;
while (dn < end) {
const uint8_t *set, *seq, *oid, *name;
uint8_t tag;
size_t len, oid_len, name_len;
+ const struct dn_element_info *info;
set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len);
if (!set || tag != ASN1_ID_SET)
- return NULL;
+ goto error;
dn = set + len;
seq = asn1_der_find_elem(set, len, 0, &tag, &len);
if (!seq || tag != ASN1_ID_SEQUENCE)
- return NULL;
+ goto error;
oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len);
if (!oid || tag != ASN1_ID_OID)
- return NULL;
+ goto error;
name = asn1_der_find_elem(seq, len, 1, &tag, &name_len);
if (!oid || (tag != ASN1_ID_PRINTABLESTRING &&
- tag != ASN1_ID_UTF8STRING))
+ tag != ASN1_ID_UTF8STRING &&
+ tag != ASN1_ID_IA5STRING))
continue;
- /* organizationName takes priority, commonName is second */
- if (asn1_oid_eq(&dn_organization_name_oid, oid_len, oid) ||
- (!printable_str &&
- asn1_oid_eq(&dn_common_name_oid,
- oid_len, oid))) {
- printable_str = name;
- printable_str_len = name_len;
- }
+ for (info = dn_elements; info->str; info++)
+ if (asn1_oid_eq(&info->oid, oid_len, oid))
+ break;
+ if (!info->str)
+ continue;
+
+ l_string_append_c(id_str, '/');
+ l_string_append(id_str, info->str);
+ l_string_append_c(id_str, '=');
+ l_string_append_fixed(id_str, (char *) name, name_len);
}
- if (printable_str)
- return l_strndup((char *) printable_str, printable_str_len);
- else
- return NULL;
+ return l_string_unwrap(id_str);
+
+error:
+ l_string_free(id_str);
+ return NULL;
}
static void tls_finished(struct l_tls *tls)
--
2.20.1
2 years, 9 months
[PATCH] dhcp: Decrease re-try delay after initial discovery message
by Tim Kourt
Some of the routers require ‘setup’ time between the moment when
connection is established and before they are ready to respond to
the DHCP discovery messages. This used to cause the DHCP clients
to wait a full delay of +-4 seconds for the next attempt to
rediscover DHCP servers. To address this issue, the time delay
between the initial discovery message and its retransmission is
shorten to milliseconds to give enough time to the routers to get
ready and minimize the delay for the clients.
---
ell/dhcp.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/ell/dhcp.c b/ell/dhcp.c
index 57d5ae2..ca81311 100644
--- a/ell/dhcp.c
+++ b/ell/dhcp.c
@@ -485,6 +485,23 @@ static uint64_t dhcp_fuzz_secs(uint32_t secs)
return ms;
}
+/*
+ * Takes a time in milliseconds and produces a fuzzed value that can be directly
+ * used by l_timeout_modify_ms. The fluctuation of the random noise added is
+ * from -63 to 63 milliseconds.
+ */
+static uint64_t dhcp_fuzz_msecs(uint64_t ms)
+{
+ uint32_t r = l_getrandom_uint32();
+
+ if (r & 0x80000000)
+ ms += r & 0x3f;
+ else
+ ms -= r & 0x3f;
+
+ return ms;
+}
+
static uint32_t dhcp_rebind_renew_retry_time(uint64_t start_t, uint32_t expiry)
{
uint64_t now = l_time_now();
@@ -1191,7 +1208,7 @@ LIB_EXPORT bool l_dhcp_client_start(struct l_dhcp_client *client)
if (err < 0)
return false;
- client->timeout_resend = l_timeout_create_ms(dhcp_fuzz_secs(4),
+ client->timeout_resend = l_timeout_create_ms(dhcp_fuzz_msecs(600),
dhcp_client_timeout_resend,
client, NULL);
CLIENT_ENTER_STATE(DHCP_STATE_SELECTING);
--
2.13.6
2 years, 9 months