Subject: [alpine-aports] [PATCH v3.2] main/ca-certificates: security upgrade to 20161130

From: Sergei Lukin <[email-masked]>
Date: Fri, 27 Jan 2017 10:04:54 +0000

ref #6528
---
 main/ca-certificates/APKBUILD               |  60 +++--
 main/ca-certificates/fix-manpage.patch      |  13 ++
 main/ca-certificates/update-ca-certificates |  86 --------
 main/ca-certificates/update-ca.c            | 327 ++++++++++++++++++++++++++++
 4 files changed, 380 insertions(+), 106 deletions(-)
 create mode 100644 main/ca-certificates/fix-manpage.patch
 delete mode 100755 main/ca-certificates/update-ca-certificates
 create mode 100644 main/ca-certificates/update-ca.c

diff --git a/main/ca-certificates/APKBUILD b/main/ca-certificates/APKBUILD
index 8f5d3fc..84e0ca6 100644
--- a/main/ca-certificates/APKBUILD
+++ b/main/ca-certificates/APKBUILD
@@ -1,30 +1,40 @@
+# Contributor: Sergei Lukin <[email-masked]>
 # Maintainer: Natanael Copa <[email-masked]>
 pkgname=ca-certificates
-pkgver=20141019
-
-_date=${pkgver%_p*}
-_nmu="+nmu${pkgver#*_p}"
-[ "$_nmu" = "+nmu${pkgver}" ] && _nmu=""
-_ver=${pkgver}
-
-pkgrel=2
+pkgver=20161130
+pkgrel=0
 pkgdesc="Common CA certificates PEM files"
 url="http://packages.debian.org/sid/ca-certificates"
-arch="noarch"
+arch="all"
 license="MPL 2.0 GPL2+"
 depends="run-parts openssl lua5.2 lua5.2-posix"
 makedepends="python"
 subpackages="$pkgname-doc"
 options="!fhs"
-triggers="ca-certificates.trigger=/usr/share/ca-certificates:/usr/local/share/ca-certificates:/etc/ssl/certs"
-source="http://ftp.no.debian.org/debian/pool/main/c/$pkgname/${pkgname}_${_ver}.tar.xz
-	update-ca-certificates
+triggers="ca-certificates.trigger=/usr/share/ca-certificates:/usr/local/share/ca-certificates:/etc/ssl/certs:/etc/ca-certificates/update.d"
+source="http://ftp.no.debian.org/debian/pool/main/c/$pkgname/${pkgname}_${pkgver}.tar.xz
+	fix-manpage.patch
+	update-ca.c
 	"
 
-_builddir="$srcdir"/$pkgname-$_ver
+_builddir="$srcdir"/$pkgname
+
+prepare() {
+	cd "$_builddir"
+	for i in $source; do
+		case $i in
+		*.patch) msg $i; patch -p1 -i "$srcdir"/$i || return 1;; 
+		esac
+	done
+}
+
 build () {
 	cd "$_builddir"
 	make || return 1
+
+
+        ${CC:-gcc} ${CFLAGS} -o update-ca-certificates "$srcdir"/update-ca.c \
+		${LDFLAGS} || return 1
 }
 
 package() {
@@ -35,6 +45,7 @@ package() {
 		"$pkgdir"/usr/local/share/ca-certificates \
 		"$pkgdir"/etc/ssl/certs \
 		|| return 1
+
 	make DESTDIR="$pkgdir" install || return 1
 	install -D -m644 sbin/update-ca-certificates.8 \
 		"$pkgdir"/usr/share/man/man8/update-ca-certificates.8 \
@@ -50,7 +61,7 @@ package() {
 
 	# http://bugs.alpinelinux.org/issues/2715
 	# http://bugs.alpinelinux.org/issues/2846
-	install -m755 "$srcdir"/update-ca-certificates "$pkgdir"/usr/sbin \
+	install -m755 update-ca-certificates "$pkgdir"/usr/sbin \
 		|| return 1
 
 	mkdir -p "$pkgdir"/etc/apk/protected_paths.d
@@ -59,11 +70,20 @@ package() {
 -etc/ssl/certs/ca-cert-*.pem
 -etc/ssl/certs/[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[r0-9]*
 EOF
+
+        cat > "$pkgdir"/etc/ca-certificates/update.d/c_rehash <<EOF
+#!/bin/sh
+exec /usr/bin/c_rehash /etc/ssl/certs
+EOF
+        chmod +x "$pkgdir"/etc/ca-certificates/update.d/c_rehash || return 1
 }
 
-md5sums="f619282081c8bfc65ea64c37fa5285ed  ca-certificates_20141019.tar.xz
-0c2fb9aa695d9d857fecd1c236930016  update-ca-certificates"
-sha256sums="684902d3f4e9ad27829f4af0d9d2d588afed03667997579b9c2be86fcd1eb73a  ca-certificates_20141019.tar.xz
-b95a80d5881a3ffeea3f36599503a141f9c5a433bc9646d673225658ebc032a1  update-ca-certificates"
-sha512sums="5b0e8fb917f5642a5a2b4fde46a706db0c652ff3fb31a5053d9123a5b670b50c6e3cf2496915cc01c613dcbe964d6432f393c12d8a697baedfad58f9d13e568b  ca-certificates_20141019.tar.xz
-ce0e6317af25a5433a4fef28db6afd0ef985089f4a6b9eb13ac1ca454de854ae3de18029fed1e385651317cb237581a38d3792c42f5f30ec12667609d689b4e1  update-ca-certificates"
+md5sums="1a0a3a1b3390dc83affed4b0c2ae1c05  ca-certificates_20161130.tar.xz
+0c3d9f5d795c7475b997e18498b7aec8  fix-manpage.patch
+d7773c690432a56911c0fa97a67bf43e  update-ca.c"
+sha256sums="04bca9e142a90a834aca0311f7ced237368d71fee7bd5c9f68ef7f4611aee471  ca-certificates_20161130.tar.xz
+60b36c4881bb367891df038a0736456c2d170496de8c339026671008b1caa09b  fix-manpage.patch
+b2231e7f0304d6360350f1f1aa9afad46fe4eeb0f0306a48eeb8cd74501ab26c  update-ca.c"
+sha512sums="8395f27d2369d694b069e1bb250b06df05f732bd9f4a4dc8652091e9c96ad1a84003e28f59cb9e13fdfd22ca5818f495d80149692e74b2d63e34db4f6a95ee9f  ca-certificates_20161130.tar.xz
+690d6bb434fb3ccce931d7ee6a167124f9c2d2e7e7a016d85f7b72a5f7f7c34db8c6133f3575e962a91981a32a88f8961776fe5fd907e57f59c03a32f2fcced3  fix-manpage.patch
+403c909ea4107d944789b8aae9c911c735ac651cf882bf8468e0f91b3179700a78cabcbec16e3ae0bed730b54d1813d5d0891c1aeae583ad80c7a20b0f425b12  update-ca.c"
diff --git a/main/ca-certificates/fix-manpage.patch b/main/ca-certificates/fix-manpage.patch
new file mode 100644
index 0000000..c4c1290
--- /dev/null
+++ b/main/ca-certificates/fix-manpage.patch
@@ -0,0 +1,13 @@
+--- ./sbin/update-ca-certificates.8.orig	2016-01-14 10:56:42.084504796 +0100
++++ ./sbin/update-ca-certificates.8	2016-01-14 10:57:21.685102125 +0100
+@@ -40,9 +40,7 @@
+ /usr/local/share/ca-certificates are also included as implicitly trusted.
+ .PP
+ Before terminating, \fBupdate-ca-certificates\fP invokes
+-\fBrun-parts\fP on /etc/ca-certificates/update.d and calls each hook with
+-a list of certificates: those added are prefixed with a +, those removed are
+-prefixed with a -.
++\fBrun-parts\fP on /etc/ca-certificates/update.d.
+ .SH OPTIONS
+ A summary of options is included below.
+ .TP
diff --git a/main/ca-certificates/update-ca-certificates b/main/ca-certificates/update-ca-certificates
deleted file mode 100755
index 1780ce5..0000000
--- a/main/ca-certificates/update-ca-certificates
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/lua5.2
-
-local CERTSDIR='/usr/share/ca-certificates/'
-local LOCALCERTSDIR='/usr/local/share/ca-certificates/'
-local ETCCERTSDIR='/etc/ssl/certs/'
-local CERTBUNDLE='ca-certificates.crt'
-local CERTSCONF='/etc/ca-certificates.conf'
-
-local posix = require 'posix'
-function string.begins(str, prefix) return str:sub(1,#prefix)==prefix end
-
-local function add(fn, out, links)
-	-- Map fn to file in etc
-	local pem = "ca-cert-"..fn:gsub('.*/', ''):gsub('.crt$',''):gsub('[, ]','_'):gsub('[()]','=')..".pem"
-	links[pem] = fn
-	-- Read the certificate for the bundle
-	local f = io.open(fn, "rb")
-	if f ~= nil then
-		local content = f:read("*all")
-		f:close()
-		out:write(content)
-		if content:sub(-1) ~= '\n' then out:write('\n') end
-	end
-end
-
-local calinks = {}
-local cacerts = {}
-
-local fd, tmpfile = posix.mkstemp(ETCCERTSDIR..'bundleXXXXXX')
-if not fd then
-	print("Failed to open temporary file for ca bundle")
-	return 1
-end
-posix.close(fd)
-posix.chmod(tmpfile, "rw-r--r--")
-local bundle = io.open(tmpfile, "wb")
-
--- Handle global CA certs from config file
-for l in io.lines(CERTSCONF) do
-	local firstchar = l:sub(1,1)
-	if firstchar ~= "#" and firstchar ~= "!" then
-		add(CERTSDIR..l, bundle, calinks)
-	end
-end
-
--- Handle local CA certificates
-local certlist = posix.glob(LOCALCERTSDIR..'*.crt')
-if certlist ~= nil then
-	table.sort(certlist)
-	for _, fn in ipairs(certlist) do
-		if posix.stat(fn, 'type') == 'regular' then
-			add(fn, bundle, calinks)
-		end
-	end
-end
-
--- Update etc cert dir for additions and deletions
-local f, target
-for f in posix.files(ETCCERTSDIR) do
-	local fn = ETCCERTSDIR..f
-	if posix.stat(fn, 'type') == 'link' then
-		local curtgt = posix.readlink(fn)
-		local target = calinks[f]
-		if target == nil then
-			-- Symlink exists but is not wanted
-			-- Delete it if it points to 'our' directory
-			if curtgt:begins(CERTSDIR) or curtgt:begins(LOCALCERTSDIR) then
-				os.remove(fn)
-			end
-		elseif curtgt ~= target then
-			-- Symlink exists but points wrong
-			posix.link(target, ETCCERTSDIR..f, true)
-		else
-			-- Symlink exists and is ok
-			calinks[f] = nil
-		end
-	end
-end
-for f, target in pairs(calinks) do
-	posix.link(target, ETCCERTSDIR..f, true)
-end
-
--- Update hashes and the bundle
-bundle:close()
-os.rename(tmpfile, ETCCERTSDIR..CERTBUNDLE)
-os.execute("c_rehash "..ETCCERTSDIR.." > /dev/null")
diff --git a/main/ca-certificates/update-ca.c b/main/ca-certificates/update-ca.c
new file mode 100644
index 0000000..20af994
--- /dev/null
+++ b/main/ca-certificates/update-ca.c
@@ -0,0 +1,327 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/stat.h>
+#include <sys/sendfile.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#define CERTSDIR "/usr/share/ca-certificates/"
+#define LOCALCERTSDIR "/usr/local/share/ca-certificates/"
+#define ETCCERTSDIR "/etc/ssl/certs/"
+#define RUNPARTSDIR "/etc/ca-certificates/update.d/"
+#define CERTBUNDLE "ca-certificates.crt"
+#define CERTSCONF "/etc/ca-certificates.conf"
+
+static const char *last_component(const char *path)
+{
+	const char *c = strrchr(path, '/');
+	if (c) return c + 1;
+	return path;
+}
+static bool str_begins(const char* str, const char* prefix)
+{
+	return !strncmp(str, prefix, strlen(prefix));
+}
+
+struct hash_item {
+	struct hash_item *next;
+	char *key;
+	char *value;
+};
+
+struct hash {
+	struct hash_item *items[256];
+};
+
+static unsigned int hash_string(const char *str)
+{
+	unsigned long h = 5381;
+	for (; *str; str++)
+		h = (h << 5) + h + *str;
+	return h;
+}
+
+static void hash_init(struct hash *h)
+{
+	memset(h, 0, sizeof *h);
+}
+
+static struct hash_item *hash_get(struct hash *h, const char *key)
+{
+	unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
+	struct hash_item *item;
+
+	for (item = h->items[bucket]; item; item = item->next)
+		if (strcmp(item->key, key) == 0)
+			return item;
+	return NULL;
+}
+
+static void hash_foreach(struct hash *h, void (*cb)(struct hash_item *))
+{
+	struct hash_item *item;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(h->items); i++) {
+		for (item = h->items[i]; item; item = item->next)
+			cb(item);
+	}
+}
+
+static bool hash_add(struct hash *h, const char *key, const char *value)
+{
+	unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
+	size_t keylen = strlen(key), valuelen = strlen(value);
+	struct hash_item *i;
+
+	i = malloc(sizeof(struct hash_item) + keylen + 1 + valuelen + 1);
+	if (!i)
+		return false;
+
+	i->key = (char*)(i+1);
+	strcpy(i->key, key);
+	i->value = i->key + keylen + 1;
+	strcpy(i->value, value);
+
+	i->next = h->items[bucket];
+	h->items[bucket] = i;
+	return true;
+}
+
+static bool
+copyfile(const char* source, int output)
+{
+	off_t bytes = 0;
+	struct stat fileinfo = {0};
+	ssize_t result;
+	int in_fd;
+
+	if ((in_fd = open(source, O_RDONLY)) == -1)
+		return false;
+
+	if (fstat(in_fd, &fileinfo) < 0) {
+		close(in_fd);
+		return false;
+	}
+
+	result = sendfile(output, in_fd, &bytes, fileinfo.st_size);
+	close(in_fd);
+
+	return fileinfo.st_size == result;
+}
+
+typedef void (*proc_path)(const char *fullpath, struct hash *, int);
+
+static void proc_localglobaldir(const char *fullpath, struct hash *h, int tmpfile_fd)
+{
+	const char *fname = last_component(fullpath);
+	size_t flen = strlen(fname);
+	char *s, *actual_file = NULL;
+
+	/* Snip off the .crt suffix */
+	if (flen > 4 && strcmp(&fname[flen-4], ".crt") == 0)
+		flen -= 4;
+
+	if (asprintf(&actual_file, "%s%.*s%s",
+				   "ca-cert-",
+				   flen, fname,
+				   ".pem") == -1) {
+		fprintf(stderr, "Cannot open path: %s\n", fullpath);
+		return;
+	}
+
+	for (s = actual_file; *s; s++) {
+		switch(*s) {
+		case ',':
+		case ' ':
+			*s = '_';
+			break;
+		case ')':
+		case '(':
+			*s = '=';
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!hash_add(h, actual_file, fullpath))
+		fprintf(stderr, "Warning! Cannot hash: %s\n", fullpath);
+	if (!copyfile(fullpath, tmpfile_fd))
+		fprintf(stderr, "Warning! Cannot copy to bundle: %s\n", fullpath);
+	free(actual_file);
+}
+
+static void proc_etccertsdir(const char* fullpath, struct hash* h, int tmpfile_fd)
+{
+	char linktarget[SYMLINK_MAX];
+	ssize_t linklen;
+
+	linklen = readlink(fullpath, linktarget, sizeof(linktarget)-1);
+	if (linklen < 0)
+		return;
+	linktarget[linklen] = 0;
+
+	struct hash_item *item = hash_get(h, last_component(fullpath));
+	if (!item) {
+		/* Symlink exists but is not wanted
+		 * Delete it if it points to 'our' directory
+		 */
+		if (str_begins(linktarget, CERTSDIR) || str_begins(linktarget, LOCALCERTSDIR))
+			unlink(fullpath);
+	} else if (strcmp(linktarget, item->value) != 0) {
+		/* Symlink exists but points wrong */
+		unlink(fullpath);
+		if (symlink(item->value, fullpath) < 0)
+			fprintf(stderr, "Warning! Cannot update symlink %s -> %s\n", item->value, fullpath);
+		item->value = 0;
+	} else {
+		/* Symlink exists and is ok */
+		item->value = 0;
+	}
+}
+
+static bool read_global_ca_list(const char* file, struct hash* d, int tmpfile_fd)
+{
+	FILE * fp = fopen(file, "r");
+	if (fp == NULL)
+		return false;
+
+	char * line = NULL;
+	size_t len = 0;
+	ssize_t read;
+
+	while ((read = getline(&line, &len, fp)) != -1) {
+		/* getline returns number of bytes in buffer, and buffer
+		 * contains delimeter if it was found */
+		if (read > 0 && line[read-1] == '\n')
+			line[read-1] = 0;
+		if (str_begins(line, "#") || str_begins(line, "!"))
+			continue;
+
+		char* fullpath = 0;
+		if (asprintf(&fullpath,"%s%s", CERTSDIR, line) != -1) {
+			proc_localglobaldir(fullpath, d, tmpfile_fd);
+			free(fullpath);
+		}
+	}
+
+	fclose(fp);
+	free(line);
+	return true;
+}
+
+typedef enum {
+	FILE_LINK,
+	FILE_REGULAR
+} filetype;
+
+static bool is_filetype(const char* path, filetype file_check)
+{
+	struct stat statbuf;
+
+	if (lstat(path, &statbuf) < 0)
+		return false;
+	switch(file_check) {
+	case FILE_LINK: return S_ISLNK(statbuf.st_mode);
+	case FILE_REGULAR: return S_ISREG(statbuf.st_mode);
+	default: break;
+	}
+
+	return false;
+}
+
+static bool dir_readfiles(struct hash* d, const char* path,
+			  filetype allowed_file_type,
+			  proc_path path_processor,
+			  int tmpfile_fd)
+{
+	DIR *dp = opendir(path);
+	if (!dp)
+		return false;
+ 
+	struct dirent *dirp;
+	while ((dirp = readdir(dp)) != NULL) {
+		if (str_begins(dirp->d_name, "."))
+			continue;
+
+		char* fullpath = 0;
+		if (asprintf(&fullpath, "%s%s", path, dirp->d_name) != -1) {
+			if (is_filetype(fullpath, allowed_file_type))
+				path_processor(fullpath, d, tmpfile_fd);
+
+			free(fullpath);
+		}
+	}
+
+	return closedir(dp) == 0;
+}
+
+static void update_ca_symlink(struct hash_item *item)
+{
+	if (!item->value)
+		return;
+
+	char* newpath = 0;
+	bool build_str = asprintf(&newpath, "%s%s", ETCCERTSDIR, item->key);
+	if (!build_str || symlink(item->value, newpath) == -1)
+		fprintf(stderr, "Warning! Cannot symlink %s -> %s\n",
+			item->value, newpath);
+	free(newpath);
+}
+
+int main(int a, char **v)
+{
+	struct hash _calinks, *calinks = &_calinks;
+
+	const char* bundle = "bundleXXXXXX";
+	char* tmpfile = 0;
+	if (asprintf(&tmpfile, "%s%s", ETCCERTSDIR, bundle) == -1)
+		return 1;
+
+	int fd = mkstemp(tmpfile);
+	if (fd == -1) {
+		fprintf(stderr, "Failed to open temporary file %s for ca bundle\n", tmpfile);
+		return 1;
+	}
+	fchmod(fd, 0644);
+
+	hash_init(calinks);
+
+	/* Handle global CA certs from config file */
+	read_global_ca_list(CERTSCONF, calinks, fd);
+
+	/* Handle local CA certificates */
+	dir_readfiles(calinks, LOCALCERTSDIR, FILE_REGULAR, &proc_localglobaldir, fd);
+
+	/* Update etc cert dir for additions and deletions*/
+	dir_readfiles(calinks, ETCCERTSDIR, FILE_LINK, &proc_etccertsdir, fd);
+	hash_foreach(calinks, update_ca_symlink);
+
+	/* Update hashes and the bundle */
+	if (fd != -1) {
+		close(fd);
+		char* newcertname = 0;
+		if (asprintf(&newcertname, "%s%s", ETCCERTSDIR, CERTBUNDLE) != -1) {
+			rename(tmpfile, newcertname);
+			free(newcertname);
+		}
+	}
+
+	free(tmpfile);
+
+	/* Execute run-parts */
+	static const char *run_parts_args[] = { "run-parts", RUNPARTSDIR, 0 };
+	execve("/usr/bin/run-parts", run_parts_args, NULL);
+	execve("/bin/run-parts", run_parts_args, NULL);
+	perror("run-parts");
+
+	return 1;
+}
-- 
2.4.11

---
Unsubscribe:  [email-masked]
Help:         [email-masked]
---