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:
parent
fa06951d90
commit
abb057844e
|
@ -126,7 +126,8 @@ Options
|
||||||
All instances of `%u` will be replaced with the download URL. If present,
|
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
|
instances of `%o` will be replaced with the local filename, plus a
|
||||||
``.part'' extension, which allows programs like wget to do file resumes
|
``.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
|
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
|
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
|
Note that an enabled repository can be operated on explicitly, regardless of the Usage
|
||||||
level set.
|
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]]
|
Package and Database Signature Checking[[SC]]
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
|
@ -750,11 +750,12 @@ typedef void (*alpm_cb_totaldl)(off_t total);
|
||||||
* @param url the URL of the file to be downloaded
|
* @param url the URL of the file to be downloaded
|
||||||
* @param localpath the directory to which the file should 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 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
|
* @return 0 on success, 1 if the file exists and is identical, -1 on
|
||||||
* error.
|
* error.
|
||||||
*/
|
*/
|
||||||
typedef int (*alpm_cb_fetch)(const char *url, const char *localpath,
|
typedef int (*alpm_cb_fetch)(const char *url, const char *localpath,
|
||||||
int force);
|
int force, const char *pinnedpubkey);
|
||||||
|
|
||||||
/** Fetch a remote pkg.
|
/** Fetch a remote pkg.
|
||||||
* @param handle the context handle
|
* @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);
|
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_ {
|
typedef enum _alpm_db_usage_ {
|
||||||
ALPM_DB_USAGE_SYNC = 1,
|
ALPM_DB_USAGE_SYNC = 1,
|
||||||
ALPM_DB_USAGE_SEARCH = (1 << 1),
|
ALPM_DB_USAGE_SEARCH = (1 << 1),
|
||||||
|
|
|
@ -242,6 +242,8 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
|
||||||
payload.handle = handle;
|
payload.handle = handle;
|
||||||
payload.force = force;
|
payload.force = force;
|
||||||
payload.unlink_on_fail = 1;
|
payload.unlink_on_fail = 1;
|
||||||
|
|
||||||
|
payload.pinnedpubkey = db->pinnedpubkey;
|
||||||
|
|
||||||
ret = _alpm_download(&payload, syncpath, NULL, &final_db_url);
|
ret = _alpm_download(&payload, syncpath, NULL, &final_db_url);
|
||||||
_alpm_dload_payload_reset(&payload);
|
_alpm_dload_payload_reset(&payload);
|
||||||
|
@ -297,6 +299,8 @@ int SYMEXPORT alpm_db_update(int force, alpm_db_t *db)
|
||||||
/* set hard upper limit of 16KiB */
|
/* set hard upper limit of 16KiB */
|
||||||
payload.max_size = 16 * 1024;
|
payload.max_size = 16 * 1024;
|
||||||
|
|
||||||
|
payload.pinnedpubkey = db->pinnedpubkey;
|
||||||
|
|
||||||
sig_ret = _alpm_download(&payload, syncpath, NULL, NULL);
|
sig_ret = _alpm_download(&payload, syncpath, NULL, NULL);
|
||||||
/* errors_ok suppresses error messages, but not the return code */
|
/* errors_ok suppresses error messages, but not the return code */
|
||||||
sig_ret = payload.errors_ok ? 0 : sig_ret;
|
sig_ret = payload.errors_ok ? 0 : sig_ret;
|
||||||
|
|
|
@ -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);
|
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 */
|
/** Sets the usage bitmask for a repo */
|
||||||
int SYMEXPORT alpm_db_set_usage(alpm_db_t *db, int usage)
|
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);
|
FREELIST(db->servers);
|
||||||
FREE(db->_path);
|
FREE(db->_path);
|
||||||
FREE(db->treename);
|
FREE(db->treename);
|
||||||
|
if(db->pinnedpubkey != NULL) {
|
||||||
|
FREE(db->pinnedpubkey);
|
||||||
|
}
|
||||||
FREE(db);
|
FREE(db);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -67,6 +67,7 @@ struct __alpm_db_t {
|
||||||
char *treename;
|
char *treename;
|
||||||
/* do not access directly, use _alpm_db_path(db) for lazy access */
|
/* do not access directly, use _alpm_db_path(db) for lazy access */
|
||||||
char *_path;
|
char *_path;
|
||||||
|
char *pinnedpubkey;
|
||||||
alpm_pkghash_t *pkgcache;
|
alpm_pkghash_t *pkgcache;
|
||||||
alpm_list_t *grpcache;
|
alpm_list_t *grpcache;
|
||||||
alpm_list_t *servers;
|
alpm_list_t *servers;
|
||||||
|
|
|
@ -327,6 +327,12 @@ static void curl_set_handle_opts(struct dload_payload *payload,
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
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 &&
|
if(!payload->allow_resume && !payload->force && payload->destfile_name &&
|
||||||
stat(payload->destfile_name, &st) == 0) {
|
stat(payload->destfile_name, &st) == 0) {
|
||||||
/* start from scratch, but only download if our local is out of date. */
|
/* 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);
|
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} 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) {
|
if(ret == -1 && !payload->errors_ok) {
|
||||||
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
|
RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ struct dload_payload {
|
||||||
int errors_ok;
|
int errors_ok;
|
||||||
int unlink_on_fail;
|
int unlink_on_fail;
|
||||||
int trust_remote_name;
|
int trust_remote_name;
|
||||||
|
char *pinnedpubkey;
|
||||||
#ifdef HAVE_LIBCURL
|
#ifdef HAVE_LIBCURL
|
||||||
CURLcode curlerr; /* last error produced by curl */
|
CURLcode curlerr; /* last error produced by curl */
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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,
|
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;
|
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));
|
STRDUP(payload->remote_name, filename, FREE(payload); RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
|
||||||
payload->max_size = size;
|
payload->max_size = size;
|
||||||
payload->servers = servers;
|
payload->servers = servers;
|
||||||
|
payload->pinnedpubkey = pinnedpubkey;
|
||||||
return payload;
|
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;
|
alpm_delta_t *delta = dlts->data;
|
||||||
if(delta->download_size != 0) {
|
if(delta->download_size != 0) {
|
||||||
struct dload_payload *payload = build_payload(
|
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);
|
ASSERT(payload, return -1);
|
||||||
*files = alpm_list_add(*files, payload);
|
*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) {
|
} else if(spkg->download_size != 0) {
|
||||||
struct dload_payload *payload;
|
struct dload_payload *payload;
|
||||||
ASSERT(spkg->filename != NULL, RET_ERR(handle, ALPM_ERR_PKG_INVALID_NAME, -1));
|
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);
|
ASSERT(payload, return -1);
|
||||||
*files = alpm_list_add(*files, payload);
|
*files = alpm_list_add(*files, payload);
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,9 @@ void config_repo_free(config_repo_t *repo)
|
||||||
if(repo == NULL) {
|
if(repo == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(repo->pinnedpubkey != NULL) {
|
||||||
|
free(repo->pinnedpubkey);
|
||||||
|
}
|
||||||
free(repo->name);
|
free(repo->name);
|
||||||
FREELIST(repo->servers);
|
FREELIST(repo->servers);
|
||||||
free(repo);
|
free(repo);
|
||||||
|
@ -204,7 +207,7 @@ static char *get_tempfile(const char *path, const char *filename)
|
||||||
|
|
||||||
/** External fetch callback */
|
/** External fetch callback */
|
||||||
static int download_with_xfercommand(const char *url, const char *localpath,
|
static int download_with_xfercommand(const char *url, const char *localpath,
|
||||||
int force)
|
int force, const char *pinnedpubkey)
|
||||||
{
|
{
|
||||||
int ret = 0, retval;
|
int ret = 0, retval;
|
||||||
int usepart = 0;
|
int usepart = 0;
|
||||||
|
@ -232,6 +235,16 @@ static int download_with_xfercommand(const char *url, const char *localpath,
|
||||||
}
|
}
|
||||||
|
|
||||||
tempcmd = strdup(config->xfercommand);
|
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 */
|
/* replace all occurrences of %o with fn.part */
|
||||||
if(strstr(tempcmd, "%o")) {
|
if(strstr(tempcmd, "%o")) {
|
||||||
usepart = 1;
|
usepart = 1;
|
||||||
|
@ -668,6 +681,8 @@ static int register_repo(config_repo_t *repo)
|
||||||
repo->name);
|
repo->name);
|
||||||
alpm_db_set_usage(db, repo->usage == 0 ? ALPM_DB_USAGE_ALL : repo->usage);
|
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)) {
|
for(i = repo->servers; i; i = alpm_list_next(i)) {
|
||||||
char *value = i->data;
|
char *value = i->data;
|
||||||
if(_add_mirror(db, value) != 0) {
|
if(_add_mirror(db, value) != 0) {
|
||||||
|
@ -915,12 +930,19 @@ static int _parse_repo(const char *key, char *value, const char *file,
|
||||||
}
|
}
|
||||||
FREELIST(values);
|
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 {
|
} else {
|
||||||
pm_printf(ALPM_LOG_WARNING,
|
pm_printf(ALPM_LOG_WARNING,
|
||||||
_("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
|
_("config file %s, line %d: directive '%s' in section '%s' not recognized.\n"),
|
||||||
file, line, key, repo->name);
|
file, line, key, repo->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ typedef struct __config_repo_t {
|
||||||
int usage;
|
int usage;
|
||||||
int siglevel;
|
int siglevel;
|
||||||
int siglevel_mask;
|
int siglevel_mask;
|
||||||
|
char *pinnedpubkey;
|
||||||
} config_repo_t;
|
} config_repo_t;
|
||||||
|
|
||||||
typedef struct __config_t {
|
typedef struct __config_t {
|
||||||
|
|
Loading…
Reference in New Issue