[pacman-dev] [PATCH 4/6] Signature verification functions

Denis A. Altoé Falqueto denisfalqueto at gmail.com
Wed Jul 7 13:43:40 EDT 2010


Two functions were created to verify the signatures made:

_alpm_gpg_checksig_memory checks signatures which are in
memory. The packages' signatures are stored in the repository
database and are kept in memory when they need to be verified.
This function will put them in a temporary file and call the
function below to make the real verification.

_alpm_gpg_checksig_file does the real verification, calling
gpg in a subprocess and checking if the signature is valid
and the key used is trusted.

The return values for both functions are: 0 = valid; 1 = invalid;
-1 = error while verifying.

Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto at gmail.com>
---
 lib/libalpm/signing.c |  282 +++++++++++++++++++++++--------------------------
 lib/libalpm/signing.h |    3 +-
 2 files changed, 135 insertions(+), 150 deletions(-)

diff --git a/lib/libalpm/signing.c b/lib/libalpm/signing.c
index 2b15528..80eba15 100644
--- a/lib/libalpm/signing.c
+++ b/lib/libalpm/signing.c
@@ -17,13 +17,8 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

-#include "config.h"
-
-#include <stdlib.h>
 #include <stdio.h>
-#include <string.h>
-#include <locale.h> /* setlocale() */
-#include <gpgme.h>
+#include <errno.h>

 /* libalpm */
 #include "signing.h"
@@ -32,169 +27,158 @@
 #include "log.h"
 #include "alpm.h"

-#define CHECK_ERR(void) do { \
-		if(err != GPG_ERR_NO_ERROR) { goto error; } \
-	} while(0)
+// Max length of the command line string. I believe that it is enough...
+#define MAX_COMMAND 2048
+#define MAX_LINE 1024
+#define TRUST_MARGINAL "[GNUPG:] TRUST_MARGINAL"
+#define TRUST_FULLY "[GNUPG:] TRUST_FULLY"
+#define TRUST_ULTIMATE "[GNUPG:] TRUST_ULTIMATE"
+
+// Names of files related to the keyring
+#define PACMAN_KEYRING_DIR "/etc/pacman.d/gnupg"
+#define PACMAN_SECRET "secret.gpg"
+#define PACMAN_TRUSTDB "trustdb.gpg"
+#define PACMAN_KEYRING "pacman.gpg"
+#define PACMAN_SHARE_DIR "/usr/share/pacman"
+
+// Commands and common parameters
+#define GPG_PROGRAM "gpg2"
+#define GPG GPG_PROGRAM " --ignore-time-conflict --no-options
--no-default-keyring"
+#define GPG_PACMAN GPG " --status-fd 1 --homedir " PACMAN_KEYRING_DIR
" --secret-keyring " \
+    PACMAN_KEYRING_DIR "/" PACMAN_SECRET " --trustdb-name "
PACMAN_KEYRING_DIR \
+    "/" PACMAN_TRUSTDB " --keyring " PACMAN_KEYRING_DIR "/" PACMAN_KEYRING \
+    " --primary-keyring " PACMAN_KEYRING_DIR "/" PACMAN_KEYRING " 2> /dev/null"

-static int gpgme_init(void)
+/**
+ * Check the PGP package signature for the given package file.
+ * The signature is stored on memory, so it needs to be saved to
+ * a temporary file.
+ * @param pkgpath the full path to a package file
+ * @param sig PGP signature data in raw form (already decoded)
+ * @return a int value : 0 (valid), 1 (invalid), -1 (an error occured)
+ */
+int _alpm_gpg_checksig_memory(const char *pkgpath, const pmpgpsig_t *sig)
 {
-	static int init = 0;
-	const char *version;
-	gpgme_error_t err;
-	gpgme_engine_info_t enginfo;
-
-	ALPM_LOG_FUNC;
-
-	if(init) {
-		/* we already successfully initialized the library */
-		return(0);
+	int status = 1, i;
+	char *tmpName = NULL;
+	FILE *tmpSig = NULL;
+	
+	// Get a temporary file, which will store the signature.
+	while (tmpSig == NULL) {
+		tmpName = tempnam(NULL, "pacman");
+		if (tmpName == NULL) {
+			_alpm_log(PM_LOG_ERROR, _("Unable to create a temporary file name.\n"));
+			status = -1;
+			goto cleanup;
+		}
+		_alpm_log(PM_LOG_DEBUG, "tmpName: \'%s\'\n", tmpName);
+		
+		// Write the signature to the temporary file
+		tmpSig = fopen(tmpName, "w+x");
+		if (tmpSig == NULL && errno == EEXIST) {
+			_alpm_log(PM_LOG_DEBUG, "the temporary file already exists\n");
+			FREE(tmpName);
+		} else if (tmpSig == NULL) {
+			_alpm_log(PM_LOG_ERROR, _("Unable to create a temporary file.\n"));
+			status = -1;
+			goto cleanup;
+		}
 	}

-	if(!alpm_option_get_signaturedir()) {
-		RET_ERR(PM_ERR_SIG_MISSINGDIR, 1);
+	// Copy the signature into the temp file
+	// Write the bytes at once, to make it more efficient
+	for (i = 0; i < sig->rawlen; i++) {
+		if (fputc(sig->rawdata[i], tmpSig) == EOF) {
+			_alpm_log(PM_LOG_ERROR, _("Error writing signature to temporary file\n"));
+			status = -1;
+			goto cleanup;
+		}
 	}

-	/* calling gpgme_check_version() returns the current version and runs
-	 * some internal library setup code */
-	version = gpgme_check_version(NULL);
-	_alpm_log(PM_LOG_DEBUG, "GPGME version: %s\n", version);
-	gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
-#ifdef LC_MESSAGES
-	gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
-#endif
-	/* NOTE:
-	 * The GPGME library installs a SIGPIPE signal handler automatically if
-	 * the default signal hander is in use. The only time we set a handler
-	 * for SIGPIPE is in dload.c, and we reset it when we are done. Given that
-	 * we do this, we can let GPGME do its automagic. However, if we install
-	 * a library-wide SIGPIPE handler, we will have to be careful.
-	 */
-
-	/* check for OpenPGP support (should be a no-brainer, but be safe) */
-	err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
-	CHECK_ERR();
-
-	/* set and check engine information */
-	err = gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, NULL,
-			alpm_option_get_signaturedir());
-	CHECK_ERR();
-	err = gpgme_get_engine_info(&enginfo);
-	CHECK_ERR();
-	_alpm_log(PM_LOG_DEBUG, "GPGME engine info: file=%s, home=%s\n",
-			enginfo->file_name, enginfo->home_dir);
-
-	init = 1;
-	return(0);
-
-error:
-	_alpm_log(PM_LOG_ERROR, _("GPGME error: %s\n"), gpgme_strerror(err));
-	RET_ERR(PM_ERR_GPGME, 1);
+	// Close the temporary file so that gpg can use it
+	fclose(tmpSig);
+	tmpSig = NULL;
+	
+	// Calls the function that checks the signature in a file
+	status = _alpm_gpg_checksig_file(pkgpath, tmpName);
+cleanup:
+	if (tmpSig != NULL) {
+		fclose(tmpSig);
+	}
+	if (tmpName != NULL) {
+		// Delete the temporary file, if it exists
+		if (access(tmpName, F_OK) != -1) {
+			remove(tmpName);
+		}
+		
+		FREE(tmpName);
+	}
+	return status;
 }

 /**
  * Check the PGP package signature for the given package file.
+ * The signature is stored in a file, which is passed to gpg.
  * @param pkgpath the full path to a package file
- * @param sig PGP signature data in raw form (already decoded)
+ * @param sigpath path to the PGP Signature file.
  * @return a int value : 0 (valid), 1 (invalid), -1 (an error occured)
  */
-int _alpm_gpgme_checksig(const char *pkgpath, const pmpgpsig_t *sig)
+int _alpm_gpg_checksig_file(const char *pkgpath, const char *sigpath)
 {
-	int ret = 0;
-	gpgme_error_t err;
-	gpgme_ctx_t ctx;
-	gpgme_data_t pkgdata, sigdata;
-	gpgme_verify_result_t result;
-	gpgme_signature_t gpgsig;
-	FILE *pkgfile = NULL, *sigfile = NULL;
-
-	ALPM_LOG_FUNC;
-
-	if(!sig || !sig->rawdata) {
-		 RET_ERR(PM_ERR_SIG_UNKNOWN, -1);
-	}
-	if(!pkgpath || access(pkgpath, R_OK) != 0) {
-		RET_ERR(PM_ERR_PKG_NOT_FOUND, -1);
+	int status = 1;
+	char *command = NULL, *line = NULL;
+	FILE *output = NULL;
+	int resultCommand = 0, statusCommand = 0, trusted = 0;
+	
+	CALLOC(command, MAX_COMMAND, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
+	
+	if (snprintf(command, MAX_COMMAND, GPG_PACMAN " --verify %s %s",
sigpath, pkgpath) >= MAX_COMMAND) {
+		_alpm_log(PM_LOG_ERROR, _("The gpg command string is too long\n"));
+		status = -1;
+		goto cleanup;
 	}
-	if(gpgme_init()) {
-		/* pm_errno was set in gpgme_init() */
-		return(-1);
+	_alpm_log(PM_LOG_DEBUG, "gpg command is: \'%s\'\n", command);
+	
+	output = popen(command, "r");
+	if (output == NULL) {
+		_alpm_log(PM_LOG_ERROR, _("Error calling gpg2 external program\n"));
+		status = -1;
+		goto cleanup;
 	}
-	err = gpgme_new(&ctx);
-	CHECK_ERR();
-
-	/* create our necessary data objects to verify the signature */
-	/* first the package itself */
-	pkgfile = fopen(pkgpath, "rb");
-	if(pkgfile == NULL) {
-		pm_errno = PM_ERR_PKG_OPEN;
-		ret = -1;
-		goto error;
-	}
-	err = gpgme_data_new_from_stream(&pkgdata, pkgfile);
-	CHECK_ERR();
-
-	/* next create data object for the signature */
-	err = gpgme_data_new_from_mem(&sigdata, (char*)sig->rawdata, sig->rawlen, 0);
-	CHECK_ERR();
-
-	/* here's where the magic happens */
-	err = gpgme_op_verify(ctx, sigdata, pkgdata, NULL);
-	CHECK_ERR();
-	result = gpgme_op_verify_result(ctx);
-		gpgsig = result->signatures;
-	if (!gpgsig || gpgsig->next) {
-		_alpm_log(PM_LOG_ERROR, _("Unexpected number of signatures\n"));
-		ret = -1;
-		goto error;
-	}
-	fprintf(stdout, "\nsummary=%x\n", gpgsig->summary);
-	fprintf(stdout, "fpr=%s\n", gpgsig->fpr);
-	fprintf(stdout, "status=%d\n", gpgsig->status);
-	fprintf(stdout, "timestamp=%lu\n", gpgsig->timestamp);
-	fprintf(stdout, "wrong_key_usage=%u\n", gpgsig->wrong_key_usage);
-	fprintf(stdout, "pka_trust=%u\n", gpgsig->pka_trust);
-	fprintf(stdout, "chain_model=%u\n", gpgsig->chain_model);
-	fprintf(stdout, "validity=%d\n", gpgsig->validity);
-	fprintf(stdout, "validity_reason=%d\n", gpgsig->validity_reason);
-	fprintf(stdout, "key=%d\n", gpgsig->pubkey_algo);
-	fprintf(stdout, "hash=%d\n", gpgsig->hash_algo);
-
-	if(gpgsig->summary & GPGME_SIGSUM_VALID) {
-		/* good signature, continue */
-		_alpm_log(PM_LOG_DEBUG, _("Package %s has a valid signature.\n"),
-				pkgpath);
-	} else if(gpgsig->summary & GPGME_SIGSUM_GREEN) {
-		/* 'green' signature, not sure what to do here */
-		_alpm_log(PM_LOG_WARNING, _("Package %s has a green signature.\n"),
-				pkgpath);
-	} else if(gpgsig->summary & GPGME_SIGSUM_KEY_MISSING) {
-		pm_errno = PM_ERR_SIG_UNKNOWN;
-		_alpm_log(PM_LOG_WARNING, _("Package %s has a signature from an
unknown key.\n"),
-				pkgpath);
-		ret = -1;
-	} else {
-		/* we'll capture everything else here */
-		pm_errno = PM_ERR_SIG_INVALID;
-		_alpm_log(PM_LOG_ERROR, _("Package %s has an invalid signature.\n"),
-				pkgpath);
-		ret = 1;
+	
+	// Read the output of the command to see if there are lines with the pattern
+	// TRUST_MARGINAL, TRUST_FULLY or TRUST_ULTIMATE. These results indicate that
+	// the level of trust of the key are at leas MARGINAL, which means that the
+	// users trusts the signer or there is a path in the web of trust that
+	// assigns at least the MARGINAL level
+	CALLOC(line, MAX_LINE, sizeof(char), RET_ERR(PM_ERR_MEMORY, NULL));
+	while (fgets(line, MAX_LINE, output)) {
+		// Debug of status messages from gpg
+		if (strncmp(line, "[GNUPG:]", (size_t)strlen("[GNUPG:]")) == 0) {
+			_alpm_log(PM_LOG_DEBUG, "%s\n", line);
+		}
+		if (strncmp(line, TRUST_MARGINAL, (size_t)strlen(TRUST_MARGINAL)) == 0 ||
+		    strncmp(line, TRUST_FULLY, (size_t)strlen(TRUST_FULLY)) == 0 ||
+		    strncmp(line, TRUST_ULTIMATE, (size_t)strlen(TRUST_ULTIMATE)) == 0) {
+			trusted = 1;
+		}
 	}
+	
+	resultCommand = pclose(output);

-error:
-	gpgme_data_release(sigdata);
-	gpgme_data_release(pkgdata);
-	gpgme_release(ctx);
-	if(sigfile) {
-		fclose(sigfile);
+	// We don't need to check the return status of the command, because the
+	// trusted variable only is 1 if the signature is valid and trusted.
+	if (trusted == 1) {
+	  status = 0;
 	}
-	if(pkgfile) {
-		fclose(pkgfile);
+cleanup:
+	if (line) {
+		FREE(line);
 	}
-	if(err != GPG_ERR_NO_ERROR) {
-		_alpm_log(PM_LOG_ERROR, _("GPGME error: %s\n"), gpgme_strerror(err));
-		RET_ERR(PM_ERR_GPGME, -1);
+	if (command) {
+		FREE(command);
 	}
-	return(ret);
+	return status;
 }

 /**
@@ -207,7 +191,7 @@ int SYMEXPORT alpm_pkg_check_pgp_signature(pmpkg_t *pkg)
 	ALPM_LOG_FUNC;
 	ASSERT(pkg != NULL, return(0));

-	return(_alpm_gpgme_checksig(alpm_pkg_get_filename(pkg),
+	return(_alpm_gpg_checksig_memory(alpm_pkg_get_filename(pkg),
 			alpm_pkg_get_pgpsig(pkg)));
 }

diff --git a/lib/libalpm/signing.h b/lib/libalpm/signing.h
index c004697..fe28a22 100644
--- a/lib/libalpm/signing.h
+++ b/lib/libalpm/signing.h
@@ -21,7 +21,8 @@

 #include "alpm.h"

-int _alpm_gpgme_checksig(const char *pkgpath, const pmpgpsig_t *sig);
+int _alpm_gpg_checksig_memory(const char *pkgpath, const pmpgpsig_t *sig);
+int _alpm_gpg_checksig_file(const char *pkgpath, const char *sigpath);

 #endif /* _ALPM_SIGNING_H */

-- 
1.7.1


More information about the pacman-dev mailing list