From a7830cf780716d71bde396cbe3697994fc94e129 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20W=C3=B3jcik?= <chocimier@tlen.pl>
Date: Tue, 1 Oct 2019 21:18:25 +0200
Subject: [PATCH] Verify repodata signature

---
 include/xbps.h.in       | 18 ++++++++++++-
 include/xbps_api_impl.h |  2 +-
 lib/archive.c           |  8 ++++--
 lib/package_unpack.c    |  4 +--
 lib/repo.c              | 56 +++++++++++++++++++++++++++++++++++++----
 lib/transaction_files.c |  2 +-
 lib/verifysig.c         | 39 +++++++++++++++++-----------
 7 files changed, 102 insertions(+), 27 deletions(-)

diff --git a/include/xbps.h.in b/include/xbps.h.in
index 46a5592f..5df41b57 100644
--- a/include/xbps.h.in
+++ b/include/xbps.h.in
@@ -110,7 +110,7 @@
  */
 #define XBPS_PKGFILES		"files.plist"
 
-/** 
+/**
  * @def XBPS_REPOIDX
  * Filename for the repository index property list.
  */
@@ -1897,6 +1897,22 @@ int xbps_file_hash_check(const char *file, const char *sha256);
 bool xbps_verify_signature(struct xbps_repo *repo, const char *sigfile,
 		unsigned char *digest);
 
+/**
+ * Verifies the RSA signature \sig_buf of bytes with hash \a digest
+ * with the RSA public-key associated in \a repo.
+ *
+ * @param[in] repo Repository to use with the RSA public key associated.
+ * @param[in] idxmeta Meta dictionary related to \a repo, containing
+ * public-key field.
+ * @param[in] sig_buf The signature of file content that has hash \digest.
+ * @param[in] sigfilelen The length of signature.
+ * @param[in] digest The digest of file content to verify.
+ *
+ * @return True if the signature is valid, false otherwise.
+ */
+bool xbps_verify_digest_signature(struct xbps_repo *repo, xbps_dictionary_t idxmeta,
+    unsigned char *sig_buf, size_t sigfilelen, unsigned char *digest);
+
 /**
  * Verifies the RSA signature of \a fname with the RSA public-key associated
  * in \a repo.
diff --git a/include/xbps_api_impl.h b/include/xbps_api_impl.h
index d0a701b5..90f16a85 100644
--- a/include/xbps_api_impl.h
+++ b/include/xbps_api_impl.h
@@ -149,7 +149,7 @@ int HIDDEN xbps_register_pkg(struct xbps_handle *, xbps_dictionary_t);
 void HIDDEN xbps_transaction_conflicts(struct xbps_handle *, xbps_array_t);
 char HIDDEN *xbps_archive_get_file(struct archive *, struct archive_entry *);
 xbps_dictionary_t HIDDEN xbps_archive_get_dictionary(struct archive *,
-		struct archive_entry *);
+		struct archive_entry *, char **bytes);
 const char HIDDEN *vpkg_user_conf(struct xbps_handle *, const char *, bool);
 xbps_array_t HIDDEN xbps_get_pkg_fulldeptree(struct xbps_handle *,
 		const char *, bool);
diff --git a/lib/archive.c b/lib/archive.c
index 94cba292..7a4c019d 100644
--- a/lib/archive.c
+++ b/lib/archive.c
@@ -56,7 +56,7 @@ xbps_archive_get_file(struct archive *ar, struct archive_entry *entry)
 }
 
 xbps_dictionary_t HIDDEN
-xbps_archive_get_dictionary(struct archive *ar, struct archive_entry *entry)
+xbps_archive_get_dictionary(struct archive *ar, struct archive_entry *entry, char **bytes)
 {
 	xbps_dictionary_t d = NULL;
 	char *buf;
@@ -66,7 +66,11 @@ xbps_archive_get_dictionary(struct archive *ar, struct archive_entry *entry)
 
 	/* If blob is already a dictionary we are done */
 	d = xbps_dictionary_internalize(buf);
-	free(buf);
+	if (bytes == NULL) {
+		free(buf);
+	} else {
+		*bytes = buf;
+	}
 	return d;
 }
 
diff --git a/lib/package_unpack.c b/lib/package_unpack.c
index e995d40c..c761264a 100644
--- a/lib/package_unpack.c
+++ b/lib/package_unpack.c
@@ -180,13 +180,13 @@ unpack_archive(struct xbps_handle *xhp,
 				goto out;
 			}
 		} else if (strcmp("./props.plist", entry_pname) == 0) {
-			binpkg_propsd = xbps_archive_get_dictionary(ar, entry);
+			binpkg_propsd = xbps_archive_get_dictionary(ar, entry, NULL);
 			if (binpkg_propsd == NULL) {
 				rv = EINVAL;
 				goto out;
 			}
 		} else if (strcmp("./files.plist", entry_pname) == 0) {
-			binpkg_filesd = xbps_archive_get_dictionary(ar, entry);
+			binpkg_filesd = xbps_archive_get_dictionary(ar, entry, NULL);
 			if (binpkg_filesd == NULL) {
 				rv = EINVAL;
 				goto out;
diff --git a/lib/repo.c b/lib/repo.c
index 1b0755d2..cd5796ea 100644
--- a/lib/repo.c
+++ b/lib/repo.c
@@ -61,11 +61,48 @@ xbps_repo_path_with_name(struct xbps_handle *xhp, const char *url, const char *n
 	    url, xhp->target_arch ? xhp->target_arch : xhp->native_arch, name);
 }
 
+static bool
+repo_verify_index(struct xbps_repo *repo, unsigned char *digest) {
+	bool verified = false;
+	unsigned char *sig_buf = NULL;
+	size_t sigfilelen = 0;
+	struct archive_entry *entry;
+
+	if (archive_read_next_header(repo->ar, &entry) != ARCHIVE_OK) {
+		xbps_dbg_printf(repo->xhp,
+		    "%s: read_next_header %s\n", repo->uri,
+		    archive_error_string(repo->ar));
+		return false;
+	}
+
+	if (strcmp(archive_entry_pathname(entry), XBPS_REPOIDX_SIG) != 0) {
+		xbps_dbg_printf(repo->xhp,
+		    "%s: no signature of %s\n", repo->uri, XBPS_REPOIDX);
+		return false;
+	}
+
+	sigfilelen = (size_t)archive_entry_size(entry);
+	sig_buf = (unsigned char *) xbps_archive_get_file(repo->ar, entry);
+	if (sig_buf == NULL) {
+		return false;
+	}
+	verified = xbps_verify_digest_signature(repo, sig_buf, sigfilelen, digest);
+
+	free(sig_buf);
+	return verified;
+}
+
 static xbps_dictionary_t
-repo_get_dict(struct xbps_repo *repo)
+repo_get_dict(struct xbps_repo *repo, bool *verified)
 {
 	struct archive_entry *entry;
 	int rv;
+	xbps_dictionary_t dict;
+	char *bytes = NULL;
+	unsigned char *digest = NULL;
+
+	if (verified != NULL)
+		*verified = false;
 
 	if (repo->ar == NULL)
 		return NULL;
@@ -77,7 +114,15 @@ repo_get_dict(struct xbps_repo *repo)
 		    archive_error_string(repo->ar));
 		return NULL;
 	}
-	return xbps_archive_get_dictionary(repo->ar, entry);
+	dict = xbps_archive_get_dictionary(repo->ar, entry, &bytes);
+	if (verified != NULL &&
+	    bytes != NULL &&
+	    (digest = xbps_buffer_hash_raw(bytes, strlen(bytes))) != NULL &&
+	    repo_verify_index(repo, digest))
+		*verified = true;
+	free(digest);
+	free(bytes);
+	return dict;
 }
 
 bool
@@ -135,6 +180,7 @@ repo_open_local(struct xbps_repo *repo, const char *repofile)
 {
 	struct stat st;
 	int rv = 0;
+	bool verified = false;
 
 	if (fstat(repo->fd, &st) == -1) {
 		rv = errno;
@@ -158,7 +204,7 @@ repo_open_local(struct xbps_repo *repo, const char *repofile)
 		    repofile, strerror(rv));
 		return false;
 	}
-	if ((repo->idx = repo_get_dict(repo)) == NULL) {
+	if ((repo->idx = repo_get_dict(repo, &verified)) == NULL) {
 		xbps_dbg_printf(repo->xhp, "[repo] `%s' failed to internalize "
 		    " index on archive, removing file.\n", repofile);
 		/* broken archive, remove it */
@@ -166,13 +212,13 @@ repo_open_local(struct xbps_repo *repo, const char *repofile)
 		return false;
 	}
 	xbps_dictionary_make_immutable(repo->idx);
-	repo->idxmeta = repo_get_dict(repo);
+	repo->idxmeta = repo_get_dict(repo, NULL);
 	if (repo->idxmeta != NULL) {
 		repo->is_signed = true;
 		xbps_dictionary_make_immutable(repo->idxmeta);
 	}
 
-	return true;
+	return verified || (!repo->is_remote && !repo->is_signed);
 }
 
 static bool
diff --git a/lib/transaction_files.c b/lib/transaction_files.c
index 41b35ca3..df1209e9 100644
--- a/lib/transaction_files.c
+++ b/lib/transaction_files.c
@@ -732,7 +732,7 @@ collect_binpkg_files(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod,
 
 		entry_pname = archive_entry_pathname(entry);
 		if ((strcmp("./files.plist", entry_pname)) == 0) {
-			filesd = xbps_archive_get_dictionary(ar, entry);
+			filesd = xbps_archive_get_dictionary(ar, entry, NULL);
 			if (filesd == NULL) {
 				rv = EINVAL;
 				goto out;
diff --git a/lib/verifysig.c b/lib/verifysig.c
index 58729a37..e0ccbfab 100644
--- a/lib/verifysig.c
+++ b/lib/verifysig.c
@@ -72,28 +72,25 @@ rsa_verify_hash(struct xbps_repo *repo, xbps_data_t pubkey,
 }
 
 bool
-xbps_verify_signature(struct xbps_repo *repo, const char *sigfile,
-		unsigned char *digest)
+xbps_verify_digest_signature(struct xbps_repo *repo, xbps_dictionary_t idxmeta,
+		unsigned char *sig_buf, size_t sigfilelen, unsigned char *digest)
 {
 	xbps_dictionary_t repokeyd = NULL;
 	xbps_data_t pubkey;
 	char *hexfp = NULL;
-	unsigned char *sig_buf = NULL;
-	size_t sigbuflen, sigfilelen;
 	char *rkeyfile = NULL;
 	bool val = false;
 
-	if (!xbps_dictionary_count(repo->idxmeta)) {
+	if (!xbps_dictionary_count(idxmeta)) {
 		xbps_dbg_printf(repo->xhp, "%s: unsigned repository\n", repo->uri);
 		return false;
 	}
 	hexfp = xbps_pubkey2fp(repo->xhp,
-	    xbps_dictionary_get(repo->idxmeta, "public-key"));
+	    xbps_dictionary_get(idxmeta, "public-key"));
 	if (hexfp == NULL) {
 		xbps_dbg_printf(repo->xhp, "%s: incomplete signed repo, missing hexfp obj\n", repo->uri);
 		return false;
 	}
-
 	/*
 	 * Prepare repository RSA public key to verify fname signature.
 	 */
@@ -108,12 +105,6 @@ xbps_verify_signature(struct xbps_repo *repo, const char *sigfile,
 	pubkey = xbps_dictionary_get(repokeyd, "public-key");
 	if (xbps_object_type(pubkey) != XBPS_TYPE_DATA)
 		goto out;
-
-	if (!xbps_mmap_file(sigfile, (void *)&sig_buf, &sigbuflen, &sigfilelen)) {
-		xbps_dbg_printf(repo->xhp, "can't open signature file %s: %s\n",
-		    sigfile, strerror(errno));
-		goto out;
-	}
 	/*
 	 * Verify fname RSA signature.
 	 */
@@ -125,14 +116,32 @@ out:
 		free(hexfp);
 	if (rkeyfile)
 		free(rkeyfile);
-	if (sig_buf)
-		(void)munmap(sig_buf, sigbuflen);
 	if (repokeyd)
 		xbps_object_release(repokeyd);
 
 	return val;
 }
 
+bool
+xbps_verify_signature(struct xbps_repo *repo, const char *sigfile,
+		unsigned char *digest)
+{
+	unsigned char *sig_buf = NULL;
+	size_t sigbuflen, sigfilelen;
+	bool result = false;
+
+	if (xbps_mmap_file(sigfile, (void *)&sig_buf, &sigbuflen, &sigfilelen)) {
+		result = xbps_verify_digest_signature(repo, repo->idxmeta, sig_buf, sigfilelen, digest);
+	} else {
+		xbps_dbg_printf(repo->xhp, "can't open signature file %s: %s\n",
+		    sigfile, strerror(errno));
+	}
+
+	if (sig_buf)
+		(void)munmap(sig_buf, sigbuflen);
+	return result;
+}
+
 bool
 xbps_verify_file_signature(struct xbps_repo *repo, const char *fname)
 {