[PATCH] dhcp-util: Fix warning on signed pointer systems
by Brian Gix
Some systems treat pointers as signed values, causing the following
error:
ell/dhcp-util.c: In function ‘_dhcp_message_builder_append’:
ell/dhcp-util.c:55:49: error: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Werror=sign-compare]
55 | if ((builder)->pos - (builder)->start + (next) > (builder)->max) \
| ^
ell/dhcp-util.c:65:3: note: in expansion of macro ‘LEN_CHECK’
65 | LEN_CHECK(builder, 1);
| ^~~~~~~~~
---
ell/dhcp-util.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/ell/dhcp-util.c b/ell/dhcp-util.c
index 7809e52..feb1168 100644
--- a/ell/dhcp-util.c
+++ b/ell/dhcp-util.c
@@ -52,7 +52,8 @@ static void dhcp_init_header(struct dhcp_message *message, uint8_t type)
}
#define LEN_CHECK(builder, next) \
- if ((builder)->pos - (builder)->start + (next) > (builder)->max) \
+ if ((unsigned)((builder)->pos - (builder)->start + (next)) > \
+ (builder)->max) \
return false;
bool _dhcp_message_builder_append(struct dhcp_message_builder *builder,
--
2.25.4
1 month, 1 week
[PATCH] main: Add handler for SIGSEGV signal
by Inga Stotland
When an ell main loop based process is spun off as a child
and the parent process dies due to a segfault, the child process
is still left running and needs to detect the segfault condition.
To address this need, add watch and signal handler for SIGSEGV.
---
ell/main.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/ell/main.c b/ell/main.c
index a479b74..712cfb7 100644
--- a/ell/main.c
+++ b/ell/main.c
@@ -619,6 +619,14 @@ static void sigterm_handler(void *user_data)
data->callback(SIGTERM, data->user_data);
}
+static void sigsegv_handler(void *user_data)
+{
+ struct signal_data *data = user_data;
+
+ if (data->callback)
+ data->callback(SIGSEGV, data->user_data);
+}
+
/**
* l_main_run_with_signal:
*
@@ -633,6 +641,7 @@ LIB_EXPORT int l_main_run_with_signal(l_main_signal_cb_t callback,
struct signal_data *data;
struct l_signal *sigint;
struct l_signal *sigterm;
+ struct l_signal *sigsegv;
int result;
data = l_new(struct signal_data, 1);
@@ -642,11 +651,13 @@ LIB_EXPORT int l_main_run_with_signal(l_main_signal_cb_t callback,
sigint = l_signal_create(SIGINT, sigint_handler, data, NULL);
sigterm = l_signal_create(SIGTERM, sigterm_handler, data, NULL);
+ sigsegv = l_signal_create(SIGSEGV, sigsegv_handler, data, NULL);
result = l_main_run();
l_signal_remove(sigint);
l_signal_remove(sigterm);
+ l_signal_remove(sigsegv);
l_free(data);
--
2.26.2
1 month, 1 week
[PATCH] dhcp-util: fix GCC 10 warning
by James Prestwood
LEN_CHECK was being fed a hardcoded '1' which was being
treated as an int. This led GCC 10.2.1 to emit a warning about
comparison between signed/unsigned integers because the
builder->max member is unsigned:
ell/dhcp-util.c: In function ‘_dhcp_message_builder_append’:
ell/dhcp-util.c:55:49: error: comparison of integer expressions of different
signedness: ‘int’ and ‘unsigned int’ [-Werror=sign-compare]
55 | if ((builder)->pos - (builder)->start + (next) > (builder)->max) \
| ^
ell/dhcp-util.c:65:3: note: in expansion of macro ‘LEN_CHECK’
65 | LEN_CHECK(builder, 1);
| ^~~~~~~~~
cc1: all warnings being treated as errors
To fix this explicitly use '1U' so the compiler treats it
as unsigned.
---
ell/dhcp-util.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ell/dhcp-util.c b/ell/dhcp-util.c
index 7809e52..af09856 100644
--- a/ell/dhcp-util.c
+++ b/ell/dhcp-util.c
@@ -62,7 +62,7 @@ bool _dhcp_message_builder_append(struct dhcp_message_builder *builder,
switch (code) {
case DHCP_OPTION_PAD:
case DHCP_OPTION_END:
- LEN_CHECK(builder, 1);
+ LEN_CHECK(builder, 1U);
builder->pos[0] = code;
builder->pos += 1;
--
2.26.2
1 month, 1 week
[PATCH v2] cert: Fix warning of possible uninitialized var
by Brian Gix
ell/cert-crypto.c: In function ‘cert_pkcs12_pbkdf’:
ell/cert-crypto.c:244:3: error: ‘bmpstring’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
244 | explicit_bzero(bmpstring, passwd_len);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---
ell/cert-crypto.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/ell/cert-crypto.c b/ell/cert-crypto.c
index 6eb4e14..838d5b5 100644
--- a/ell/cert-crypto.c
+++ b/ell/cert-crypto.c
@@ -198,7 +198,8 @@ uint8_t *cert_pkcs12_pbkdf(const char *password,
/* Documented as v(ceiling(s/v)), usually will just equal v */
unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1);
/* Documented as p(ceiling(s/p)), usually will just equal v */
- unsigned int p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1);
+ unsigned int p_len = password ?
+ (passwd_len + hash->v - 1) & ~(hash->v - 1) : 0;
uint8_t di[hash->v + s_len + p_len];
uint8_t *ptr;
unsigned int j;
@@ -235,10 +236,11 @@ uint8_t *cert_pkcs12_pbkdf(const char *password,
ptr += s_len + salt_len - j;
}
- for (j = passwd_len; j < p_len; j += passwd_len, ptr += passwd_len)
- memcpy(ptr, bmpstring, passwd_len);
-
if (p_len) {
+ for (j = passwd_len; j < p_len;
+ j += passwd_len, ptr += passwd_len)
+ memcpy(ptr, bmpstring, passwd_len);
+
memcpy(ptr, bmpstring, p_len + passwd_len - j);
explicit_bzero(bmpstring, passwd_len);
--
2.25.4
1 month, 1 week
[PATCH v3] cert: Fix warning of possible uninitialized var
by Brian Gix
ell/cert-crypto.c: In function ‘cert_pkcs12_pbkdf’:
ell/cert-crypto.c:244:3: error: ‘bmpstring’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
244 | explicit_bzero(bmpstring, passwd_len);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---
ell/cert-crypto.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ell/cert-crypto.c b/ell/cert-crypto.c
index 6eb4e14..d07fabb 100644
--- a/ell/cert-crypto.c
+++ b/ell/cert-crypto.c
@@ -194,7 +194,7 @@ uint8_t *cert_pkcs12_pbkdf(const char *password,
{
/* All lengths in bytes instead of bits */
size_t passwd_len = password ? 2 * strlen(password) + 2 : 0;
- uint8_t *bmpstring;
+ uint8_t *uninitialized_var(bmpstring);
/* Documented as v(ceiling(s/v)), usually will just equal v */
unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1);
/* Documented as p(ceiling(s/p)), usually will just equal v */
--
2.25.4
1 month, 1 week
[PATCH] cert: Fix warning of possible uninitialized var
by Brian Gix
ell/cert-crypto.c: In function ‘cert_pkcs12_pbkdf’:
ell/cert-crypto.c:244:3: error: ‘bmpstring’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
244 | explicit_bzero(bmpstring, passwd_len);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---
ell/cert-crypto.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ell/cert-crypto.c b/ell/cert-crypto.c
index 6eb4e14..b3d3000 100644
--- a/ell/cert-crypto.c
+++ b/ell/cert-crypto.c
@@ -194,7 +194,7 @@ uint8_t *cert_pkcs12_pbkdf(const char *password,
{
/* All lengths in bytes instead of bits */
size_t passwd_len = password ? 2 * strlen(password) + 2 : 0;
- uint8_t *bmpstring;
+ uint8_t *bmpstring = NULL;
/* Documented as v(ceiling(s/v)), usually will just equal v */
unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1);
/* Documented as p(ceiling(s/p)), usually will just equal v */
--
2.25.4
1 month, 1 week
[PATCH 1/3] buf: Add a generic extensible buffer struct
by Andrew Zaborowski
Add a generic buffer class that can extend to the left and to the right
as data is appended or prepended. Nested buffers can also be added.
Building the complicated structures of nested genl attributes and also
various structures like 802.11 IEs passed in the contents of those
attributes is the motivation for this.
l_buf_new() and l_buf_append_new() take the amount of buffer space and
the number of nested buffers to pre-allocate but the user can go over
these values and the buffers will grow. While the code looks a little
complicated most operations are actually simple when the buffers don't
need to grow.
I considered these few features, all of which would be nice to have
for l_genl_msg building for example, but, some of which are mutually
exclusive without blowing up the size of struct l_buf, the complexity
or the number of mallocs:
1. nested l_bufs can be added *at the head*, in addition to at the
tail, of the parent l_buf. (similar to how static data can be
prepended or appended to the current l_buf)
2. multiple nested buffers of one parent can exist at the same time
and be written into or have their own children added in random
order.
3. l_buf contains enough information to detect when the initial
@nested_cnt iovecs are exhausted, in l_buf_append_new().
4. on top of 3. the iov array automatically extends when the initial
@nested_cnt iovecs are exhausted, in l_buf_append_new(), rather
than return NULL.
When the iov array extends parent/children nested l_bufs don't
become invalid.
5. no extra "finalize" call is required after all data is written
to all nested buffers and no tree structure needs to be flattened,
the iov array is ready to use in a writev() at any time.
6. on top of 5. no l_buf_free() is needed for nested buffers.
The number of l_buf_free calls matches the number of l_buf_new
calls only.
This version sacrifices 1. and 2. for 3., 4., 5. and 6.
---
Makefile.am | 6 +-
ell/buf.c | 192 ++++++++++++++++++++++++++++++++++++++
ell/buf.h | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++
ell/ell.h | 1 +
ell/ell.sym | 5 +
5 files changed, 460 insertions(+), 2 deletions(-)
create mode 100644 ell/buf.c
create mode 100644 ell/buf.h
diff --git a/Makefile.am b/Makefile.am
index 2f9a4ce..93bbbf2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -58,7 +58,8 @@ pkginclude_HEADERS = ell/ell.h \
ell/gpio.h \
ell/path.h \
ell/icmp6.h \
- ell/acd.h
+ ell/acd.h \
+ ell/buf.h
lib_LTLIBRARIES = ell/libell.la
@@ -141,7 +142,8 @@ ell_libell_la_SOURCES = $(linux_headers) \
ell/path.c \
ell/icmp6.c \
ell/icmp6-private.h \
- ell/acd.c
+ ell/acd.c \
+ ell/buf.c
ell_libell_la_LDFLAGS = -Wl,--no-undefined \
-Wl,--version-script=$(top_srcdir)/ell/ell.sym \
diff --git a/ell/buf.c b/ell/buf.c
new file mode 100644
index 0000000..8900656
--- /dev/null
+++ b/ell/buf.c
@@ -0,0 +1,192 @@
+/*
+ *
+ * Embedded Linux library
+ *
+ * Copyright (C) 2021 Intel Corporation. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include "util.h"
+#include "private.h"
+#include "buf.h"
+
+LIB_EXPORT struct l_buf *l_buf_new(size_t headroom, size_t tailroom,
+ unsigned int nested_cnt)
+{
+ struct l_buf *buf;
+ unsigned int iov_cnt = 1 + nested_cnt;
+ struct iovec *alloced;
+
+ buf = l_malloc(sizeof(struct l_buf) +
+ iov_cnt * 2 * sizeof(struct iovec) +
+ headroom + tailroom);
+ buf->parent = NULL;
+ buf->child = NULL;
+ /* Point @used to the first byte after the end of the struct */
+ buf->used = buf->iovecs;
+ buf->used_cnt = 1;
+ /* Point @alloced to the first byte after the end of @used */
+ alloced = buf->used + iov_cnt;
+ buf->alloced_cnt = iov_cnt;
+ /* Point @alloced[0].iov_base to first byte after the end of @alloced */
+ alloced->iov_base = alloced + iov_cnt;
+ alloced->iov_len = headroom + tailroom;
+
+ buf->used->iov_base = alloced->iov_base + headroom;
+ buf->used->iov_len = 0;
+
+ return buf;
+}
+
+LIB_EXPORT bool l_buf_append_init(struct l_buf *buf, struct l_buf *parent,
+ size_t headroom, size_t tailroom)
+{
+ struct l_buf *base;
+
+ if (unlikely(!buf || !parent))
+ return false;
+
+ for (base = parent; base->parent; base = base->parent);
+
+ if (unlikely(base->used_cnt == base->alloced_cnt)) {
+ struct iovec *old_used = base->used;
+ struct l_buf *i;
+
+ /* Allocate a bigger buffer for the used+allocated array */
+ base->alloced_cnt *= 2;
+
+ if (old_used == base->iovecs) {
+ struct iovec *old_alloced = old_used + base->used_cnt;
+
+ /* Assign the now uneeded space to the first iovec */
+ if (old_alloced->iov_base ==
+ old_alloced + base->used_cnt) {
+ old_alloced->iov_base = old_used;
+ old_alloced->iov_len += base->used_cnt * 2 *
+ sizeof(struct iovec);
+ }
+
+ base->used = l_malloc(base->alloced_cnt * 2 *
+ sizeof(struct iovec));
+ memcpy(base->used, old_used,
+ base->used_cnt * sizeof(struct iovec));
+ memcpy(base->used + base->alloced_cnt, old_alloced,
+ base->used_cnt * sizeof(struct iovec));
+ } else {
+ base->used = l_realloc(base->used, base->alloced_cnt *
+ 2 * sizeof(struct iovec));
+ memcpy(base->used + base->alloced_cnt,
+ base->used + base->used_cnt,
+ base->used_cnt * sizeof(struct iovec));
+ }
+
+ /* Update all intermediate l_bufs */
+ for (i = parent; i != base; i = i->parent) {
+ i->used = base->used + (i->used - old_used);
+ i->alloced_cnt = base->alloced_cnt;
+ }
+ }
+
+ buf->parent = parent;
+ buf->child = NULL;
+ buf->used = base->used + (base->used_cnt++);
+ buf->alloced_cnt = base->alloced_cnt;
+ return true;
+}
+
+LIB_EXPORT void l_buf_free(struct l_buf *buf)
+{
+ const void *static_iov_base;
+ const struct iovec *used;
+ const struct iovec *alloced;
+
+ if (unlikely(!buf || buf->parent))
+ return;
+
+ used = buf->used;
+ alloced = used + buf->alloced_cnt;
+
+ if (used == buf->iovecs)
+ static_iov_base = alloced + buf->alloced_cnt;
+ else
+ static_iov_base = buf->iovecs;
+
+ for (; buf->used_cnt; buf->used_cnt--, used++, alloced++) {
+ explicit_bzero(used->iov_base, used->iov_len);
+
+ if (alloced->iov_base != static_iov_base)
+ l_free(alloced->iov_base);
+ }
+
+ if (buf->used != buf->iovecs)
+ l_free(buf->used);
+
+ do {
+ struct l_buf *child = buf->child;
+
+ l_free(buf);
+ buf = child;
+ } while (buf);
+}
+
+LIB_EXPORT void l_buf_extend(struct l_buf *buf, size_t bytes, bool at_head)
+{
+ struct iovec *cur = buf->used;
+ struct iovec *cur_alloced = cur + buf->alloced_cnt;
+ size_t headroom = cur->iov_base - cur_alloced->iov_base;
+ const void *static_iov_base;
+
+ if (!buf->parent) {
+ if (cur == buf->iovecs)
+ static_iov_base = cur_alloced + buf->alloced_cnt;
+ else
+ static_iov_base = buf->iovecs;
+ } else
+ static_iov_base = NULL;
+
+ cur_alloced->iov_len += bytes;
+
+ /* Use realloc only if it can save us the memcpy/memmove */
+ if (cur_alloced->iov_base != static_iov_base && !at_head &&
+ cur->iov_len) {
+ cur_alloced->iov_base =
+ l_realloc(cur_alloced->iov_base, cur_alloced->iov_len);
+ cur->iov_base = cur_alloced->iov_base + headroom;
+ } else {
+ void *old_buf = cur_alloced->iov_base;
+
+ cur_alloced->iov_base = l_malloc(cur_alloced->iov_len);
+ cur->iov_base = cur_alloced->iov_base + headroom;
+
+ if (at_head)
+ cur->iov_base += bytes;
+
+ if (cur->iov_len) {
+ memcpy(cur->iov_base, old_buf + headroom, cur->iov_len);
+ explicit_bzero(old_buf + headroom, cur->iov_len);
+ }
+
+ if (old_buf != static_iov_base)
+ l_free(old_buf);
+ }
+}
diff --git a/ell/buf.h b/ell/buf.h
new file mode 100644
index 0000000..a3d64d3
--- /dev/null
+++ b/ell/buf.h
@@ -0,0 +1,258 @@
+/*
+ *
+ * Embedded Linux library
+ *
+ * Copyright (C) 2021 Intel Corporation. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __ELL_BUF_H
+#define __ELL_BUF_H
+
+#include <sys/uio.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * At the low level this stores up to @alloced_cnt contiguous buffers or
+ * segments. Within each segment there's a "used" part of the segment
+ * identified by the start pointer and the length. There's also
+ * differentation between base l_bufs, that own the allocated memory,
+ * and nested l_bufs that point to their parent l_buf's segments.
+ *
+ * In the base l_buf, the @iovecs array contains 2 * @alloced_cnt iovecs.
+ * The last @alloced_cnt iovecs each point at the entirety of the
+ * corresponding segment's allocated memory while the first @alloced_cnt
+ * iovecs point at the "used" part of the corresponding segment. Only
+ * segments from 0 to (@used_cnt - 1) have memory allocated.
+ *
+ * @used[0] is the "used" iovec for the "current" segment, i.e. anything
+ * between @used->iov_base and @used->iov_base + @used->iov_len.
+ * @used + @alloced_cnt points at the entirety of the "current" segment.
+ * Anything before @used->iov_base (and after @used[alloced_cnt].iov_base)
+ * is the headroom and anything after @used->iov_base + @used->iov_len
+ * (up to @used[alloced_cnt].iov_base + @used[alloced_cnt].iov_len) is
+ * the tailroom. Initally the "used" part is empty, i.e. @used->iov_base
+ * points somewhere inside the allocated segment and @used->iov_len is 0.
+ * l_buf_prepend() is then used to expand it into the headroom and
+ * l_buf_append() to expand it into the tailroom, reducing the available
+ * room. The headroom and tailroom of the allocated segments, and the
+ * @iov_base and @iov_len of the not-yet-allocated segments have
+ * uninitialized values.
+ *
+ * There's no way to change which segment is "current". The segments
+ * after "current" are accessed by creating a nested l_buf using
+ * l_buf_append_init() and l_buf_append_new(). Its "current" segment is
+ * going to be different. Segments are allocated their memory when they
+ * become the "current" segment of an l_buf. That memory is not freed
+ * until the base l_buf is freed though. Nested l_bufs can't be freed
+ * using l_buf_free, they're also automatically freed with the base l_buf.
+ * A nested l_buf and all its childred l_bufs also become invalid when
+ * a new call to l_buf_append_new() is made for the same parent l_buf or
+ * its ancestor.
+ */
+struct l_buf {
+ struct {
+ struct l_buf *parent;
+ struct l_buf *child;
+ struct iovec *used;
+ unsigned int alloced_cnt;
+ };
+ unsigned int used_cnt;
+ struct iovec iovecs[0];
+};
+
+struct l_buf *l_buf_new(size_t headroom, size_t tailroom,
+ unsigned int nested_cnt);
+bool l_buf_append_init(struct l_buf *buf, struct l_buf *parent,
+ size_t headroom, size_t tailroom);
+
+static inline struct l_buf *l_buf_append_new(struct l_buf *parent,
+ size_t headroom,
+ size_t tailroom)
+{
+ struct l_buf *ref_parent = parent;
+ struct l_buf *old_child;
+
+ if (unlikely(!parent))
+ return NULL;
+
+ /*
+ * Find the first ancestor referenced by its .parent->child, i.e.
+ * one that was allocated dynamically by l_buf_append_new().
+ * That ancestor is going to hold the reference to the new l_buf
+ * so that it can be automatically re-used and freed later. Don't
+ * link the new l_buf to a parent that is not part of the singly
+ * linked list formed by the .child pointers, otherwise it'd leak.
+ */
+ while (ref_parent->parent && ref_parent->parent->child != ref_parent)
+ ref_parent = ref_parent->parent;
+
+ if (ref_parent->child)
+ /* Save previous child(ren) l_buf(s) for re-use */
+ old_child = ref_parent->child->child;
+ else {
+ ref_parent->child = l_malloc(offsetof(struct l_buf, used_cnt));
+ old_child = NULL;
+ }
+
+ /* Cannot fail when parent and parent->child are non-NULL */
+ l_buf_append_init(ref_parent->child, parent, headroom, tailroom);
+
+ ref_parent->child->child = old_child;
+ return parent->child;
+}
+
+void l_buf_free(struct l_buf *buf);
+void l_buf_extend(struct l_buf *buf, size_t bytes, bool at_head);
+
+static inline uint8_t *l_buf_get_headroom(struct l_buf *buf, size_t bytes)
+{
+ struct iovec *cur = buf->used;
+ struct iovec *cur_alloced = cur + buf->alloced_cnt;
+
+ if (cur->iov_base - bytes < cur_alloced->iov_base)
+ l_buf_extend(buf, cur_alloced->iov_base + bytes - cur->iov_base,
+ true);
+
+ cur->iov_base -= bytes;
+ cur->iov_len += bytes;
+ return cur->iov_base;
+}
+
+static inline uint8_t *l_buf_get_tailroom(struct l_buf *buf, size_t bytes)
+{
+ struct iovec *cur = buf->used;
+ struct iovec *cur_alloced = cur + buf->alloced_cnt;
+ void *end = cur->iov_base + cur->iov_len;
+
+ if (end + bytes > cur_alloced->iov_base + cur_alloced->iov_len) {
+ l_buf_extend(buf, end + bytes -
+ cur_alloced->iov_base - cur_alloced->iov_len,
+ false);
+ end = cur->iov_base + cur->iov_len;
+ }
+
+ cur->iov_len += bytes;
+ return end;
+}
+
+static inline void l_buf_prepend(struct l_buf *buf, const void *data,
+ size_t len)
+{
+ if (likely(len))
+ memcpy(l_buf_get_headroom(buf, len), data, len);
+}
+
+static inline void l_buf_append(struct l_buf *buf, const void *data, size_t len)
+{
+ if (likely(len))
+ memcpy(l_buf_get_tailroom(buf, len), data, len);
+}
+
+static inline void l_buf_prependv(struct l_buf *buf, const struct iovec *iov,
+ unsigned int iov_cnt)
+{
+ while (iov_cnt) {
+ iov_cnt--;
+ l_buf_prepend(buf, iov[iov_cnt].iov_base, iov[iov_cnt].iov_len);
+ }
+}
+
+static inline void l_buf_appendv(struct l_buf *buf, const struct iovec *iov,
+ unsigned int iov_cnt)
+{
+ while (iov_cnt) {
+ iov_cnt--;
+ l_buf_append(buf, iov->iov_base, iov->iov_len);
+ iov++;
+ }
+}
+
+#define l_buf_prepends(buf, in) l_buf_prepend((buf), &(in), sizeof(in))
+#define l_buf_appends(buf, in) l_buf_append((buf), &(in), sizeof(in))
+
+static inline void l_buf_pad(struct l_buf *buf, uint8_t value, size_t len)
+{
+ if (likely(len))
+ memset(l_buf_get_tailroom(buf, len), value, len);
+}
+
+static inline size_t l_buf_get_len(const struct l_buf *buf)
+{
+ const struct l_buf *base;
+ unsigned int i;
+ size_t len = 0;
+
+ if (unlikely(!buf))
+ return 0;
+
+ for (base = buf; base->parent; base = base->parent);
+
+ for (i = buf->used - base->used; i < base->used_cnt; i++)
+ len += base->used[i].iov_len;
+
+ return len;
+}
+
+static inline bool l_buf_put_headroom(struct l_buf *buf, size_t bytes)
+{
+ if (buf->used->iov_len < bytes)
+ return false;
+
+ buf->used->iov_base += bytes;
+ buf->used->iov_len -= bytes;
+ return true;
+}
+
+static inline bool l_buf_put_tailroom(struct l_buf *buf, size_t bytes)
+{
+ if (buf->used->iov_len < bytes)
+ return false;
+
+ buf->used->iov_len -= bytes;
+ return true;
+}
+
+static inline uint8_t *l_buf_get_data(const struct l_buf *buf, size_t *out_len)
+{
+ size_t len = l_buf_get_len(buf);
+ uint8_t *data = l_malloc(len);
+ uint8_t *ptr = data;
+ int i;
+
+ if (out_len)
+ *out_len = len;
+
+ for (i = 0; len; i++) {
+ memcpy(ptr, buf->used[i].iov_base, buf->used[i].iov_len);
+ ptr += buf->used[i].iov_len;
+ len -= buf->used[i].iov_len;
+ }
+
+ return data;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ELL_BUF_H */
diff --git a/ell/ell.h b/ell/ell.h
index 22fddf7..ab8f0f0 100644
--- a/ell/ell.h
+++ b/ell/ell.h
@@ -64,3 +64,4 @@
#include <ell/gpio.h>
#include <ell/path.h>
#include <ell/acd.h>
+#include <ell/buf.h>
diff --git a/ell/ell.sym b/ell/ell.sym
index aa0c046..8372e5f 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -664,6 +664,11 @@ global:
l_acd_set_debug;
l_acd_set_skip_probes;
l_acd_set_defend_policy;
+ /* buf */
+ l_buf_new;
+ l_buf_append_init;
+ l_buf_free;
+ l_buf_extend;
local:
*;
};
--
2.27.0
1 month, 2 weeks
[PATCH] dbus: explicitly handle messages with NULL interface
by James Prestwood
It was assumed that the DBus daemon would filter messages with
no interfaces set, but some daemons do not (dbus-broker). This
leads to the potential for a crash if the method call has no
interface set. A crash can be seen in IWD with a few lines of
python:
bus = dbus.SystemBus()
obj = bus.get_object("net.connman.iwd", "/")
print(obj.FooBar())
The above isn't necissarily a 'valid' way of doing things, but
it does result in a crash which traces back to ELL. The actual
method call (FooBar in this case) is arbitrary and could be
anything.
++++++++ backtrace ++++++++
0 0x7f532cda6a70 in /lib64/libc.so.6
1 0x47c4d2 in _dbus_object_tree_dispatch() at ell/dbus-service.c:1755
2 0x473f23 in message_read_handler() at ell/dbus.c:284
3 0x46be0c in io_callback() at ell/io.c:118
4 0x46b12d in l_main_iterate() at ell/main.c:471 (discriminator 2)
5 0x46b1dc in l_main_run() at ell/main.c:520
6 0x46b3ec in l_main_run_with_signal() at ell/main.c:648
7 0x403ea9 in main() at src/main.c:490
8 0x7f532cd91042 in /lib64/libc.so.6
+++++++++++++++++++++++++++
The DBus spec does mention the possibility of the interface field
being empty. It does not recommend doing this, but does not
explicitly forbid it. Handling of this case is left up to the
implementation.
The fix is simple: check that the message has an interface set and
if not return an error.
---
ell/dbus-service.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/ell/dbus-service.c b/ell/dbus-service.c
index 4976b43..a7d6236 100644
--- a/ell/dbus-service.c
+++ b/ell/dbus-service.c
@@ -1749,6 +1749,16 @@ bool _dbus_object_tree_dispatch(struct _dbus_object_tree *tree,
member = l_dbus_message_get_member(message);
msg_sig = l_dbus_message_get_signature(message);
+ /*
+ * Nothing in the spec explicitly forbids this, but handling of such
+ * messages is left up to the implementation.
+ *
+ * TODO: Another route is to go looking for a matching method under this
+ * object and call it.
+ */
+ if (!interface)
+ return false;
+
if (!msg_sig)
msg_sig = "";
--
2.26.2
1 month, 2 weeks
[PATCH] ell/cipher.c: fix build on uclibc
by Fabrice Fontaine
Include missing.h to avoid a build failure when explicit_bzero is
missing
Signed-off-by: Fabrice Fontaine <fontaine.fabrice(a)gmail.com>
---
ell/cipher.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ell/cipher.c b/ell/cipher.c
index 5d67d7a..e274459 100644
--- a/ell/cipher.c
+++ b/ell/cipher.c
@@ -34,6 +34,7 @@
#include "cipher.h"
#include "private.h"
#include "random.h"
+#include "missing.h"
#ifndef HAVE_LINUX_IF_ALG_H
#ifndef HAVE_LINUX_TYPES_H
--
2.29.2
1 month, 2 weeks
[PATCH 1/3] cert: Add PKCS#12 loading support
by Andrew Zaborowski
Add ability to parse PKCS#12 files in l_cert_load_container_file(). This
has only been tested with the files produced by openssl so far.
Extend the static buffer in struct asn1_oid (asn1-private.h) so we can
handle some 11-byte OIDs used in PKCS#12 files.
---
ell/asn1-private.h | 2 +-
ell/cert.c | 690 +++++++++++++++++++++++++++++++++++++++++++++
ell/pem.c | 4 +
3 files changed, 695 insertions(+), 1 deletion(-)
diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index 2a31241..079570b 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -37,7 +37,7 @@
struct asn1_oid {
uint8_t asn1_len;
- uint8_t asn1[10];
+ uint8_t asn1[11];
};
#define asn1_oid_eq(oid1, oid2_len, oid2_string) \
diff --git a/ell/cert.c b/ell/cert.c
index a37b501..cf81931 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -585,6 +585,11 @@ struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t *der,
return l_key_new(L_KEY_RSA, der, der_len);
}
+/*
+ * The passphrase, if given, must have been validated as UTF-8 unless the
+ * caller knows that PKCS#12 encryption algorithms are not used.
+ * Use l_utf8_validate.
+ */
struct l_key *cert_key_from_pkcs8_encrypted_private_key_info(const uint8_t *der,
size_t der_len,
const char *passphrase)
@@ -739,6 +744,636 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
return pkey;
}
+static const uint8_t *cert_unpack_pkcs7_content_info(const uint8_t *container,
+ size_t container_len, int pos,
+ const struct asn1_oid *expected_oid,
+ struct asn1_oid *out_oid,
+ uint8_t *out_tag, size_t *out_len)
+{
+ const uint8_t *content_info;
+ size_t content_info_len;
+ const uint8_t *type;
+ size_t type_len;
+ const uint8_t *ret;
+ uint8_t tag;
+
+ if (!(content_info = asn1_der_find_elem(container, container_len, pos,
+ &tag, &content_info_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return NULL;
+
+ if (!(type = asn1_der_find_elem(content_info, content_info_len, 0,
+ &tag, &type_len)) ||
+ tag != ASN1_ID_OID ||
+ type_len > sizeof(out_oid->asn1))
+ return NULL;
+
+ if (expected_oid && !asn1_oid_eq(expected_oid, type_len, type))
+ return NULL;
+
+ if (!(ret = asn1_der_find_elem(content_info, content_info_len,
+ ASN1_CONTEXT_EXPLICIT(0),
+ out_tag, out_len)) ||
+ ret + *out_len != content_info + content_info_len)
+ return NULL;
+
+ if (out_oid) {
+ out_oid->asn1_len = type_len;
+ memcpy(out_oid->asn1, type, type_len);
+ }
+
+ return ret;
+}
+
+/* RFC5652 Section 8 */
+static uint8_t *cert_decrypt_pkcs7_encrypted_data(const uint8_t *data,
+ size_t data_len,
+ const char *password,
+ struct asn1_oid *out_oid,
+ size_t *out_len)
+{
+ const uint8_t *version;
+ size_t version_len;
+ const uint8_t *encrypted_info;
+ size_t encrypted_info_len;
+ const uint8_t *type;
+ size_t type_len;
+ const uint8_t *alg_id;
+ size_t alg_id_len;
+ const uint8_t *encrypted;
+ size_t encrypted_len;
+ uint8_t tag;
+ struct l_cipher *alg;
+ uint8_t *plaintext;
+ int i;
+ bool ok;
+ bool is_block;
+
+ if (!(version = asn1_der_find_elem(data, data_len, 0, &tag,
+ &version_len)) ||
+ tag != ASN1_ID_INTEGER || version_len != 1 ||
+ !L_IN_SET(version[0], 0, 2))
+ return NULL;
+
+ if (!(encrypted_info = asn1_der_find_elem(data, data_len, 1, &tag,
+ &encrypted_info_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return NULL;
+
+ if (!(type = asn1_der_find_elem(encrypted_info, encrypted_info_len, 0,
+ &tag, &type_len)) ||
+ tag != ASN1_ID_OID ||
+ type_len > sizeof(out_oid->asn1))
+ return NULL;
+
+ if (!(alg_id = asn1_der_find_elem(encrypted_info, encrypted_info_len, 1,
+ &tag, &alg_id_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return NULL;
+
+ /* Not optional in our case, defined [0] IMPLICIT OCTET STRING */
+ if (!(encrypted = asn1_der_find_elem(encrypted_info, encrypted_info_len,
+ ASN1_CONTEXT_IMPLICIT(0),
+ &tag, &encrypted_len)) ||
+ tag != ASN1_ID(ASN1_CLASS_CONTEXT, 0, 0) ||
+ encrypted_len < 8)
+ return NULL;
+
+ if (!(alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, password,
+ &is_block)))
+ return NULL;
+
+ plaintext = l_malloc(encrypted_len);
+ ok = l_cipher_decrypt(alg, encrypted, plaintext, encrypted_len);
+ l_cipher_free(alg);
+
+ if (!ok) {
+ l_free(plaintext);
+ return NULL;
+ }
+
+ if (is_block) {
+ bool ok = true;
+
+ /* Also validate the padding */
+ if (encrypted_len < plaintext[encrypted_len - 1] ||
+ plaintext[encrypted_len - 1] > 16) {
+ plaintext[encrypted_len - 1] = 1;
+ ok = false;
+ }
+
+ for (i = 1; i < plaintext[encrypted_len - 1]; i++)
+ if (plaintext[encrypted_len - 1 - i] !=
+ plaintext[encrypted_len - 1])
+ ok = false;
+
+ if (!ok) {
+ explicit_bzero(plaintext, encrypted_len);
+ l_free(plaintext);
+ return NULL;
+ }
+
+ encrypted_len -= plaintext[encrypted_len - 1];
+ }
+
+ if (out_oid) {
+ out_oid->asn1_len = type_len;
+ memcpy(out_oid->asn1, type, type_len);
+ }
+
+ *out_len = encrypted_len;
+ return plaintext;
+}
+
+/* RFC7292 Appendix A. */
+static const struct cert_pkcs12_hash pkcs12_mac_algs[] = {
+ {
+ L_CHECKSUM_MD5, 16, 16, 64,
+ { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0f, 0x02, 0x05 } }
+ },
+ {
+ L_CHECKSUM_SHA1, 20, 20, 64,
+ { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } }
+ },
+ {
+ L_CHECKSUM_SHA224, 28, 28, 64,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 } }
+ },
+ {
+ L_CHECKSUM_SHA256, 32, 32, 64,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } }
+ },
+ {
+ L_CHECKSUM_SHA384, 48, 48, 128,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } }
+ },
+ {
+ L_CHECKSUM_SHA512, 64, 64, 128,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } }
+ },
+ {
+ L_CHECKSUM_SHA512, 64, 28, 128,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05 } }
+ },
+ {
+ L_CHECKSUM_SHA512, 64, 32, 128,
+ { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06 } }
+ },
+};
+
+static const struct asn1_oid pkcs12_key_bag_oid = {
+ 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x01 }
+};
+
+static const struct asn1_oid pkcs12_pkcs8_shrouded_key_bag_oid = {
+ 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02 }
+};
+
+static const struct asn1_oid pkcs12_cert_bag_oid = {
+ 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03 }
+};
+
+static const struct asn1_oid pkcs12_safe_contents_bag_oid = {
+ 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x06 }
+};
+
+static const struct asn1_oid pkcs9_x509_certificate_oid = {
+ 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x16, 0x01 }
+};
+
+/* RFC7292 Section 4.2.3 */
+static bool cert_parse_pkcs12_cert_bag(const uint8_t *data, size_t data_len,
+ struct l_certchain **out_certchain)
+{
+ const uint8_t *cert_bag;
+ size_t cert_bag_len;
+ const uint8_t *cert_id;
+ size_t cert_id_len;
+ const uint8_t *cert_value;
+ size_t cert_value_len;
+ uint8_t tag;
+ struct l_cert *cert;
+
+ if (!(cert_bag = asn1_der_find_elem(data, data_len, 0,
+ &tag, &cert_bag_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ if (!(cert_id = asn1_der_find_elem(cert_bag, cert_bag_len, 0,
+ &tag, &cert_id_len)) ||
+ tag != ASN1_ID_OID)
+ return false;
+
+ if (!(cert_value = asn1_der_find_elem(cert_bag, cert_bag_len,
+ ASN1_CONTEXT_EXPLICIT(0),
+ &tag, &cert_value_len)) ||
+ tag != ASN1_ID_OCTET_STRING ||
+ cert_value + cert_value_len != data + data_len)
+ return false;
+
+ /* Skip unsupported certificate types */
+ if (!asn1_oid_eq(&pkcs9_x509_certificate_oid, cert_id_len, cert_id))
+ return true;
+
+ if (!(cert = l_cert_new_from_der(cert_value, cert_value_len)))
+ return false;
+
+ if (!*out_certchain)
+ *out_certchain = certchain_new_from_leaf(cert);
+ else
+ certchain_link_issuer(*out_certchain, cert);
+
+ return true;
+}
+
+static bool cert_parse_pkcs12_safe_contents(const uint8_t *data,
+ size_t data_len, const char *password,
+ struct l_certchain **out_certchain,
+ struct l_key **out_privkey)
+{
+ const uint8_t *safe_contents;
+ size_t safe_contents_len;
+ uint8_t tag;
+
+ if (!(safe_contents = asn1_der_find_elem(data, data_len, 0, &tag,
+ &safe_contents_len)) ||
+ tag != ASN1_ID_SEQUENCE ||
+ data + data_len != safe_contents + safe_contents_len)
+ return false;
+
+ /* RFC7292 Section 4.2 */
+ while (safe_contents_len) {
+ const uint8_t *safe_bag;
+ size_t safe_bag_len;
+ const uint8_t *bag_id;
+ size_t bag_id_len;
+ const uint8_t *bag_value;
+ int bag_value_len;
+
+ /* RFC7292 Section 4.2 */
+ if (!(safe_bag = asn1_der_find_elem(safe_contents,
+ safe_contents_len, 0,
+ &tag, &safe_bag_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ if (!(bag_id = asn1_der_find_elem(safe_bag, safe_bag_len, 0,
+ &tag, &bag_id_len)) ||
+ tag != ASN1_ID_OID)
+ return false;
+
+ /*
+ * The bagValue is EXPLICITly tagged but we don't want to
+ * unpack the inner TLV yet so don't use asn1_der_find_elem.
+ */
+ safe_bag_len -= bag_id + bag_id_len - safe_bag;
+ safe_bag = bag_id + bag_id_len;
+
+ if (safe_bag_len < 4)
+ return false;
+
+ tag = *safe_bag++;
+ safe_bag_len--;
+ bag_value_len = asn1_parse_definite_length(&safe_bag,
+ &safe_bag_len);
+ bag_value = safe_bag;
+
+ if (bag_value_len < 0 || bag_value_len > (int) safe_bag_len ||
+ tag != ASN1_ID(ASN1_CLASS_CONTEXT, 1, 0))
+ return false;
+
+ /* PKCS#9 attributes ignored */
+
+ safe_contents_len -= (safe_bag + safe_bag_len - safe_contents);
+ safe_contents = safe_bag + safe_bag_len;
+
+ if (asn1_oid_eq(&pkcs12_key_bag_oid, bag_id_len, bag_id)) {
+ if (!out_privkey || *out_privkey)
+ continue;
+
+ *out_privkey =
+ cert_key_from_pkcs8_private_key_info(bag_value,
+ bag_value_len);
+ if (!*out_privkey)
+ return false;
+ } else if (asn1_oid_eq(&pkcs12_pkcs8_shrouded_key_bag_oid,
+ bag_id_len, bag_id)) {
+ if (!out_privkey || *out_privkey)
+ continue;
+
+ *out_privkey =
+ cert_key_from_pkcs8_encrypted_private_key_info(
+ bag_value,
+ bag_value_len,
+ password);
+ if (!*out_privkey)
+ return false;
+ } else if (asn1_oid_eq(&pkcs12_cert_bag_oid,
+ bag_id_len, bag_id)) {
+ if (!out_certchain)
+ continue;
+
+ if (!cert_parse_pkcs12_cert_bag(bag_value, bag_value_len,
+ out_certchain))
+ return false;
+ } else if (asn1_oid_eq(&pkcs12_safe_contents_bag_oid,
+ bag_id_len, bag_id)) {
+ /* TODO: depth check */
+ if (!(cert_parse_pkcs12_safe_contents(bag_value,
+ bag_value_len,
+ password,
+ out_certchain,
+ out_privkey)))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool cert_check_pkcs12_integrity(const uint8_t *mac_data,
+ size_t mac_data_len,
+ const uint8_t *auth_safe,
+ size_t auth_safe_len,
+ const char *password)
+{
+ const uint8_t *mac;
+ size_t mac_len;
+ const uint8_t *mac_salt;
+ size_t mac_salt_len;
+ const uint8_t *iterations_data;
+ size_t iterations_len;
+ unsigned int iterations;
+ const uint8_t *digest_alg;
+ size_t digest_alg_len;
+ const uint8_t *digest;
+ size_t digest_len;
+ const uint8_t *alg_id;
+ size_t alg_id_len;
+ const struct cert_pkcs12_hash *mac_hash;
+ L_AUTO_FREE_VAR(uint8_t *, key) = NULL;
+ struct l_checksum *hmac;
+ uint8_t hmac_val[64];
+ uint8_t tag;
+ bool ok;
+ unsigned int i;
+
+ if (!(mac = asn1_der_find_elem(mac_data, mac_data_len, 0, &tag,
+ &mac_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ if (!(mac_salt = asn1_der_find_elem(mac_data, mac_data_len, 1, &tag,
+ &mac_salt_len)) ||
+ tag != ASN1_ID_OCTET_STRING || mac_salt_len > 1024)
+ return false;
+
+ if (!(iterations_data = asn1_der_find_elem(mac_data, mac_data_len, 2,
+ &tag,
+ &iterations_len)) ||
+ tag != ASN1_ID_INTEGER || iterations_len > 4)
+ return false;
+
+ for (iterations = 0; iterations_len; iterations_len--)
+ iterations = (iterations << 8) | *iterations_data++;
+
+ if (iterations < 1 || iterations > 8192)
+ return false;
+
+ /* RFC2315 Section 9.4 */
+ if (!(digest_alg = asn1_der_find_elem(mac, mac_len, 0, &tag,
+ &digest_alg_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ if (!(digest = asn1_der_find_elem(mac, mac_len, 1, &tag,
+ &digest_len)) ||
+ tag != ASN1_ID_OCTET_STRING)
+ return false;
+
+ if (!(alg_id = asn1_der_find_elem(digest_alg, digest_alg_len,
+ 0, &tag, &alg_id_len)) ||
+ tag != ASN1_ID_OID)
+ return false;
+
+ /* This is going to be used for both the MAC and its key derivation */
+ for (i = 0; i < L_ARRAY_SIZE(pkcs12_mac_algs); i++)
+ if (asn1_oid_eq(&pkcs12_mac_algs[i].oid, alg_id_len, alg_id)) {
+ mac_hash = &pkcs12_mac_algs[i];
+ break;
+ }
+
+ if (i == L_ARRAY_SIZE(pkcs12_mac_algs) || digest_len != mac_hash->u)
+ return false;
+
+ if (!(key = cert_pkcs12_pbkdf(password, mac_hash,
+ mac_salt, mac_salt_len,
+ iterations, 3, mac_hash->u)))
+ return false;
+
+ hmac = l_checksum_new_hmac(mac_hash->alg, key, mac_hash->u);
+ explicit_bzero(key, mac_hash->u);
+
+ if (!hmac)
+ return false;
+
+ ok = l_checksum_update(hmac, auth_safe, auth_safe_len) &&
+ l_checksum_get_digest(hmac, hmac_val, mac_hash->len) > 0;
+ l_checksum_free(hmac);
+
+ if (!ok)
+ return false;
+
+ /*
+ * SHA-512/224 and SHA-512/256 are not supported. We can truncate the
+ * output for key derivation but we can't do this inside the HMAC
+ * algorithms based on these hashes. We skip the MAC verification
+ * if one of these hashes is used (identified by .u != .len)
+ */
+ if (mac_hash->u != mac_hash->len)
+ return true;
+
+ return l_secure_memcmp(hmac_val, digest, digest_len) == 0;
+}
+
+/* RFC5652 Section 4 */
+static const struct asn1_oid pkcs7_data_oid = {
+ 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01 }
+};
+
+/* RFC5652 Section 8 */
+static const struct asn1_oid pkcs7_encrypted_data_oid = {
+ 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x06 }
+};
+
+static bool cert_parse_auth_safe_content(const uint8_t *data, size_t data_len,
+ uint8_t tag,
+ const struct asn1_oid *data_oid,
+ const char *password,
+ struct l_certchain **out_certchain,
+ struct l_key **out_privkey)
+{
+ if (asn1_oid_eq(&pkcs7_encrypted_data_oid,
+ data_oid->asn1_len, data_oid->asn1)) {
+ uint8_t *plaintext;
+ size_t plaintext_len;
+ struct asn1_oid oid;
+ bool ok;
+
+ if (tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ /*
+ * This is same as PKCS#7 encryptedData but the ciphers
+ * used are from PKCS#12 (broken but still the default
+ * everywhere) and PKCS#5 (recommended).
+ */
+ plaintext = cert_decrypt_pkcs7_encrypted_data(data,
+ data_len,
+ password, &oid,
+ &plaintext_len);
+ if (!plaintext)
+ return false;
+
+ /*
+ * Since we only support PKCS#7 data and encryptedData
+ * types, and there's no point re-encrypting
+ * encryptedData, the plaintext must be a PKCS#7
+ * "data".
+ */
+ ok = asn1_oid_eq(&pkcs7_data_oid,
+ oid.asn1_len, oid.asn1) &&
+ cert_parse_pkcs12_safe_contents(plaintext,
+ plaintext_len,
+ password,
+ out_certchain,
+ out_privkey);
+ explicit_bzero(plaintext, plaintext_len);
+ l_free(plaintext);
+
+ if (!ok)
+ return false;
+ } else if (asn1_oid_eq(&pkcs7_data_oid,
+ data_oid->asn1_len, data_oid->asn1)) {
+ if (tag != ASN1_ID_OCTET_STRING)
+ return false;
+
+ if (!cert_parse_pkcs12_safe_contents(data, data_len,
+ password,
+ out_certchain,
+ out_privkey))
+ return false;
+ }
+ /* envelopedData support not needed */
+
+ return true;
+}
+
+static bool cert_parse_pkcs12_pfx(const uint8_t *ptr, size_t len,
+ const char *password,
+ struct l_certchain **out_certchain,
+ struct l_key **out_privkey)
+{
+ const uint8_t *version;
+ size_t version_len;
+ const uint8_t *auth_safe;
+ size_t auth_safe_len;
+ const uint8_t *mac_data;
+ size_t mac_data_len;
+ const uint8_t *auth_safe_seq;
+ size_t auth_safe_seq_len;
+ uint8_t tag;
+ unsigned int i;
+ struct l_certchain *certchain = NULL;
+ struct l_key *privkey = NULL;
+
+ /* RFC7292 Section 4 */
+ if (!(version = asn1_der_find_elem(ptr, len, 0, &tag, &version_len)) ||
+ tag != ASN1_ID_INTEGER)
+ return false;
+
+ if (version_len != 1 || version[0] != 3)
+ return false;
+
+ /*
+ * Since we only support the password-based integrity mode, the
+ * authSafe must be of PKCS#7 type "data" and not "signedData".
+ */
+ if (!(auth_safe = cert_unpack_pkcs7_content_info(ptr, len, 1,
+ &pkcs7_data_oid, NULL,
+ &tag,
+ &auth_safe_len)) ||
+ tag != ASN1_ID_OCTET_STRING)
+ return false;
+
+ /*
+ * openssl can generate PFX structures without macData not signed
+ * with a public key so handle this case, otherwise the macData
+ * would not be optional.
+ */
+ if (auth_safe + auth_safe_len == ptr + len)
+ goto integrity_check_done;
+
+ if (!(mac_data = asn1_der_find_elem(ptr, len, 2, &tag,
+ &mac_data_len)) ||
+ tag != ASN1_ID_SEQUENCE)
+ return false;
+
+ if (!cert_check_pkcs12_integrity(mac_data, mac_data_len,
+ auth_safe, auth_safe_len,
+ password))
+ return false;
+
+integrity_check_done:
+ if (!(auth_safe_seq = asn1_der_find_elem(auth_safe, auth_safe_len, 0,
+ &tag, &auth_safe_seq_len)) ||
+ tag != ASN1_ID_SEQUENCE ||
+ auth_safe + auth_safe_len !=
+ auth_safe_seq + auth_safe_seq_len)
+ return false;
+
+ i = 0;
+ while (1) {
+ struct asn1_oid data_oid;
+ const uint8_t *data;
+ size_t data_len;
+
+ if (!(data = cert_unpack_pkcs7_content_info(auth_safe_seq,
+ auth_safe_seq_len, i++,
+ NULL, &data_oid, &tag,
+ &data_len)))
+ goto error;
+
+ if (!cert_parse_auth_safe_content(data, data_len, tag,
+ &data_oid, password,
+ out_certchain ?
+ &certchain : NULL,
+ out_privkey ?
+ &privkey : NULL))
+ goto error;
+
+ if (data + data_len == auth_safe_seq + auth_safe_seq_len)
+ break;
+ }
+
+ if (out_certchain)
+ *out_certchain = certchain;
+
+ if (out_privkey)
+ *out_privkey = privkey;
+
+ return true;
+
+error:
+ if (certchain)
+ l_certchain_free(certchain);
+
+ if (privkey)
+ l_key_free(privkey);
+
+ return false;
+}
+
/*
* Look at a file, try to detect which of the few X.509 certificate and/or
* private key container formats it uses and load any certificates in it as
@@ -750,10 +1385,16 @@ struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der,
* PEM PKCS#8 encrypted and unencrypted private keys
* PEM legacy PKCS#1 encrypted and unencrypted private keys
* Raw X.509 certificates (.cer, .der, .crt)
+ * PKCS#12 certificates
+ * PKCS#12 encrypted private keys
*
* The raw format contains exactly one certificate, PEM and PKCS#12 files
* can contain any combination of certificates and private keys.
*
+ * The password must have been validated as UTF-8 (use l_utf8_validate)
+ * unless the caller knows that no PKCS#12-defined encryption algorithm
+ * or MAC is used.
+ *
* Returns false on "unrecoverable" errors, and *out_certchain,
* *out_privkey and *out_encrypted (if provided) are not modified. However
* when true is returned, *out_certchain and *out_privkey (if provided) may
@@ -823,6 +1464,32 @@ LIB_EXPORT bool l_cert_load_container_file(const char *filename,
goto close;
}
+
+ if (tag == ASN1_ID_INTEGER) {
+ /*
+ * Since we don't support public key-protected PKCS#12
+ * modes, we always require the password at least for
+ * the integrity check. Strictly speaking encryption
+ * may not actually be in use. We also don't support
+ * files with different integrity and privacy
+ * passwords, they must be identical if privacy is
+ * enabled.
+ */
+ encrypted = true;
+
+ if (!password) {
+ error = !out_encrypted;
+ done = true;
+ goto close;
+ }
+
+ error = !cert_parse_pkcs12_pfx(seq_data, len, password,
+ out_certchain ?
+ &certchain : NULL,
+ out_privkey ?
+ &privkey : NULL);
+ goto close;
+ }
}
not_der_after_all:
@@ -890,6 +1557,29 @@ not_der_after_all:
continue;
}
+ /* Cisco/gnutls-type PEM-encoded PKCS#12, probably rare */
+ if (L_IN_STRSET(type_label, "PKCS12")) {
+ encrypted = true;
+
+ if (!password) {
+ if (certchain && out_privkey) {
+ l_certchain_free(certchain);
+ certchain = NULL;
+ }
+
+ error = !out_encrypted;
+ done = true;
+ goto next;
+ }
+
+ error = !cert_parse_pkcs12_pfx(der, der_len, password,
+ out_certchain ?
+ &certchain : NULL,
+ out_privkey ?
+ &privkey : NULL);
+ goto next;
+ }
+
next:
explicit_bzero(der, der_len);
l_free(der);
diff --git a/ell/pem.c b/ell/pem.c
index bc2865a..67d2af1 100644
--- a/ell/pem.c
+++ b/ell/pem.c
@@ -791,6 +791,10 @@ LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf,
* success case and on error when NULL is returned. This can be used to
* check if a passphrase is required without prior information.
*
+ * The passphrase, if given, must have been validated as UTF-8 unless the
+ * caller knows that PKCS#12 encryption algorithms are not used.
+ * Use l_utf8_validate.
+ *
* Returns: An l_key object to be freed with an l_key_free* function,
* or NULL.
**/
--
2.27.0
1 month, 3 weeks