Add per-repo PinnedPubKey option

This sets curl's CURLOPT_PINNEDPUBLICKEY option in the built-in
downloader, or replaces %p in XferCommand.  This pins public
keys to ensure your TLS connection is not man-in-the-middled
without relying on CAs etc.  Probably most useful currently
for very small or single groups of servers.

It would obviously be best as a per-mirror option, but such
a thing currently does not exist.

Signed-off-by: Travis Burtrum <travis.archlinux@burtrum.org>
This commit is contained in:
Travis Burtrum 2016-10-31 02:12:31 -04:00
parent fa06951d90
commit abb057844e
10 changed files with 74 additions and 8 deletions

View File

@ -126,7 +126,8 @@ Options
All instances of `%u` will be replaced with the download URL. If present,
instances of `%o` will be replaced with the local filename, plus a
``.part'' extension, which allows programs like wget to do file resumes
properly.
properly. If present, instances of `%p` will be replaced with the value of
the repo specific PinnedPubKey directive, or `` if that is not set.
+
This option is useful for users who experience problems with built-in
HTTP/FTP support, or need the more advanced proxy support that comes with
@ -276,6 +277,15 @@ even be used for different architectures.
Note that an enabled repository can be operated on explicitly, regardless of the Usage
level set.
*PinnedPubKey =* pinnedpubkey::
The string can be the file name of your pinned public key. The file format expected
is "PEM" or "DER". The string can also be any number of base64 encoded sha256
hashes preceded by "sha256//" and separated by ";"
+
When negotiating a TLS or SSL connection, the server sends a certificate indicating
its identity. A public key is extracted from this certificate and if it does not
exactly match the public key provided to this option, pacman will abort the
connection before sending or receiving any data.
Package and Database Signature Checking[[SC]]
---------------------------------------------

View File

@ -750,11 +750,12 @@ typedef void (*alpm_cb_totaldl)(off_t total);
* @param url the URL of the file to be downloaded
* @param localpath the directory to which the file should be downloaded
* @param force whether to force an update, even if the file is the same
* @param pinnedpubkey a pinned public key string
* @return 0 on success, 1 if the file exists and is identical, -1 on
* error.
*/
typedef int (*alpm_cb_fetch)(const char *url, const char *localpath,
int force);
int force, const char *pinnedpubkey);
/** Fetch a remote pkg.
* @param handle the context handle
@ -1037,6 +1038,13 @@ alpm_list_t *alpm_db_get_groupcache(alpm_db_t *db);
*/
alpm_list_t *alpm_db_search(alpm_db_t *db, const alpm_list_t *needles);
/** Sets the pinned public key of a database.
* @param db pointer to the package database to set the status for
* @param pinnedpubkey a pinned public key string
* @return 0 on success, or -1 on error
*/
int alpm_db_set_pinnedpubkey(alpm_db_t *db, char *pinnedpubkey);
typedef enum _alpm_db_usage_ {
ALPM_DB_USAGE_SYNC = 1,
ALPM_DB_USAGE_SEARCH = (1 << 1),

View File

@ -243,6 +243,8 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
payload.force = force;
payload.unlink_on_fail = 1;
payload.pinnedpubkey = db->pinnedpubkey;
ret = _alpm_download(&payload, syncpath, NULL, &final_db_url);
_alpm_dload_payload_reset(&payload);
updated = (updated || ret == 0);
@ -297,6 +299,8 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
/* set hard upper limit of 16KiB */
payload.max_size = 16 * 1024;
payload.pinnedpubkey = db->pinnedpubkey;
sig_ret = _alpm_download(&payload, syncpath, NULL, NULL);
/* errors_ok suppresses error messages, but not the return code */
sig_ret = payload.errors_ok ? 0 : sig_ret;

View File

@ -306,6 +306,15 @@ alpm_list_t SYMEXPORT *alpm_db_search(alpm_db_t *db, const alpm_list_t *needles)
return _alpm_db_search(db, needles);
}
/** Sets the pinned public key for a repo */
int SYMEXPORT alpm_db_set_pinnedpubkey(alpm_db_t *db, char *pinnedpubkey)
{
ASSERT(db != NULL, return -1);
ASSERT(pinnedpubkey != NULL, return 0);
db->pinnedpubkey = strdup(pinnedpubkey);
return 0;
}
/** Sets the usage bitmask for a repo */
int SYMEXPORT alpm_db_set_usage(alpm_db_t *db, int usage)
{
@ -351,6 +360,9 @@ void _alpm_db_free(alpm_db_t *db)
FREELIST(db->servers);
FREE(db->_path);
FREE(db->treename);
if(db->pinnedpubkey != NULL) {
FREE(db->pinnedpubkey);
}
FREE(db);
return;

View File

@ -67,6 +67,7 @@ struct __alpm_db_t {
char *treename;
/* do not access directly, use _alpm_db_path(db) for lazy access */
char *_path;
char *pinnedpubkey;
alpm_pkghash_t *pkgcache;
alpm_list_t *grpcache;
alpm_list_t *servers;

View File

@ -327,6 +327,12 @@ static void curl_set_handle_opts(struct dload_payload *payload,
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
}
if(payload->pinnedpubkey != NULL) {
_alpm_log(handle, ALPM_LOG_DEBUG,
"using curl pinnedpubkey: %s\n", payload->pinnedpubkey);
curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, payload->pinnedpubkey);
}
if(!payload->allow_resume && !payload->force && payload->destfile_name &&
stat(payload->destfile_name, &st) == 0) {
/* start from scratch, but only download if our local is out of date. */
@ -646,7 +652,7 @@ int _alpm_download(struct dload_payload *payload, const char *localpath,
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
#endif
} else {
int ret = handle->fetchcb(payload->fileurl, localpath, payload->force);
int ret = handle->fetchcb(payload->fileurl, localpath, payload->force, payload->pinnedpubkey);
if(ret == -1 && !payload->errors_ok) {
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
}

View File

@ -41,6 +41,7 @@ struct dload_payload {
int errors_ok;
int unlink_on_fail;
int trust_remote_name;
char *pinnedpubkey;
#ifdef HAVE_LIBCURL
CURLcode curlerr; /* last error produced by curl */
#endif

View File

@ -862,7 +862,7 @@ static int validate_deltas(alpm_handle_t *handle, alpm_list_t *deltas)
}
static struct dload_payload *build_payload(alpm_handle_t *handle,
const char *filename, size_t size, alpm_list_t *servers)
const char *filename, size_t size, alpm_list_t *servers, char *pinnedpubkey)
{
struct dload_payload *payload;
@ -870,6 +870,7 @@ static struct dload_payload *build_payload(alpm_handle_t *handle,
STRDUP(payload->remote_name, filename, FREE(payload); RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
payload->max_size = size;
payload->servers = servers;
payload->pinnedpubkey = pinnedpubkey;
return payload;
}
@ -898,7 +899,7 @@ static int find_dl_candidates(alpm_db_t *repo, alpm_list_t **files, alpm_list_t
alpm_delta_t *delta = dlts->data;
if(delta->download_size != 0) {
struct dload_payload *payload = build_payload(
handle, delta->delta, delta->delta_size, repo->servers);
handle, delta->delta, delta->delta_size, repo->servers, repo->pinnedpubkey);
ASSERT(payload, return -1);
*files = alpm_list_add(*files, payload);
}
@ -909,7 +910,7 @@ static int find_dl_candidates(alpm_db_t *repo, alpm_list_t **files, alpm_list_t
} else if(spkg->download_size != 0) {
struct dload_payload *payload;
ASSERT(spkg->filename != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
payload = build_payload(handle, spkg->filename, spkg->size, repo->servers);
payload = build_payload(handle, spkg->filename, spkg->size, repo->servers, repo->pinnedpubkey);
ASSERT(payload, return -1);
*files = alpm_list_add(*files, payload);
}

View File

@ -163,6 +163,9 @@ void config_repo_free(config_repo_t *repo)
if(repo == NULL) {
return;
}
if(repo->pinnedpubkey != NULL) {
free(repo->pinnedpubkey);
}
free(repo->name);
FREELIST(repo->servers);
free(repo);
@ -204,7 +207,7 @@ static char *get_tempfile(const char *path, const char *filename)
/** External fetch callback */
static int download_with_xfercommand(const char *url, const char *localpath,
int force)
int force, const char *pinnedpubkey)
{
int ret = 0, retval;
int usepart = 0;
@ -232,6 +235,16 @@ static int download_with_xfercommand(const char *url, const char *localpath,
}
tempcmd = strdup(config->xfercommand);
/* replace all occurrences of %p with pinnedpubkey */
if(strstr(tempcmd, "%p")) {
if(pinnedpubkey == NULL) {
parsedcmd = strreplace(tempcmd, "%p", "");
} else {
parsedcmd = strreplace(tempcmd, "%p", pinnedpubkey);
}
free(tempcmd);
tempcmd = parsedcmd;
}
/* replace all occurrences of %o with fn.part */
if(strstr(tempcmd, "%o")) {
usepart = 1;
@ -668,6 +681,8 @@ static int register_repo(config_repo_t *repo)
repo->name);
alpm_db_set_usage(db, repo->usage == 0 ? ALPM_DB_USAGE_ALL : repo->usage);
alpm_db_set_pinnedpubkey(db, repo->pinnedpubkey);
for(i = repo->servers; i; i = alpm_list_next(i)) {
char *value = i->data;
if(_add_mirror(db, value) != 0) {
@ -915,12 +930,19 @@ static int _parse_repo(const char *key, char *value, const char *file,
}
FREELIST(values);
}
} else if(strcmp(key, "PinnedPubKey") == 0) {
if(!value) {
pm_printf(ALPM_LOG_ERROR, _("config file %s, line %d: directive '%s' needs a value\n"),
file, line, key);
} else {
repo->pinnedpubkey = strdup(value);
pm_printf(ALPM_LOG_DEBUG, "repo config: pinnedpubkey: %s\n", value);
}
} else {
pm_printf(ALPM_LOG_WARNING,
_("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
file, line, key, repo->name);
}
return ret;
}

View File

@ -40,6 +40,7 @@ typedef struct __config_repo_t {
int usage;
int siglevel;
int siglevel_mask;
char *pinnedpubkey;
} config_repo_t;
typedef struct __config_t {