[pacman-dev] [PATCH] Package and database signing

Dan McGee dan at archlinux.org
Tue Jun 29 23:42:08 EDT 2010


On Tue, Jun 29, 2010 at 9:54 PM, Dan McGee <dan at archlinux.org> wrote:
> From: Denis A. Altoé Falqueto <denisfalqueto at gmail.com>
>
> The packages and database files will have the possibility of being
> digitally signed, using gpg2 as the tool to sign and verify the
> signatures.
>
> makepkg and repo-add were changed so they can receive a key to be
> used in the signing process and use gpg2 instead of gpg. The case for
> gpg2 is that it allows user agents to be used for passphrase entry.
> For example, in KDE, it will open a dialog to ask for the passphrase
> and it will be cached for some time. It is also possible with Gnome.
> In simpler environments, the passphrase will be asked everytime
> in the command line.

I think ideally we would be able to use either of the programs,
possibly even at runtime. They both have their advantages, and gnupg2
depends on something like libusb which I'm fairly certain we have no
need for. This isn't to mention the other platforms we try to support.

> repo-add will store the signature of the packages added, if it exists.
> The signature will be stored in the section %PGPSIG% of the desc file
> and will have a variable length. The reason is that different key sizes
> generate different signature sizes. So we must be prepared for any size.
>
> libalpm was changed to check the signature in the database update and
> package installation (not in -U, for now).
So you've taken a step backward from what I think I had? It has been a
while of course. :)

> Each repository in pacman.conf
> can be configured to use the signature feature according to three options:
> Always, Optional and None. Always indicates that the signature must exists
> and be valid for the repository to be updated. Optional doesn't demnds the
> signature, but if it is present, it must be valid. None doesn't bother with
> signatures at all.
>
> Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto at gmail.com>
> ---
>  configure.ac             |    4 -
>  doc/makepkg.8.txt        |    4 +
>  doc/makepkg.conf.5.txt   |    6 +-
>  doc/repo-add.8.txt       |    7 +-
>  lib/libalpm/alpm.h       |    2 +-
>  lib/libalpm/be_files.c   |   96 ++++++++++++++--
>  lib/libalpm/error.c      |    2 -
>  lib/libalpm/signing.c    |  282 ++++++++++++++++++++++------------------------
>  lib/libalpm/signing.h    |    3 +-
>  lib/libalpm/sync.c       |    2 +-
>  scripts/.gitignore       |    1 +
>  scripts/Makefile.am      |    3 +
>  scripts/makepkg.sh.in    |   32 ++++--
>  scripts/pacman-key.sh.in |  278 +++++++++++++++++++++++++++++++++++++++++++++
>  scripts/repo-add.sh.in   |   63 ++++++++---
>  15 files changed, 588 insertions(+), 197 deletions(-)
>  create mode 100644 scripts/pacman-key.sh.in
>
> diff --git a/configure.ac b/configure.ac
> index b6ce68b..008975b 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -154,10 +154,6 @@ else
>  fi
>  AM_CONDITIONAL(INTERNAL_DOWNLOAD, test "x$internaldownload" = "xyes")
>
> -# Check for gpgme
> -AC_CHECK_LIB([gpgme], [gpgme_check_version], ,
> -       AC_MSG_ERROR([gpgme is needed to compile pacman!]))
> -
>  # Checks for header files.
>  AC_CHECK_HEADERS([fcntl.h glob.h libintl.h limits.h locale.h string.h strings.h sys/ioctl.h sys/param.h sys/statvfs.h sys/syslimits.h sys/time.h syslog.h wchar.h])
>
> diff --git a/doc/makepkg.8.txt b/doc/makepkg.8.txt
> index a2fdb3f..4d8f26b 100644
> --- a/doc/makepkg.8.txt
> +++ b/doc/makepkg.8.txt
> @@ -161,6 +161,10 @@ Options
>        (Passed to pacman) Prevent pacman from displaying a progress bar;
>        useful if you are redirecting makepkg output to file.
>
> +*\--signwithkey*::
> +       Select a specific key to be used to sign the package. If absent,
> +       the default from the keyring key will be used.
> +
>
>  Additional Features
>  -------------------
> diff --git a/doc/makepkg.conf.5.txt b/doc/makepkg.conf.5.txt
> index a565bd6..f82bc19 100644
> --- a/doc/makepkg.conf.5.txt
> +++ b/doc/makepkg.conf.5.txt
> @@ -94,9 +94,9 @@ Options
>                PKGBUILD options array.
>
>        *sign*;;
> -               Generate a PGP signature file using GnuPG. This will execute `gpg
> -               --detach-sign --use-agent` on the built package to generate a detached
> -               signature file, using the GPG agent if it is available. The signature
> +               Generate a PGP signature file using GnuPG. This will execute `gpg2
> +               --detach-sign` on the built package to generate a detached signature
> +               file, using the GPG agent if it is available. The signature
>                file will be the entire filename of the package with a ``.sig''
>                extension.
>
> diff --git a/doc/repo-add.8.txt b/doc/repo-add.8.txt
> index e6cc940..6b8c0ef 100644
> --- a/doc/repo-add.8.txt
> +++ b/doc/repo-add.8.txt
> @@ -10,9 +10,9 @@ repo-add - package database maintenance utility
>
>  Synopsis
>  --------
> -repo-add [-q] <path-to-db> <package1> [<package2> ...]
> +repo-add [-q] [-s [-k key]] <path-to-db> <package1> [<package2> ...]
>
> -repo-remove [-q] <path-to-db> <packagename> [<packagename2> ...]
> +repo-remove [-q] [-s [-k key]] <path-to-db> <packagename> [<packagename2> ...]
>
>
>  Description
> @@ -40,6 +40,9 @@ Options
>        signature file, using the GPG agent if it is available. The signature file
>        will be the entire filename of the database with a ``.sig'' extension.
>
> +*-k key*::
> +       Select a specific key to be used for the signing of the database file.
> +       If absent, the default key from the default keyring will be used.
We should be uniform with our options in makepkg/repo-add, at least
the long option should also be available here I would think.

>  See Also
>  --------
> diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h
> index 2ce049d..7eeb734 100644
> --- a/lib/libalpm/alpm.h
> +++ b/lib/libalpm/alpm.h
> @@ -512,6 +512,7 @@ enum _pmerrno_t {
>        PM_ERR_DB_NOT_FOUND,
>        PM_ERR_DB_WRITE,
>        PM_ERR_DB_REMOVE,
> +       PM_ERR_DB_SIG,
>        /* Servers */
>        PM_ERR_SERVER_BAD_URL,
>        PM_ERR_SERVER_NONE,
> @@ -550,7 +551,6 @@ enum _pmerrno_t {
>        /* External library errors */
>        PM_ERR_LIBARCHIVE,
>        PM_ERR_LIBFETCH,
> -       PM_ERR_GPGME,
>        PM_ERR_EXTERNAL_DOWNLOAD
>  };
>
> diff --git a/lib/libalpm/be_files.c b/lib/libalpm/be_files.c
> index 1235165..9751b4e 100644
> --- a/lib/libalpm/be_files.c
> +++ b/lib/libalpm/be_files.c
> @@ -49,7 +49,9 @@
>  #include "delta.h"
>  #include "deps.h"
>  #include "dload.h"
> +#include "signing.h"
>
> +#define DBSIGEXT ".sig"
This seems silly. It is not going to change.

>
>  static int checkdbdir(pmdb_t *db)
>  {
> @@ -208,7 +210,8 @@ static int remove_olddir(const char *syncdbpath, alpm_list_t *dirlist)
>  */
>  int SYMEXPORT alpm_db_update(int force, pmdb_t *db)
>  {
> -       char *dbfile, *dbfilepath;
> +       char *dbfile = NULL, *dbfilepath = NULL, *dbsigfile = NULL,
> +               *fulldbfile = NULL, *fulldbsigfile = NULL;
Some of these vars should be defined in the scope where they are used,
not up here.

>        const char *dbpath, *syncdbpath;
>        alpm_list_t *newdirlist = NULL, *olddirlist = NULL;
>        alpm_list_t *onlynew = NULL, *onlyold = NULL;
> @@ -238,17 +241,62 @@ int SYMEXPORT alpm_db_update(int force, pmdb_t *db)
>        dbpath = alpm_option_get_dbpath();
>
>        ret = _alpm_download_single_file(dbfile, db->servers, dbpath, force);
> -       free(dbfile);
> -
>        if(ret == 1) {
>                /* files match, do nothing */
>                pm_errno = 0;
> +               free(dbfile);
>                return(1);
>        } else if(ret == -1) {
>                /* pm_errno was set by the download code */
>                _alpm_log(PM_LOG_DEBUG, "failed to sync db: %s\n", alpm_strerrorlast());
> +               free(dbfile);
Why was this moved? I'm not following.
>                return(-1);
>        }
> +
> +       /* Check the signature of the database, if it is marked as signed */
> +       if(db->pgp_verify != PM_PGP_VERIFY_NEVER) {
<<< Define vars here

> +               /* Assemble the signature file name */
> +               len = strlen(dbfile) + strlen(DBSIGEXT) + 1;
> +               MALLOC(dbsigfile, len, RET_ERR(PM_ERR_MEMORY, -1));
> +               sprintf(dbsigfile, "%s" DBSIGEXT, dbfile);
> +
> +               /* Try to download the signature file */
> +               ret = _alpm_download_single_file(dbsigfile, db->servers, dbpath, force);
> +               if (ret == -1 && db->pgp_verify == PM_PGP_VERIFY_ALWAYS) {
> +                       _alpm_log(PM_LOG_DEBUG, "failed to download signature for db: %s\n", alpm_strerrorlast());
> +                       free(dbfile);
> +                       free(dbsigfile);
> +                       return (-1);
> +               }
> +               else if (ret != -1) {
<<< Define variables here

> +                       /* Assemble the full db and signature file names */
> +                       MALLOC(fulldbfile, strlen(dbpath) + strlen(dbfile) + 2, RET_ERR(PM_ERR_MEMORY, -1));
> +                       MALLOC(fulldbsigfile, strlen(dbpath) + strlen(dbsigfile) + 2, RET_ERR(PM_ERR_MEMORY, -1));
> +                       sprintf(fulldbfile, "%s/%s", dbpath, dbfile);
> +                       sprintf(fulldbsigfile, "%s/%s", dbpath, dbsigfile);
> +
> +                       /* Check the signature */
> +                       int ret = _alpm_gpg_checksig_file(fulldbfile, fulldbsigfile);
> +                       _alpm_log(PM_LOG_DEBUG, "return from _alpm_gpg_checksig_file = %d\n", ret);
> +
> +                       FREE(fulldbfile);
> +                       FREE(fulldbsigfile);
> +
> +                       /* VerifSig = Always -> we will only accept 0 as a correct value
> +                        * (missing or invalid signatures are errors)
> +                        * VerifSig = Optional -> we will accept 0 or -1 as correct values
> +                        * (missing signature is ok, but if it present, it must be valid) */
> +                       if((db->pgp_verify == PM_PGP_VERIFY_ALWAYS && ret != 0) ||
> +                                       (db->pgp_verify == PM_PGP_VERIFY_OPTIONAL && ret == 1)) {
> +                               _alpm_log(PM_LOG_ERROR, "the signature doesn't match the repository database.\n");
> +                               free(dbfile);
> +                               free(dbsigfile);
> +                               RET_ERR(PM_ERR_DB_SIG, -1);
> +                       }
> +               }
> +               FREE(dbsigfile);
> +       }
> +       FREE(dbfile);
>
>        syncdbpath = _alpm_db_path(db);
>
> @@ -256,7 +304,7 @@ int SYMEXPORT alpm_db_update(int force, pmdb_t *db)
>        len = strlen(dbpath) + strlen(db->treename) + strlen(DBEXT) + 1;
>        MALLOC(dbfilepath, len, RET_ERR(PM_ERR_MEMORY, -1));
>        sprintf(dbfilepath, "%s%s" DBEXT, dbpath, db->treename);
> -
> +
Whitespace changes or what is this? Try to keep the patch clean.

>        if(force) {
>                /* if forcing update, remove the old dir and extract the db */
>                if(_alpm_rmrf(syncdbpath) != 0) {
> @@ -291,6 +339,10 @@ int SYMEXPORT alpm_db_update(int force, pmdb_t *db)
>        ret = _alpm_unpack(dbfilepath, syncdbpath, onlynew, 0);
>
>  cleanup:
> +       if (dbsigfile != NULL)
> +               FREE(dbsigfile);
> +       if (dbfile != NULL)
> +               FREE(dbfile);
This shouldn't be here if you cleaned up above correctly, so make sure
that happens. Moving the vars in the correct scope will make that a
lot clearer.

>        FREELIST(newdirlist);
>        FREELIST(olddirlist);
>        alpm_list_free(onlynew);
> @@ -437,7 +489,8 @@ int _alpm_db_read(pmdb_t *db, pmpkg_t *info, pmdbinfrq_t inforeq)
>        char line[513];
>        int     sline = sizeof(line)-1;
>        char *pkgpath = NULL;
> -
> +       alpm_list_t *pgpsig_lines = NULL;
> +
>        ALPM_LOG_FUNC;
>
>        if(db == NULL) {
> @@ -610,11 +663,33 @@ int _alpm_db_read(pmdb_t *db, pmpkg_t *info, pmdbinfrq_t inforeq)
>                                STRDUP(info->md5sum, _alpm_strtrim(line), goto error);
>                        } else if(strcmp(line, "%PGPSIG%") == 0) {
>                                /* PGPSIG tag only appears in sync repositories,
> -                                * not the local one. */
> -                               if(fgets(line, 512, fp) == NULL) {
> -                                       goto error;
> +                                * not the local one. The size must not be fixed,
> +                                * because the key used will affect the final size */
> +                               pgpsig_lines = NULL;
> +                               int len = 0;
> +                               /* Create a list of strings to store the signature for now */
> +                               while(fgets(line, sline, fp) && strlen(_alpm_strtrim(line))) {
> +                                       char *linedup;
> +                                       STRDUP(linedup, _alpm_strtrim(line), goto error);
> +                                       pgpsig_lines = alpm_list_add(pgpsig_lines, linedup);
> +                                       len += strlen(linedup);
>                                }
> -                               STRDUP(info->pgpsig.encdata, _alpm_strtrim(line), goto error);
> +
> +                               MALLOC(info->pgpsig.encdata, (size_t)(len + 1), goto error);
> +                               info->pgpsig.encdata[0] = '\0';
> +
> +                               /* Assemble the signature from the list of strings */
> +                               while (pgpsig_lines != NULL) {
> +                                       alpm_list_t *pgpsig_line = pgpsig_lines;
> +                                       pgpsig_lines = pgpsig_lines->next;
> +
> +                                       strcat(info->pgpsig.encdata, (char *)pgpsig_line->data);
> +
> +                                       /* Free the current node, as it is not needed anymore */
> +                                       free(pgpsig_line->data);
> +                                       free(pgpsig_line);
> +                               }
> +                               pgpsig_lines = NULL;
Once again, variable scope please.

When would a signature ever be longer than our current maximum line
length of 512? I don't remember seeing one anywhere near that. I'd
rather just bump all of this up to a slightly bigger buffer than all
this code.

>                        } else if(strcmp(line, "%REPLACES%") == 0) {
>                                while(fgets(line, sline, fp) && strlen(_alpm_strtrim(line))) {
>                                        char *linedup;
> @@ -731,6 +806,9 @@ int _alpm_db_read(pmdb_t *db, pmpkg_t *info, pmdbinfrq_t inforeq)
>        return(0);
>
>  error:
> +       if (pgpsig_lines) {
> +               FREELIST(pgpsig_lines);
> +       }
Scoped vars++

>        free(pkgpath);
>        if(fp) {
>                fclose(fp);
> diff --git a/lib/libalpm/error.c b/lib/libalpm/error.c
> index 907a9f2..1fccc46 100644
> --- a/lib/libalpm/error.c
> +++ b/lib/libalpm/error.c
> @@ -158,8 +158,6 @@ const char SYMEXPORT *alpm_strerror(int err)
>                        /* obviously shouldn't get here... */
>                        return _("download library error");
>  #endif
> -               case PM_ERR_GPGME:
> -                       return _("gpgme error");
You took this out...but failed to add yours in.

>                case PM_ERR_EXTERNAL_DOWNLOAD:
>                        return _("error invoking external downloader");
>                /* Unknown error! */
> 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"
*Never* ever remove this header.

> -
> -#include <stdlib.h>
>  #include <stdio.h>
> -#include <string.h>
> -#include <locale.h> /* setlocale() */
I can already see trouble ahead...

> -#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
git grep PATH_MAX instead of inventing our own constants

> +#define MAX_LINE 1024
> +#define TRUST_MARGINAL "[GNUPG:] TRUST_MARGINAL"
> +#define TRUST_FULLY "[GNUPG:] TRUST_FULLY"
> +#define TRUST_ULTIMATE "[GNUPG:] TRUST_ULTIMATE"
Oh boy.

> +
> +// 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"
Please see how we do something like "conffile" in
src/pacman/Makefile.am, and of course we would want all of this to
flow through from the frontend.

> +
> +// 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"
This does not make a good case to me for moving away from gpgme or
some library. Holy smokes.

> -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;
*Please* follow variable naming conventions you've already encountered
elsewhere. This is not even close. tmp_name, tmp_sig.

> +
> +       // 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;
> +               }
This is all silly.

$ cat valgrind.supp.sig | gpg2 --verify - valgrind.supp
gpg: Signature made Tue 29 Jun 2010 10:19:40 PM CDT using DSA key ID F53A76ED
gpg: Good signature from "Dan McGee <dpmcgee at gmail.com>"
gpg:                 aka "Dan McGee (Developer) <dan at archlinux.org>"
gpg:                 aka "Dan McGee (Jabber) <toofishes at toofishes.net>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: A5CA 9D55 15DC 2CA7 3DF7  48CA 5C2E 46A0 F53A 76ED

We can read the data from stdin and skip any temp file.

>        }
>
> -       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;
>  }

I've stopped reading about here. I can't make it though a 800 lines
changed patch in one go. You have to try to split these up into
logical chunks or it is just going to be too tough to review (and on
that note, a heck of a lot more work for you to go back and make
adjustments).

Once again, I know my comments come across as harsh, and I appreciate
the work you put in, but especially with something sensitive like
package signing I'm not going to accept anything that isn't
super-solid.


>  /**
>  * 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 */
>
> diff --git a/lib/libalpm/sync.c b/lib/libalpm/sync.c
> index 3994d5d..aa1fffe 100644
> --- a/lib/libalpm/sync.c
> +++ b/lib/libalpm/sync.c
> @@ -938,7 +938,7 @@ int _alpm_sync_commit(pmtrans_t *trans, pmdb_t *db_local, alpm_list_t **data)
>                pmdb_t *sdb = alpm_pkg_get_db(spkg);
>
>                if(sdb->pgp_verify != PM_PGP_VERIFY_NEVER) {
> -                       int ret = _alpm_gpgme_checksig(filepath, pgpsig);
> +                       int ret = _alpm_gpg_checksig_memory(filepath, pgpsig);
>                        if((sdb->pgp_verify == PM_PGP_VERIFY_ALWAYS && ret != 0) ||
>                                        (sdb->pgp_verify == PM_PGP_VERIFY_OPTIONAL && ret == 1)) {
>                                errors++;
> diff --git a/scripts/.gitignore b/scripts/.gitignore
> index eafc493..1c662de 100644
> --- a/scripts/.gitignore
> +++ b/scripts/.gitignore
> @@ -4,3 +4,4 @@ rankmirrors
>  repo-add
>  repo-remove
>  pkgdelta
> +pacman-key
> diff --git a/scripts/Makefile.am b/scripts/Makefile.am
> index 330acb9..d4f96de 100644
> --- a/scripts/Makefile.am
> +++ b/scripts/Makefile.am
> @@ -7,6 +7,7 @@ bin_SCRIPTS = \
>
>  OURSCRIPTS = \
>        makepkg \
> +       pacman-key \
>        pacman-optimize \
>        pkgdelta \
>        rankmirrors \
> @@ -14,6 +15,7 @@ OURSCRIPTS = \
>
>  EXTRA_DIST = \
>        makepkg.sh.in \
> +       pacman-key.sh.in \
>        pacman-optimize.sh.in \
>        pkgdelta.sh.in \
>        rankmirrors.sh.in \
> @@ -60,6 +62,7 @@ $(OURSCRIPTS): Makefile
>        @mv $@.tmp $@
>
>  makepkg: $(srcdir)/makepkg.sh.in
> +pacman-key: ${srcdir}/pacman-key.sh.in
>  pacman-optimize: $(srcdir)/pacman-optimize.sh.in
>  pkgdelta: $(srcdir)/pkgdelta.sh.in
>  rankmirrors: $(srcdir)/rankmirrors.sh.in
> diff --git a/scripts/makepkg.sh.in b/scripts/makepkg.sh.in
> index 223bf12..cb25312 100644
> --- a/scripts/makepkg.sh.in
> +++ b/scripts/makepkg.sh.in
> @@ -28,7 +28,7 @@
>  # makepkg uses quite a few external programs during its execution. You
>  # need to have at least the following installed for makepkg to function:
>  #   bsdtar (libarchive), bzip2, coreutils, fakeroot, find (findutils),
> -#   gettext, grep, gzip, openssl, sed, tput (ncurses), xz
> +#   gettext, grep, gzip, openssl, sed, tput (ncurses), xz, gpg
gpg? or gpg2

>
>  # gettext initialization
>  export TEXTDOMAIN='pacman'
> @@ -43,6 +43,8 @@ BUILDSCRIPT='@BUILDSCRIPT@'
>  startdir="$PWD"
>  srcdir="$startdir/src"
>  pkgdir="$startdir/pkg"
> +GPG="gpg2"
> +SIG_EXT=".sig"
SIG_EXT is silly. Please remove it from all the scripts, this will not
change and it just adds 5 extra characters per use for not a whole lot
of reason.

>
>  packaging_options=('strip' 'docs' 'libtool' 'emptydirs' 'zipman' 'purge')
>  other_options=('ccache' 'distcc' 'makeflags' 'force')
> @@ -74,6 +76,7 @@ BUILDFUNC=0
>  PKGFUNC=0
>  SPLITPKG=0
>  PKGLIST=""
> +SIGNKEY=""
>
>  # Forces the pkgver of the current PKGBUILD. Used by the fakeroot call
>  # when dealing with svn/cvs/etc PKGBUILDs.
> @@ -1028,7 +1031,7 @@ create_package() {
>        local ret=0
>
>        [[ -f $pkg_file ]] && rm -f "$pkg_file"
> -       [[ -f $pkg_file.sig ]] && rm -f "$pkg_file.sig"
> +       [[ -f ${pkg_file}${SIG_EXT} ]] && rm -f "${pkg_file}${SIG_EXT}"
>
>        # when fileglobbing, we want * in an empty directory to expand to
>        # the null string rather than itself
> @@ -1055,7 +1058,7 @@ create_package() {
>        if (( ! ret )) && [[ "$PKGDEST" != "${startdir}" ]]; then
>                ln -sf "${pkg_file}" "${pkg_file/$PKGDEST/$startdir}"
>                ret=$?
> -               [[ -f $pkg_file.sig ]] && ln -sf "$pkg_file.sig" "${pkg_file/$PKGDEST/$startdir}.sig"
> +               [[ -f ${pkg_file}${SIG_EXT} ]] && ln -sf "${pkg_file}${SIG_EXT}" "${pkg_file/$PKGDEST/$startdir}${SIG_EXT}"
>        fi
>
>        if (( ret )); then
> @@ -1070,13 +1073,24 @@ create_signature() {
>        local ret=0
>        local filename="$1"
>        msg "$(gettext "Signing package...")"
> -       if [ ! $(type -p "gpg") ]; then
> -               error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
> +       if [[ ! $(type -p "${GPG}") ]]; then
> +               error "$(gettext "Cannot find the ${GPG} binary! Is gnupg installed?")"
>                exit 1 # $E_MISSING_PROGRAM
>        fi
> -       gpg --detach-sign --use-agent "$filename" || ret=$?
> +
> +       # Check if SIGNKEY is valid.
> +       local SIGNWITHKEY=""
> +       if [[ "${SIGNKEY}" ]]; then
> +               if ! ${GPG} --list-key "${SIGNKEY}" 1>/dev/null 2>&1; then
> +                       error "$(gettext "The key ${SIGNKEY} doesn\'t exist.")"
> +                       exit 1
> +               fi
> +               SIGNWITHKEY="-u ${SIGNKEY}"
> +       fi
> +       # The signature will be generated directly in ascii-friendly format
> +       ${GPG} --detach-sign ${SIGNWITHKEY} "$filename" || ret=$?
>        if (( ! ret )); then
> -               msg2 "$(gettext "Created signature file %s.")" "$filename.sig"
> +               msg2 "$(gettext "Created signature file %s.")" "${filename}${SIG_EXT}"
>        else
>                warning "$(gettext "Failed to sign package file.")"
>        fi
> @@ -1522,6 +1536,7 @@ usage() {
>        echo "$(gettext "  --pkg <list>     Only build listed packages from a split package")"
>        echo "$(gettext "  --skipinteg      Do not fail when integrity checks are missing")"
>        echo "$(gettext "  --source         Generate a source-only tarball without downloaded sources")"
> +       echo "$(gettext "  --signwithkey    Selects an specific key to use for signing")"
>        echo
>        echo "$(gettext "These options can be passed to pacman:")"
>        echo
> @@ -1557,7 +1572,7 @@ OPT_SHORT="AcCdefFghiLmop:rRsV"
>  OPT_LONG="allsource,asroot,ignorearch,clean,cleancache,nodeps"
>  OPT_LONG="$OPT_LONG,noextract,force,forcever:,geninteg,help,holdver"
>  OPT_LONG="$OPT_LONG,install,log,nocolor,nobuild,pkg:,rmdeps,repackage,skipinteg"
> -OPT_LONG="$OPT_LONG,source,syncdeps,version,config:"
> +OPT_LONG="$OPT_LONG,source,syncdeps,version,config:,signwithkey"
>  # Pacman Options
>  OPT_LONG="$OPT_LONG,noconfirm,noprogressbar"
>  OPT_TEMP="$(parse_options $OPT_SHORT $OPT_LONG "$@" || echo 'PARSE_OPTIONS FAILED')"
> @@ -1600,6 +1615,7 @@ while true; do
>                --skipinteg)      SKIPINTEG=1 ;;
>                --source)         SOURCEONLY=1 ;;
>                -s|--syncdeps)    DEP_BIN=1 ;;
> +               --signwithkey)    shift; SIGNKEY=$1 ;;
>
>                -h|--help)        usage; exit 0 ;; # E_OK
>                -V|--version)     version; exit 0 ;; # E_OK
> diff --git a/scripts/pacman-key.sh.in b/scripts/pacman-key.sh.in
> new file mode 100644
> index 0000000..a33c684
> --- /dev/null
> +++ b/scripts/pacman-key.sh.in
> @@ -0,0 +1,278 @@
> +#!/bin/bash -e
> +#
> +#   pacman-key - manages pacman's keyring
> +#   @configure_input@
> +#
> +#   Copyright (c) 2010 - Denis A. Altoé Falqueto <denisfalqueto at gmail.com>
> +#
> +#   This program is free software; you can redistribute it and/or modify
> +#   it under the terms of the GNU General Public License as published by
> +#   the Free Software Foundation; either version 2 of the License, or
> +#   (at your option) any later version.
> +#
> +#   This program 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 General Public License for more details.
> +#
> +#   You should have received a copy of the GNU General Public License
> +#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +#
> +
> +# gettext initialization
> +export TEXTDOMAIN='pacman'
> +export TEXTDOMAINDIR='@localedir@'
> +
> +# Based on apt-key, from Debian
> +# Author: Denis A. Altoé Falqueto <denisfalqueto at gmail dot com>
> +
> +PACMAN_KEY_VERSION="0.1"
Don't version official scripts different than the entire package,
please- follow the trend of the rest of the scripts.

> +
> +# According to apt-key, gpg doesn't like to be called without a secret keyring.
> +# We will not really need one, because pacman will not sign packages, just verify
> +# their integrities.
> +PACMAN_KEYRING_DIR="/etc/pacman.d/gnupg"
> +PACMAN_SECRET="secret.gpg"
> +PACMAN_TRUSTDB="trustdb.gpg"
> +PACMAN_KEYRING="pacman.gpg"
> +PACMAN_SHARE_DIR="/usr/share/pacman"
None of these directories should be hardcoded. Look at the other scripts.

> +
> +# Default parameters for the command gpg. Some more will be added when needed
> +GPG_PROGRAM="gpg2"
> +GPG="${GPG_PROGRAM} --ignore-time-conflict --no-options --no-default-keyring"
> +GPG_PACMAN="${GPG} --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}"
> +SIG_EXT=".sig"
> +
> +# Read-only keyring with keys to be added to the keyring
> +ADDED_KEYS="${PACMAN_SHARE_DIR}/addedkeys.gpg"
> +
> +# Read-only keyring with keys removed from the keyring. They need to be removed before
> +# the keys from the added keyring be really imported
> +REMOVED_KEYS="${PACMAN_SHARE_DIR}/removedkeys.gpg"
> +
> +usage() {
> +    echo "pacman-key - Pacman's keyring management utility"
> +    echo "Usage: $(basename $0) command [arguments]"
> +    echo
> +    echo "Manage pacman's list of trusted keys"
> +    echo
> +    echo "  pacman-key add <file> ...                  - add the key contained in <file> ('-' for stdin)"
> +    echo "  pacman-key del <keyid> ...                 - remove the key <keyid>"
> +    echo "  pacman-key export <keyid> ...              - output the key <keyid>"
> +    echo "  pacman-key exportall                       - output all trusted keys"
> +    echo "  pacman-key receive <keyserver> <keyid> ... - fetch the keyids from the specified keyserver URL"
> +    echo "  pacman-key trust <keyid> ...               - set the truslevel of the given key"
> +    echo "  pacman-key updatedb                        - update the trustdb of pacman"
> +    echo "  pacman-key reload                          - reloads the keys from the keyring package"
> +    echo "  pacman-key list                            - list keys"
> +    echo "  pacman-key finger <keyid> ...              - list fingerprints"
> +    echo "  pacman-key adv <params>                    - pass advanced options to gpg"
> +    echo "  pacman-key help                            - displays this message"
> +    echo "  pacman-key version                         - displays the current version"
> +    echo
> +    echo "If no specific keyring file is given the command applies to the default keyring."
> +}
> +
> +prepare_homedir() {
> +    if [[ ! -d ${PACMAN_KEYRING_DIR} ]] ; then
> +        mkdir -p "${PACMAN_KEYRING_DIR}"
> +        [[ ! -f "${PACMAN_KEYRING_DIR}/${PACMAN_SECRET}" ]] && touch "${PACMAN_KEYRING_DIR}/${PACMAN_SECRET}"
> +        [[ ! -f "${PACMAN_KEYRING_DIR}/${PACMAN_KEYRING}" ]] && touch "${PACMAN_KEYRING_DIR}/${PACMAN_KEYRING}"
> +        chmod 700 "${PACMAN_KEYRING_DIR}"
> +        chmod 600 "${PACMAN_KEYRING_DIR}"/*
> +    fi
> +}
> +
> +add_key() {
> +    ${GPG_PACMAN} --quiet --batch --import "$1"
> +}
> +
> +remove_key() {
> +    ${GPG_PACMAN} --quiet --batch --delete-key --yes "$1"
> +}
> +
> +update_trustdb() {
> +    ${GPG_PACMAN} --batch --check-trustdb
> +}
> +
> +list_sigs() {
> +    ${GPG_PACMAN} --batch --list-sigs
> +}
> +
> +list_fingerprints() {
> +    ${GPG_PACMAN} --batch --fingerprint $*
> +}
> +
> +export_key() {
> +    ${GPG_PACMAN} --armor --export "$1"
> +}
> +
> +export_all() {
> +    ${GPG_PACMAN} --armor --export
> +}
> +
> +trust_key() {
> +    # Verify if the key exists in pacman's keyring
> +    if ${GPG_PACMAN} --list-key "$1" > /dev/null 2>&1 ; then
> +      ${GPG_PACMAN} --fingerprint "$1"
> +      ${GPG_PACMAN} --edit-key "$1"
> +    else
> +      echo "The key identified by $1 doesn't exist"
> +      exit 1
> +    fi
> +}
> +
> +reload_keyring() {
> +    # Verify the signature of removed keys file
> +    if [[ -f ${REMOVED_KEYS} ]] && ! ${GPG_PACMAN} --quiet --verify ${REMOVED_KEYS}${SIG_EXT} ; then
> +        echo "The signature of file ${REMOVED_KEYS} is not valid."
> +        exit 1
> +    fi
> +
> +    # Verify the signature of the added keys file
> +    if [[ -f ${ADDED_KEYS} ]] && ! ${GPG_PACMAN} --quiet --verify ${ADDED_KEYS}${SIG_EXT} ; then
> +        echo "The signature of file ${ADDED_KEYS} is not valid."
> +        exit 1
> +    fi
> +
> +    # Remove the keys from REMOVED_KEYS keyring
> +    [[ -r ${REMOVED_KEYS} ]] && cat "${REMOVED_KEYS}" | while read key ; do
> +        ${GPG_PACMAN} --quiet --batch --yes --delete-keys ${key}
> +    done
> +
> +    # Add keys from the current set of keys from pacman-keyring package. The web of trust will
> +    # be updated automatically.
> +    if [[ -r ${ADDED_KEYS} ]] ; then
> +       add_keys=$(${GPG} --keyring ${ADDED_KEYS} --with-colons --list-keys | grep ^pub | cut -d: -f5)
> +       for add_key in $add_keys; do
> +            echo "Chave $add_key"
> +           ${GPG} --quiet --batch --keyring $ADDED_KEYS --export $add_key | ${GPG_PACMAN} --import
> +           ADDED=1
> +       done
> +    fi
> +
> +    # Update trustdb, just to be sure
> +    update_trustdb
> +}
> +
> +receive() {
> +    keyserver="$1"
> +    shift
> +    ${GPG_PACMAN} --keyserver ${keyserver} $*
> +}
> +
> +# PROGRAM START
> +
> +if ! type gettext &>/dev/null; then
> +       gettext() {
> +               echo "$@"
> +       }
> +fi
> +
> +command="$1"
> +if [[ -z "$command" ]]; then
> +    usage
> +    exit 1
> +fi
> +shift
> +
> +if [[ "$command" != "version" && "$command" != "help" ]] && ! which "${GPG_PROGRAM}" >/dev/null 2>&1; then
> +    echo >&2 "Warning: gnupg does not seem to be installed."
> +    echo >&2 "Warning: pacman-key requires gnupg for most operations."
> +    echo >&2
> +fi
> +
> +prepare_homedir
> +
> +case "$command" in
> +    add)
> +        if (( $# == 0 )) ; then
> +            echo "You need to specify at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        while (( $# > 0 )) ; do
> +          add_key $1
> +          shift
> +        done
> +        ;;
> +    del|rm|remove)
> +        if (( $# == 0 )) ; then
> +            echo "You need to specify at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        while (( $# > 0 )) ; do
> +            remove_key $1
> +            shift
> +        done
> +        ;;
> +    updatedb)
> +        update_trustdb
> +       ;;
> +    reload)
> +        reload_keyring
> +        ;;
> +    list)
> +        list_sigs
> +        ;;
> +    finger*)
> +        if (( $# == 0 )) ; then
> +            echo "You need to specify at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        list_fingerprints $*
> +        ;;
> +    export)
> +        if (( $# == 0 )) ; then
> +            echo "You need to specify at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        while (( $# > 0 )) ; do
> +            export_key $1
> +            shift
> +        done
> +        ;;
> +    exportall)
> +        export_all
> +        ;;
> +    receive)
> +        if (( $# < 2 )) ; then
> +            echo "You need to specify the keyserver and at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        receive $*
> +        ;;
> +    trust)
> +        if (( $# == 0 )) ; then
> +            echo "You need to specify at least one key identifier"
> +            usage
> +            exit 1
> +        fi
> +        while (( $# > 0 )) ; do
> +            trust_key $1
> +            shift
> +        done
> +        ;;
> +    adv*)
> +        echo "Executing: ${GPG_PACMAN} $*"
> +        ${GPG_PACMAN} $* || ret=$?
> +       exit $ret
> +        ;;
> +    --help)
> +        usage
> +        ;;
> +    --version)
> +        echo "pacman-key v${PACMAN_KEY_VERSION}"
> +        echo " This program can be freely distributed under the GPL v2"
> +        ;;
> +    *)
> +        usage
> +        exit 1
> +        ;;
> +esac
> diff --git a/scripts/repo-add.sh.in b/scripts/repo-add.sh.in
> index 9b04be9..ad9b524 100644
> --- a/scripts/repo-add.sh.in
> +++ b/scripts/repo-add.sh.in
> @@ -26,6 +26,8 @@ export TEXTDOMAINDIR='@localedir@'
>
>  myver='@PACKAGE_VERSION@'
>  confdir='@sysconfdir@'
> +GPG="gpg2"
> +SIG_EXT=".sig"
>
>  QUIET=0
>  SIGN=0
> @@ -62,8 +64,8 @@ error() {
>  # print usage instructions
>  usage() {
>        printf "repo-add, repo-remove (pacman) %s\n\n" "$myver"
> -       printf "$(gettext "Usage: repo-add [-q] [-s] [-v] <path-to-db> <package|delta> ...\n")"
> -       printf "$(gettext "Usage: repo-remove [-q] <path-to-db> <packagename|delta> ...\n\n")"
> +       printf "$(gettext "Usage: repo-add [-q] [-s [-k key]] [-v] <path-to-db> <package|delta> ...\n")"
> +       printf "$(gettext "Usage: repo-remove [-q] [-s [-k key]] <path-to-db> <packagename|delta> ...\n\n")"
>        printf "$(gettext "\
>  repo-add will update a package database by reading a package file.\n\
>  Multiple packages to add can be specified on the command line.\n\n")"
> @@ -185,13 +187,24 @@ create_signature() {
>        local dbfile="$1"
>        local ret=0
>        msg "$(gettext "Signing database...")"
> -       if [ ! $(type -p "gpg") ]; then
> -               error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
> +       if [ ! $(type -p "${GPG}") ]; then
> +               error "$(gettext "Cannot find the ${GPG} binary! Is gnupg installed?")"
>                exit 1 # $E_MISSING_PROGRAM
>        fi
> -       gpg --detach-sign --use-agent "$dbfile" || ret=$?
> +
> +       # Check if SIGNKEY is valid.
> +       local SIGNWITHKEY=""
> +       if [[ "${SIGNKEY}" ]]; then
> +               if ! "${GPG}" --list-key "${SIGNKEY}" 1>/dev/null 2>&1; then
> +                       error "$(gettext "The key ${SIGNKEY} doesnn't exist.")"
> +                       exit 1
> +               fi
> +               SIGNWITHKEY="-u ${SIGNKEY}"
> +       fi
> +       echo "${GPG} --detach-sign ${SIGNWITHKEY} $dbfile"
> +       ${GPG} --detach-sign ${SIGNWITHKEY} "$dbfile" || ret=$?
>        if (( ! ret )); then
> -               msg2 "$(gettext "Created signature file %s.")" "$dbfile.sig"
> +               msg2 "$(gettext "Created signature file %s.")" "${dbfile}${SIG_EXT}"
>        else
>                warning "$(gettext "Failed to sign package database.")"
>        fi
> @@ -203,15 +216,15 @@ verify_signature() {
>        local dbfile="$1"
>        local ret=0
>        msg "$(gettext "Verifying database signature...")"
> -       if [ ! $(type -p "gpg") ]; then
> -               error "$(gettext "Cannot find the gpg binary! Is gnupg installed?")"
> +       if [ ! $(type -p "${GPG}") ]; then
> +               error "$(gettext "Cannot find the ${GPG} binary! Is gnupg installed?")"
>                exit 1 # $E_MISSING_PROGRAM
>        fi
> -       if [[ ! -f $dbfile.sig ]]; then
> +       if [[ ! -f ${dbfile}${SIG_EXT} ]]; then
>                warning "$(gettext "No existing signature found, skipping verification.")"
>                return
>        fi
> -       gpg --verify "$dbfile.sig" || ret=$?
> +       ${GPG} --verify "${dbfile}${SIG_EXT}" || ret=$?
>        if (( ! ret )); then
>                msg2 "$(gettext "Database signature file verified.")"
>        else
> @@ -298,9 +311,9 @@ db_write_entry()
>        echo -e "%MD5SUM%\n$md5sum\n" >>desc
>
>        # add base64'd PGP signature
> -       if [[ -f $startdir/$pkgfile.sig ]]; then
> +       if [[ -f $startdir/$pkgfile${SIG_EXT} ]]; then
>                echo -e "%PGPSIG%" >>desc
> -               echo -e "$(openssl base64 -in "$startdir/$pkgfile.sig" | tr -d '\n')\n" >>desc
> +               echo -e "$(openssl enc -base64 -in "$startdir/$pkgfile${SIG_EXT}")\n" >>desc
>        fi
>
>        [[ -n $url ]] && echo -e "%URL%\n$url\n" >>desc
> @@ -492,10 +505,24 @@ trap 'trap_exit "$(gettext "An unknown error has occured. Exiting...")"' ERR
>
>  success=0
>  # parse arguments
> -for arg in "$@"; do
> +while [[ $# > 0 ]] ; do
> +       arg="$1"
>        case "$arg" in
>                -q|--quiet) QUIET=1;;
> -               -s|--sign) SIGN=1;;
> +               -s|--sign)
> +                       SIGN=1
> +                       # The signature will be made, even if there are no operations
> +                       success=1
I don't follow this at all...

> +                       ;;
> +               -k)
> +                       shift
> +                       SIGNKEY="$1"
> +                       # Check if key really exists
> +                       if ! ${GPG} --list-key ${SIGNKEY} 1> /dev/null 2>&1; then
> +                               error "$(gettext "Cannot find key $SIGNKEY.")"
> +                               exit 1
> +                       fi
> +                       ;;
>                -v|--verify) VERIFY=1;;
>                *)
>                        if [[ -z $REPO_DB_FILE ]]; then
> @@ -510,6 +537,7 @@ for arg in "$@"; do
>                        fi
>                        ;;
>        esac
> +       shift
>  done
>
>  # if at least one operation was a success, re-zip database
> @@ -529,18 +557,19 @@ if (( success )); then
>        cd "$tmpdir"
>        if [[ -n $(ls) ]]; then
>                bsdtar -c${TAR_OPT}f "$filename" *
> -               create_signature "$filename"
>        else
>                # we have no packages remaining? zip up some emptyness
>                warning "$(gettext "No packages remain, creating empty database.")"
>                bsdtar -c${TAR_OPT}f "$filename" -T /dev/null
>        fi
> +       # The signature must be dealt with in both cases, empty repo or not.
> +       create_signature "$filename"
>        cd "$startdir"
>
>        [[ -f $REPO_DB_FILE ]] && mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
> -       [[ -f $REPO_DB_FILE.sig ]] && rm -f "$REPO_DB_FILE.sig"
> +       [[ -f $REPO_DB_FILE${SIG_EXT} ]] && rm -f "$REPO_DB_FILE${SIG_EXT}"
>        [[ -f $tmpdir/$filename ]] && mv "$tmpdir/$filename" "$REPO_DB_FILE"
> -       [[ -f $tmpdir/$filename.sig ]] && mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
> +       [[ -f $tmpdir/$filename${SIG_EXT} ]] && mv "$tmpdir/$filename${SIG_EXT}" "$REPO_DB_FILE${SIG_EXT}"
>        ln -sf "$REPO_DB_FILE" "${REPO_DB_FILE%.tar.*}"
>  else
>        msg "$(gettext "No packages modified, nothing to do.")"
> --
> 1.7.1
>
>


More information about the pacman-dev mailing list