commit 871bd038071fdaf6129995ef9f56226616b47d25 from: Josh Rickmar via: Thomas Adam date: Sun Jul 03 22:08:17 2022 UTC create and verify tags signed by SSH keys This adds a new -s flag to 'got tag' that specifies the signer identity (for example, a key file) of the tagger. The tag object will include a signature that validates each of the tag object headers and the tag message. Verifying these signed tags requires maintaining an allowed signers file which maps signer identities (i.e. the email address of the tagger) to SSH public keys. See ssh-keygen(1) for more details of the allowed signers file. After creating this file and providing the path to it in got.conf(5) using the allowed_signers option, tags may be verified using with 'got tag -V tag_name'. The return code will be non-zero if a signature fails to verify. ok stsp@ commit - 39b829747f18e3f29fe9a14487752d62711b1c0e commit + 871bd038071fdaf6129995ef9f56226616b47d25 blob - fb9c075a4d4365bb17d11dfec773e37ffe801d0e blob + 4d07c4e9776270a88e120fd49a7e344ea732a5ff --- got/got.c +++ got/got.c @@ -57,6 +57,8 @@ #include "got_gotconfig.h" #include "got_dial.h" #include "got_patch.h" +#include "got_sigs.h" +#include "got_date.h" #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) @@ -683,6 +685,76 @@ get_author(char **author, struct got_repository *repo, *author = NULL; } return err; +} + +static const struct got_error * +get_allowed_signers(char **allowed_signers, struct got_repository *repo, + struct got_worktree *worktree) +{ + const char *got_allowed_signers = NULL; + const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; + + *allowed_signers = NULL; + + if (worktree) + worktree_conf = got_worktree_get_gotconfig(worktree); + repo_conf = got_repo_get_gotconfig(repo); + + /* + * Priority of potential author information sources, from most + * significant to least significant: + * 1) work tree's .got/got.conf file + * 2) repository's got.conf file + */ + + if (worktree_conf) + got_allowed_signers = got_gotconfig_get_allowed_signers_file( + worktree_conf); + if (got_allowed_signers == NULL) + got_allowed_signers = got_gotconfig_get_allowed_signers_file( + repo_conf); + + if (got_allowed_signers) { + *allowed_signers = strdup(got_allowed_signers); + if (*allowed_signers == NULL) + return got_error_from_errno("strdup"); + } + return NULL; +} + +static const struct got_error * +get_revoked_signers(char **revoked_signers, struct got_repository *repo, + struct got_worktree *worktree) +{ + const char *got_revoked_signers = NULL; + const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; + + *revoked_signers = NULL; + + if (worktree) + worktree_conf = got_worktree_get_gotconfig(worktree); + repo_conf = got_repo_get_gotconfig(repo); + + /* + * Priority of potential author information sources, from most + * significant to least significant: + * 1) work tree's .got/got.conf file + * 2) repository's got.conf file + */ + + if (worktree_conf) + got_revoked_signers = got_gotconfig_get_revoked_signers_file( + worktree_conf); + if (got_revoked_signers == NULL) + got_revoked_signers = got_gotconfig_get_revoked_signers_file( + repo_conf); + + if (got_revoked_signers) { + *revoked_signers = strdup(got_revoked_signers); + if (*revoked_signers == NULL) + return got_error_from_errno("strdup"); + } + return NULL; } static const struct got_error * @@ -6836,7 +6908,8 @@ usage_tag(void) { fprintf(stderr, "usage: %s tag [-c commit] [-r repository] [-l] " - "[-m message] name\n", getprogname()); + "[-m message] [-s signer_id] name\n", + getprogname()); exit(1); } @@ -6916,12 +6989,14 @@ get_tag_refname(char **refname, const char *tag_name) } static const struct got_error * -list_tags(struct got_repository *repo, const char *tag_name) +list_tags(struct got_repository *repo, const char *tag_name, int verify_tags, + const char *allowed_signers, const char *revoked_signers, int verbosity) { static const struct got_error *err = NULL; struct got_reflist_head refs; struct got_reflist_entry *re; char *wanted_refname = NULL; + int bad_sigs = 0; TAILQ_INIT(&refs); @@ -6945,7 +7020,8 @@ list_tags(struct got_repository *repo, const char *tag const char *refname; char *refstr, *tagmsg0, *tagmsg, *line, *id_str, *datestr; char datebuf[26]; - const char *tagger; + const char *tagger, *ssh_sig = NULL; + char *sig_msg = NULL; time_t tagger_time; struct got_object_id *id; struct got_tag_object *tag; @@ -6961,8 +7037,6 @@ list_tags(struct got_repository *repo, const char *tag err = got_error_from_errno("got_ref_to_str"); break; } - printf("%stag %s %s\n", GOT_COMMIT_SEP_STR, refname, refstr); - free(refstr); err = got_ref_resolve(&id, repo, re->ref); if (err) @@ -6995,6 +7069,22 @@ list_tags(struct got_repository *repo, const char *tag if (err) break; } + + if (verify_tags) { + ssh_sig = got_sigs_get_tagmsg_ssh_signature( + got_object_tag_get_message(tag)); + if (ssh_sig && allowed_signers == NULL) { + err = got_error_msg( + GOT_ERR_VERIFY_TAG_SIGNATURE, + "SSH signature verification requires " + "setting allowed_signers in " + "got.conf(5)"); + break; + } + } + + printf("%stag %s %s\n", GOT_COMMIT_SEP_STR, refname, refstr); + free(refstr); printf("from: %s\n", tagger); datestr = get_datestr(&tagger_time, datebuf); if (datestr) @@ -7024,6 +7114,19 @@ list_tags(struct got_repository *repo, const char *tag } } free(id_str); + + if (ssh_sig) { + err = got_sigs_verify_tag_ssh(&sig_msg, tag, ssh_sig, + allowed_signers, revoked_signers, verbosity); + if (err && err->code == GOT_ERR_BAD_TAG_SIGNATURE) + bad_sigs = 1; + else if (err) + break; + printf("signature: %s", sig_msg); + free(sig_msg); + sig_msg = NULL; + } + if (commit) { err = got_object_commit_get_logmsg(&tagmsg0, commit); if (err) @@ -7049,6 +7152,9 @@ list_tags(struct got_repository *repo, const char *tag done: got_ref_list_free(&refs); free(wanted_refname); + + if (err == NULL && bad_sigs) + err = got_error(GOT_ERR_BAD_TAG_SIGNATURE); return err; } @@ -7097,9 +7203,6 @@ done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno2("close", *tagmsg_path); - /* Editor is done; we can now apply unveil(2) */ - if (err == NULL) - err = apply_unveil(repo_path, 0, NULL); if (err) { free(*tagmsg); *tagmsg = NULL; @@ -7109,7 +7212,8 @@ done: static const struct got_error * add_tag(struct got_repository *repo, const char *tagger, - const char *tag_name, const char *commit_arg, const char *tagmsg_arg) + const char *tag_name, const char *commit_arg, const char *tagmsg_arg, + const char *key_file, int verbosity) { const struct got_error *err = NULL; struct got_object_id *commit_id = NULL, *tag_id = NULL; @@ -7165,10 +7269,18 @@ add_tag(struct got_repository *repo, const char *tagge preserve_tagmsg = 1; goto done; } + /* Editor is done; we can now apply unveil(2) */ + err = got_sigs_apply_unveil(); + if (err) + goto done; + err = apply_unveil(got_repo_get_path(repo), 0, NULL); + if (err) + goto done; } err = got_object_tag_create(&tag_id, tag_name, commit_id, - tagger, time(NULL), tagmsg ? tagmsg : tagmsg_arg, repo); + tagger, time(NULL), tagmsg ? tagmsg : tagmsg_arg, key_file, repo, + verbosity); if (err) { if (tagmsg_path) preserve_tagmsg = 1; @@ -7222,11 +7334,13 @@ cmd_tag(int argc, char *argv[]) struct got_worktree *worktree = NULL; char *cwd = NULL, *repo_path = NULL, *commit_id_str = NULL; char *gitconfig_path = NULL, *tagger = NULL; + char *allowed_signers = NULL, *revoked_signers = NULL; const char *tag_name = NULL, *commit_id_arg = NULL, *tagmsg = NULL; - int ch, do_list = 0; + int ch, do_list = 0, verify_tags = 0, verbosity = 0; + const char *signer_id = NULL; int *pack_fds = NULL; - while ((ch = getopt(argc, argv, "c:m:r:l")) != -1) { + while ((ch = getopt(argc, argv, "c:m:r:ls:Vv")) != -1) { switch (ch) { case 'c': commit_id_arg = optarg; @@ -7244,6 +7358,18 @@ cmd_tag(int argc, char *argv[]) case 'l': do_list = 1; break; + case 's': + signer_id = optarg; + break; + case 'V': + verify_tags = 1; + break; + case 'v': + if (verbosity < 0) + verbosity = 0; + else if (verbosity < 3) + verbosity++; + break; default: usage_tag(); /* NOTREACHED */ @@ -7304,26 +7430,40 @@ cmd_tag(int argc, char *argv[]) } } - if (do_list) { + if (do_list || verify_tags) { + error = got_repo_open(&repo, repo_path, NULL, pack_fds); + if (error != NULL) + goto done; + error = get_allowed_signers(&allowed_signers, repo, worktree); + if (error) + goto done; + error = get_revoked_signers(&revoked_signers, repo, worktree); + if (error) + goto done; if (worktree) { /* Release work tree lock. */ got_worktree_close(worktree); worktree = NULL; } - error = got_repo_open(&repo, repo_path, NULL, pack_fds); - if (error != NULL) - goto done; + /* + * Remove "cpath" promise unless needed for signature tmpfile + * creation. + */ + if (verify_tags) + got_sigs_apply_unveil(); + else { #ifndef PROFILE - /* Remove "cpath" promise. */ - if (pledge("stdio rpath wpath flock proc exec sendfd unveil", - NULL) == -1) - err(1, "pledge"); + if (pledge("stdio rpath wpath flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); #endif + } error = apply_unveil(got_repo_get_path(repo), 1, NULL); if (error) goto done; - error = list_tags(repo, tag_name); + error = list_tags(repo, tag_name, verify_tags, allowed_signers, + revoked_signers, verbosity); } else { error = get_gitconfig_path(&gitconfig_path); if (error) @@ -7343,6 +7483,11 @@ cmd_tag(int argc, char *argv[]) } if (tagmsg) { + if (signer_id) { + error = got_sigs_apply_unveil(); + if (error) + goto done; + } error = apply_unveil(got_repo_get_path(repo), 0, NULL); if (error) goto done; @@ -7367,7 +7512,8 @@ cmd_tag(int argc, char *argv[]) } error = add_tag(repo, tagger, tag_name, - commit_id_str ? commit_id_str : commit_id_arg, tagmsg); + commit_id_str ? commit_id_str : commit_id_arg, tagmsg, + signer_id, verbosity); } done: if (repo) { @@ -7388,6 +7534,8 @@ done: free(gitconfig_path); free(commit_id_str); free(tagger); + free(allowed_signers); + free(revoked_signers); return error; } @@ -12418,23 +12566,7 @@ cat_tree(struct got_object_id *id, struct got_reposito got_object_tree_close(tree); return err; } - -static void -format_gmtoff(char *buf, size_t sz, time_t gmtoff) -{ - long long h, m; - char sign = '+'; - if (gmtoff < 0) { - sign = '-'; - gmtoff = -gmtoff; - } - - h = (long long)gmtoff / 3600; - m = ((long long)gmtoff - h*3600) / 60; - snprintf(buf, sz, "%c%02lld%02lld", sign, h, m); -} - static const struct got_error * cat_commit(struct got_object_id *id, struct got_repository *repo, FILE *outfile) { @@ -12466,14 +12598,14 @@ cat_commit(struct got_object_id *id, struct got_reposi fprintf(outfile, "%s%s\n", GOT_COMMIT_LABEL_PARENT, pid_str); free(pid_str); } - format_gmtoff(gmtoff, sizeof(gmtoff), + got_date_format_gmtoff(gmtoff, sizeof(gmtoff), got_object_commit_get_author_gmtoff(commit)); fprintf(outfile, "%s%s %lld %s\n", GOT_COMMIT_LABEL_AUTHOR, got_object_commit_get_author(commit), (long long)got_object_commit_get_author_time(commit), gmtoff); - format_gmtoff(gmtoff, sizeof(gmtoff), + got_date_format_gmtoff(gmtoff, sizeof(gmtoff), got_object_commit_get_committer_gmtoff(commit)); fprintf(outfile, "%s%s %lld %s\n", GOT_COMMIT_LABEL_COMMITTER, got_object_commit_get_author(commit), @@ -12532,7 +12664,7 @@ cat_tag(struct got_object_id *id, struct got_repositor fprintf(outfile, "%s%s\n", GOT_TAG_LABEL_TAG, got_object_tag_get_name(tag)); - format_gmtoff(gmtoff, sizeof(gmtoff), + got_date_format_gmtoff(gmtoff, sizeof(gmtoff), got_object_tag_get_tagger_gmtoff(tag)); fprintf(outfile, "%s%s %lld %s\n", GOT_TAG_LABEL_TAGGER, got_object_tag_get_tagger(tag), blob - aa54a17419d407bb09bbc7b00af392c74aa8801f blob + 948b9b8fc5270878f0eb2aa61a579197663e4827 --- gotweb/Makefile +++ gotweb/Makefile @@ -15,7 +15,7 @@ SRCS = gotweb.c parse.y blame.c commit_graph.c delta. diff_main.c diff_atomize_text.c diff_myers.c diff_output.c \ diff_output_plain.c diff_output_unidiff.c \ diff_output_edscript.c diff_patience.c \ - bloom.c murmurhash2.c + bloom.c murmurhash2.c sigs.c date.c MAN = ${PROG}.conf.5 ${PROG}.8 CPPFLAGS += -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR} \ blob - /dev/null blob + b005c2c948e0b4b35147550b1b23fef240ddf8b4 (mode 644) --- /dev/null +++ include/got_date.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 Josh Rickmar + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +void +got_date_format_gmtoff(char *, size_t, time_t); blob - f9e082c128ad793ff84090bd88e7521672822a66 blob + 81018778e6b22147a6c7dabec33ba165789d46cc --- include/got_error.h +++ include/got_error.h @@ -171,6 +171,8 @@ #define GOT_ERR_PATCH_FAILED 151 #define GOT_ERR_FILEIDX_DUP_ENTRY 152 #define GOT_ERR_PIN_PACK 153 +#define GOT_ERR_BAD_TAG_SIGNATURE 154 +#define GOT_ERR_VERIFY_TAG_SIGNATURE 155 struct got_error { int code; blob - 3dbe5d7d43cf45ec0e7997d43f266c3ce0c9fcbe blob + 26e15d93b91bc42ee028fa8ecf60a8d1ac4dfdc9 --- include/got_gotconfig.h +++ include/got_gotconfig.h @@ -29,3 +29,19 @@ const char *got_gotconfig_get_author(const struct got_ */ void got_gotconfig_get_remotes(int *, const struct got_remote_repo **, const struct got_gotconfig *); + +/* + * Obtain the filename of the allowed signers file. + * Returns NULL if no configuration file is found or no allowed signers file + * is configured. + */ +const char * +got_gotconfig_get_allowed_signers_file(const struct got_gotconfig *); + +/* + * Obtain the filename of the revoked signers file. + * Returns NULL if no configuration file is found or no revoked signers file + * is configured. + */ +const char * +got_gotconfig_get_revoked_signers_file(const struct got_gotconfig *); blob - a8d0318ceaa7152627e8c8718ba039f8517bc3e4 blob + 1cd6f349912d3e03ebbdccfd4beeeb54663af7fb --- include/got_object.h +++ include/got_object.h @@ -351,4 +351,4 @@ const struct got_error *got_object_commit_add_parent(s /* Create a new tag object in the repository. */ const struct got_error *got_object_tag_create(struct got_object_id **, const char *, struct got_object_id *, const char *, - time_t, const char *, struct got_repository *); + time_t, const char *, const char *, struct got_repository *, int verbosity); blob - /dev/null blob + 204a6265963d6dcbf4d6f3de13f4bdbafaafc6fa (mode 644) --- /dev/null +++ include/got_sigs.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Josh Rickmar + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +const struct got_error * +got_sigs_apply_unveil(void); + +const struct got_error * +got_sigs_sign_tag_ssh(pid_t *, int *, int *, const char *, int); + +const char * +got_sigs_get_tagmsg_ssh_signature(const char *); + +const struct got_error * +got_sigs_verify_tag_ssh(char **, struct got_tag_object *, const char *, + const char *, const char *, int); blob - /dev/null blob + 815b291ce868d18136ce8f45fa2f890b6f6c08f9 (mode 644) --- /dev/null +++ lib/date.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Josh Rickmar + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "got_date.h" + +void +got_date_format_gmtoff(char *buf, size_t sz, time_t gmtoff) +{ + long long h, m; + char sign = '+'; + + if (gmtoff < 0) { + sign = '-'; + gmtoff = -gmtoff; + } + + h = (long long)gmtoff / 3600; + m = ((long long)gmtoff - h*3600) / 60; + snprintf(buf, sz, "%c%02lld%02lld", sign, h, m); +} blob - e89cc3eb38f4cad65bdc171efbcdf20537011837 blob + 35d22ec31b15f0e724ecfef6bbc4ecfa8c92b242 --- lib/error.c +++ lib/error.c @@ -224,6 +224,8 @@ static const struct got_error got_errors[] = { { GOT_ERR_PATCH_FAILED, "patch failed to apply" }, { GOT_ERR_FILEIDX_DUP_ENTRY, "duplicate file index entry" }, { GOT_ERR_PIN_PACK, "could not pin pack file" }, + { GOT_ERR_BAD_TAG_SIGNATURE, "invalid tag signature" }, + { GOT_ERR_VERIFY_TAG_SIGNATURE, "cannot verify signature" }, }; static struct got_custom_error { blob - 5e02aa1efeff0dd226e617da410a4663d8376d9a blob + 39337ed4d9cbe7dfa5939b3f4dcb38793ccddfbd --- lib/got_lib_gotconfig.h +++ lib/got_lib_gotconfig.h @@ -20,6 +20,8 @@ struct got_gotconfig { char *author; int nremotes; struct got_remote_repo *remotes; + char *allowed_signers_file; + char *revoked_signers_file; }; const struct got_error *got_gotconfig_read(struct got_gotconfig **, blob - 1b25e59fd7186315518ba384b03307eba0cb0f2d blob + d7a3b44f1263da90512579341f553502dc6d4a4e --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -172,6 +172,8 @@ enum got_imsg_type { /* Messages related to gotconfig files. */ GOT_IMSG_GOTCONFIG_PARSE_REQUEST, GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST, + GOT_IMSG_GOTCONFIG_ALLOWEDSIGNERS_REQUEST, + GOT_IMSG_GOTCONFIG_REVOKEDSIGNERS_REQUEST, GOT_IMSG_GOTCONFIG_REMOTES_REQUEST, GOT_IMSG_GOTCONFIG_INT_VAL, GOT_IMSG_GOTCONFIG_STR_VAL, @@ -760,6 +762,10 @@ const struct got_error *got_privsep_recv_gitconfig_rem const struct got_error *got_privsep_send_gotconfig_parse_req(struct imsgbuf *, int); const struct got_error *got_privsep_send_gotconfig_author_req(struct imsgbuf *); +const struct got_error *got_privsep_send_gotconfig_allowed_signers_req( + struct imsgbuf *); +const struct got_error *got_privsep_send_gotconfig_revoked_signers_req( + struct imsgbuf *); const struct got_error *got_privsep_send_gotconfig_remotes_req( struct imsgbuf *); const struct got_error *got_privsep_recv_gotconfig_str(char **, blob - 162adf613a914f48646e2c282878578a1c726c19 blob + 4263124d516f9667576c82c1dff455733d247f0d --- lib/gotconfig.c +++ lib/gotconfig.c @@ -100,6 +100,24 @@ got_gotconfig_read(struct got_gotconfig **conf, const if (err) goto done; + err = got_privsep_send_gotconfig_allowed_signers_req(ibuf); + if (err) + goto done; + + err = got_privsep_recv_gotconfig_str(&(*conf)->allowed_signers_file, + ibuf); + if (err) + goto done; + + err = got_privsep_send_gotconfig_revoked_signers_req(ibuf); + if (err) + goto done; + + err = got_privsep_recv_gotconfig_str(&(*conf)->revoked_signers_file, + ibuf); + if (err) + goto done; + err = got_privsep_send_gotconfig_remotes_req(ibuf); if (err) goto done; @@ -157,3 +175,15 @@ got_gotconfig_get_remotes(int *nremotes, const struct *nremotes = conf->nremotes; *remotes = conf->remotes; } + +const char * +got_gotconfig_get_allowed_signers_file(const struct got_gotconfig *conf) +{ + return conf->allowed_signers_file; +} + +const char * +got_gotconfig_get_revoked_signers_file(const struct got_gotconfig *conf) +{ + return conf->revoked_signers_file; +} blob - 87e3158a38d75521cf4c032fb893550039a3683e blob + 2cb607608f8d00326409231209fe90a0502a0fba --- lib/object_create.c +++ lib/object_create.c @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -35,6 +37,7 @@ #include "got_repository.h" #include "got_opentemp.h" #include "got_path.h" +#include "got_sigs.h" #include "got_lib_sha1.h" #include "got_lib_deflate.h" @@ -45,6 +48,8 @@ #include "got_lib_object_create.h" +#include "buf.h" + #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif @@ -608,19 +613,21 @@ done: const struct got_error * got_object_tag_create(struct got_object_id **id, const char *tag_name, struct got_object_id *object_id, const char *tagger, - time_t tagger_time, const char *tagmsg, struct got_repository *repo) + time_t tagger_time, const char *tagmsg, const char *key_file, + struct got_repository *repo, int verbosity) { const struct got_error *err = NULL; SHA1_CTX sha1_ctx; char *header = NULL; char *tag_str = NULL, *tagger_str = NULL; char *id_str = NULL, *obj_str = NULL, *type_str = NULL; - size_t headerlen, len = 0, n; + size_t headerlen, len = 0, sig_len = 0, n; FILE *tagfile = NULL; off_t tagsize = 0; char *msg0 = NULL, *msg; const char *obj_type_str; int obj_type; + BUF *buf = NULL; *id = NULL; @@ -681,9 +688,79 @@ got_object_tag_create(struct got_object_id **id, while (isspace((unsigned char)msg[0])) msg++; - len = strlen(obj_str) + strlen(type_str) + strlen(tag_str) + - strlen(tagger_str) + 1 + strlen(msg) + 1; + if (key_file) { + FILE *out; + pid_t pid; + size_t len; + int in_fd, out_fd; + int status; + + err = buf_alloc(&buf, 0); + if (err) + goto done; + + /* signed message */ + err = buf_puts(&len, buf, obj_str); + if (err) + goto done; + err = buf_puts(&len, buf, type_str); + if (err) + goto done; + err = buf_puts(&len, buf, tag_str); + if (err) + goto done; + err = buf_puts(&len, buf, tagger_str); + if (err) + goto done; + err = buf_putc(buf, '\n'); + if (err) + goto done; + err = buf_puts(&len, buf, msg); + if (err) + goto done; + err = buf_putc(buf, '\n'); + if (err) + goto done; + + err = got_sigs_sign_tag_ssh(&pid, &in_fd, &out_fd, key_file, + verbosity); + if (err) + goto done; + if (buf_write_fd(buf, in_fd) == -1) { + err = got_error_from_errno("write"); + goto done; + } + if (close(in_fd) == -1) { + err = got_error_from_errno("close"); + goto done; + } + if (waitpid(pid, &status, 0) == -1) { + err = got_error_from_errno("waitpid"); + goto done; + } + + out = fdopen(out_fd, "r"); + if (out == NULL) { + err = got_error_from_errno("fdopen"); + goto done; + } + buf_empty(buf); + err = buf_load(&buf, out); + if (err) + goto done; + sig_len = buf_len(buf) + 1; + err = buf_putc(buf, '\0'); + if (err) + goto done; + if (close(out_fd) == -1) { + err = got_error_from_errno("close"); + goto done; + } + } + + len = strlen(obj_str) + strlen(type_str) + strlen(tag_str) + + strlen(tagger_str) + 1 + strlen(msg) + 1 + sig_len; if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_TAG, len) == -1) { err = got_error_from_errno("asprintf"); goto done; @@ -764,6 +841,17 @@ got_object_tag_create(struct got_object_id **id, } tagsize += n; + if (key_file && buf_len(buf) > 0) { + len = buf_len(buf); + SHA1Update(&sha1_ctx, buf_get(buf), len); + n = fwrite(buf_get(buf), 1, len, tagfile); + if (n != len) { + err = got_ferror(tagfile, GOT_ERR_IO); + goto done; + } + tagsize += n; + } + *id = malloc(sizeof(**id)); if (*id == NULL) { err = got_error_from_errno("malloc"); @@ -783,6 +871,8 @@ done: free(header); free(obj_str); free(tagger_str); + if (buf) + buf_release(buf); if (tagfile && fclose(tagfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); if (err) { blob - b56d9cafacf9c2ef80cdf8a02fa3161cbbffc6b6 blob + e5c1a77dd9cbf5a1fbb32633f6161301ed5e1fc5 --- lib/privsep.c +++ lib/privsep.c @@ -2360,6 +2360,28 @@ got_privsep_send_gotconfig_author_req(struct imsgbuf * GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST, 0, 0, -1, NULL, 0) == -1) return got_error_from_errno("imsg_compose " "GOTCONFIG_AUTHOR_REQUEST"); + + return flush_imsg(ibuf); +} + +const struct got_error * +got_privsep_send_gotconfig_allowed_signers_req(struct imsgbuf *ibuf) +{ + if (imsg_compose(ibuf, + GOT_IMSG_GOTCONFIG_ALLOWEDSIGNERS_REQUEST, 0, 0, -1, NULL, 0) == -1) + return got_error_from_errno("imsg_compose " + "GOTCONFIG_ALLOWEDSIGNERS_REQUEST"); + + return flush_imsg(ibuf); +} + +const struct got_error * +got_privsep_send_gotconfig_revoked_signers_req(struct imsgbuf *ibuf) +{ + if (imsg_compose(ibuf, + GOT_IMSG_GOTCONFIG_REVOKEDSIGNERS_REQUEST, 0, 0, -1, NULL, 0) == -1) + return got_error_from_errno("imsg_compose " + "GOTCONFIG_REVOKEDSIGNERS_REQUEST"); return flush_imsg(ibuf); } blob - /dev/null blob + 0b04e3f959ed7aab07534ee9bb5cb4a63e0dd93f (mode 644) --- /dev/null +++ lib/sigs.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2022 Josh Rickmar + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_error.h" +#include "got_date.h" +#include "got_object.h" +#include "got_opentemp.h" + +#include "got_sigs.h" + +#include "buf.h" + +#ifndef MIN +#define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) +#endif + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#ifndef GOT_TAG_PATH_SSH_KEYGEN +#define GOT_TAG_PATH_SSH_KEYGEN "/usr/bin/ssh-keygen" +#endif + +#ifndef GOT_TAG_PATH_SIGNIFY +#define GOT_TAG_PATH_SIGNIFY "/usr/bin/signify" +#endif + +const struct got_error * +got_sigs_apply_unveil() +{ + if (unveil(GOT_TAG_PATH_SSH_KEYGEN, "x") != 0) { + return got_error_from_errno2("unveil", + GOT_TAG_PATH_SSH_KEYGEN); + } + if (unveil(GOT_TAG_PATH_SIGNIFY, "x") != 0) { + return got_error_from_errno2("unveil", + GOT_TAG_PATH_SIGNIFY); + } + + return NULL; +} + +const struct got_error * +got_sigs_sign_tag_ssh(pid_t *newpid, int *in_fd, int *out_fd, + const char* key_file, int verbosity) +{ + const struct got_error *error = NULL; + int pid, in_pfd[2], out_pfd[2]; + const char* argv[11]; + int i = 0, j; + + *newpid = -1; + *in_fd = -1; + *out_fd = -1; + + argv[i++] = GOT_TAG_PATH_SSH_KEYGEN; + argv[i++] = "-Y"; + argv[i++] = "sign"; + argv[i++] = "-f"; + argv[i++] = key_file; + argv[i++] = "-n"; + argv[i++] = "git"; + if (verbosity <= 0) { + argv[i++] = "-q"; + } else { + /* ssh(1) allows up to 3 "-v" options. */ + for (j = 0; j < MIN(3, verbosity); j++) + argv[i++] = "-v"; + } + argv[i++] = NULL; + assert(i <= nitems(argv)); + + if (pipe2(in_pfd, 0) == -1) + return got_error_from_errno("pipe2"); + if (pipe2(out_pfd, 0) == -1) + return got_error_from_errno("pipe2"); + + pid = fork(); + if (pid == -1) { + error = got_error_from_errno("fork"); + close(in_pfd[0]); + close(in_pfd[1]); + close(out_pfd[0]); + close(out_pfd[1]); + return error; + } else if (pid == 0) { + if (close(in_pfd[1]) == -1) + err(1, "close"); + if (close(out_pfd[1]) == -1) + err(1, "close"); + if (dup2(in_pfd[0], 0) == -1) + err(1, "dup2"); + if (dup2(out_pfd[0], 1) == -1) + err(1, "dup2"); + if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1) + err(1, "execv"); + abort(); /* not reached */ + } + if (close(in_pfd[0]) == -1) + return got_error_from_errno("close"); + if (close(out_pfd[0]) == -1) + return got_error_from_errno("close"); + *newpid = pid; + *in_fd = in_pfd[1]; + *out_fd = out_pfd[1]; + return NULL; +} + +static char * +signer_identity(const char *tagger) +{ + char *lt, *gt; + + lt = strstr(tagger, " <"); + gt = strrchr(tagger, '>'); + if (lt && gt && lt+1 < gt) + return strndup(lt+2, gt-lt-2); + return NULL; +} + +static const char* BEGIN_SSH_SIG = "-----BEGIN SSH SIGNATURE-----\n"; +static const char* END_SSH_SIG = "-----END SSH SIGNATURE-----\n"; + +const char * +got_sigs_get_tagmsg_ssh_signature(const char *tagmsg) +{ + const char *s = tagmsg, *begin = NULL, *end = NULL; + + while ((s = strstr(s, BEGIN_SSH_SIG)) != NULL) { + begin = s; + s += strlen(BEGIN_SSH_SIG); + } + if (begin) + end = strstr(begin+strlen(BEGIN_SSH_SIG), END_SSH_SIG); + if (end == NULL) + return NULL; + return (end[strlen(END_SSH_SIG)] == '\0') ? begin : NULL; +} + +static const struct got_error * +got_tag_write_signed_data(BUF *buf, struct got_tag_object *tag, + const char *start_sig) +{ + const struct got_error *err = NULL; + struct got_object_id *id; + char *id_str = NULL; + char *tagger = NULL; + const char *tagmsg; + char gmtoff[6]; + size_t len; + + id = got_object_tag_get_object_id(tag); + err = got_object_id_str(&id_str, id); + if (err) + goto done; + + const char *type_label = NULL; + switch (got_object_tag_get_object_type(tag)) { + case GOT_OBJ_TYPE_BLOB: + type_label = GOT_OBJ_LABEL_BLOB; + break; + case GOT_OBJ_TYPE_TREE: + type_label = GOT_OBJ_LABEL_TREE; + break; + case GOT_OBJ_TYPE_COMMIT: + type_label = GOT_OBJ_LABEL_COMMIT; + break; + case GOT_OBJ_TYPE_TAG: + type_label = GOT_OBJ_LABEL_TAG; + break; + default: + break; + } + got_date_format_gmtoff(gmtoff, sizeof(gmtoff), + got_object_tag_get_tagger_gmtoff(tag)); + if (asprintf(&tagger, "%s %lld %s", got_object_tag_get_tagger(tag), + got_object_tag_get_tagger_time(tag), gmtoff) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + err = buf_puts(&len, buf, GOT_TAG_LABEL_OBJECT); + if (err) + goto done; + err = buf_puts(&len, buf, id_str); + if (err) + goto done; + err = buf_putc(buf, '\n'); + if (err) + goto done; + err = buf_puts(&len, buf, GOT_TAG_LABEL_TYPE); + if (err) + goto done; + err = buf_puts(&len, buf, type_label); + if (err) + goto done; + err = buf_putc(buf, '\n'); + if (err) + goto done; + err = buf_puts(&len, buf, GOT_TAG_LABEL_TAG); + if (err) + goto done; + err = buf_puts(&len, buf, got_object_tag_get_name(tag)); + if (err) + goto done; + err = buf_putc(buf, '\n'); + if (err) + goto done; + err = buf_puts(&len, buf, GOT_TAG_LABEL_TAGGER); + if (err) + goto done; + err = buf_puts(&len, buf, tagger); + if (err) + goto done; + err = buf_puts(&len, buf, "\n"); + if (err) + goto done; + tagmsg = got_object_tag_get_message(tag); + err = buf_append(&len, buf, tagmsg, start_sig-tagmsg); + if (err) + goto done; + +done: + free(id_str); + free(tagger); + return err; +} + +const struct got_error * +got_sigs_verify_tag_ssh(char **msg, struct got_tag_object *tag, + const char *start_sig, const char* allowed_signers, const char* revoked, + int verbosity) +{ + const struct got_error *error = NULL; + const char* argv[17]; + int pid, status, in_pfd[2], out_pfd[2]; + char* parsed_identity = NULL; + const char *identity; + char* tmppath = NULL; + FILE *tmpsig, *out = NULL; + BUF *buf; + int i = 0, j; + + *msg = NULL; + + error = got_opentemp_named(&tmppath, &tmpsig, + GOT_TMPDIR_STR "/got-tagsig"); + if (error) + goto done; + + identity = got_object_tag_get_tagger(tag); + parsed_identity = signer_identity(identity); + if (parsed_identity != NULL) + identity = parsed_identity; + + if (fputs(start_sig, tmpsig) == EOF) { + error = got_error_from_errno("fputs"); + goto done; + } + if (fflush(tmpsig) == EOF) { + error = got_error_from_errno("fflush"); + goto done; + } + + error = buf_alloc(&buf, 0); + if (error) + goto done; + error = got_tag_write_signed_data(buf, tag, start_sig); + if (error) + goto done; + + argv[i++] = GOT_TAG_PATH_SSH_KEYGEN; + argv[i++] = "-Y"; + argv[i++] = "verify"; + argv[i++] = "-f"; + argv[i++] = allowed_signers; + argv[i++] = "-I"; + argv[i++] = identity; + argv[i++] = "-n"; + argv[i++] = "git"; + argv[i++] = "-s"; + argv[i++] = tmppath; + if (revoked) { + argv[i++] = "-r"; + argv[i++] = revoked; + } + if (verbosity > 0) { + /* ssh(1) allows up to 3 "-v" options. */ + for (j = 0; j < MIN(3, verbosity); j++) + argv[i++] = "-v"; + } + argv[i++] = NULL; + assert(i <= nitems(argv)); + + if (pipe2(in_pfd, 0) == -1) { + error = got_error_from_errno("pipe2"); + goto done; + } + if (pipe2(out_pfd, 0) == -1) { + error = got_error_from_errno("pipe2"); + goto done; + } + + pid = fork(); + if (pid == -1) { + error = got_error_from_errno("fork"); + close(in_pfd[0]); + close(in_pfd[1]); + close(out_pfd[0]); + close(out_pfd[1]); + return error; + } else if (pid == 0) { + if (close(in_pfd[1]) == -1) + err(1, "close"); + if (close(out_pfd[1]) == -1) + err(1, "close"); + if (dup2(in_pfd[0], 0) == -1) + err(1, "dup2"); + if (dup2(out_pfd[0], 1) == -1) + err(1, "dup2"); + if (execv(GOT_TAG_PATH_SSH_KEYGEN, (char **const)argv) == -1) + err(1, "execv"); + abort(); /* not reached */ + } + if (close(in_pfd[0]) == -1) { + error = got_error_from_errno("close"); + goto done; + } + if (close(out_pfd[0]) == -1) { + error = got_error_from_errno("close"); + goto done; + } + if (buf_write_fd(buf, in_pfd[1]) == -1) { + error = got_error_from_errno("write"); + goto done; + } + if (close(in_pfd[1]) == -1) { + error = got_error_from_errno("close"); + goto done; + } + if (waitpid(pid, &status, 0) == -1) { + error = got_error_from_errno("waitpid"); + goto done; + } + if (!WIFEXITED(status)) { + error = got_error(GOT_ERR_BAD_TAG_SIGNATURE); + goto done; + } + + out = fdopen(out_pfd[1], "r"); + if (out == NULL) { + error = got_error_from_errno("fdopen"); + goto done; + } + error = buf_load(&buf, out); + if (error) + goto done; + error = buf_putc(buf, '\0'); + if (error) + goto done; + if (close(out_pfd[1]) == -1) { + error = got_error_from_errno("close"); + goto done; + } + out = NULL; + *msg = buf_get(buf); + if (WEXITSTATUS(status) != 0) + error = got_error(GOT_ERR_BAD_TAG_SIGNATURE); + +done: + free(parsed_identity); + free(tmppath); + if (tmpsig && fclose(tmpsig) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + if (out && fclose(out) == EOF && error == NULL) + error = got_error_from_errno("fclose"); + return error; +} blob - ccb786281d0d837592d77d712cd547f65ec906ad blob + a3485e939c9e5dbb2d7836e31c2630ff6aeeafe8 --- libexec/got-read-gotconfig/got-read-gotconfig.c +++ libexec/got-read-gotconfig/got-read-gotconfig.c @@ -558,6 +558,24 @@ main(int argc, char *argv[]) } err = send_gotconfig_str(&ibuf, gotconfig->author ? gotconfig->author : ""); + break; + case GOT_IMSG_GOTCONFIG_ALLOWEDSIGNERS_REQUEST: + if (gotconfig == NULL) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = send_gotconfig_str(&ibuf, + gotconfig->allowed_signers_file ? + gotconfig->allowed_signers_file : ""); + break; + case GOT_IMSG_GOTCONFIG_REVOKEDSIGNERS_REQUEST: + if (gotconfig == NULL) { + err = got_error(GOT_ERR_PRIVSEP_MSG); + break; + } + err = send_gotconfig_str(&ibuf, + gotconfig->revoked_signers_file ? + gotconfig->revoked_signers_file : ""); break; case GOT_IMSG_GOTCONFIG_REMOTES_REQUEST: if (gotconfig == NULL) { blob - 1ce499222101a45de399bd433825c767df869d91 blob + 504e691250732f7b2baee47695fc1794127b2adb --- libexec/got-read-gotconfig/gotconfig.h +++ libexec/got-read-gotconfig/gotconfig.h @@ -1,4 +1,5 @@ /* + * Copyright (c) 2022 Josh Rickmar * Copyright (c) 2020, 2021 Tracey Emery * Copyright (c) 2020 Stefan Sperling * @@ -66,6 +67,8 @@ struct gotconfig { char *author; struct gotconfig_remote_repo_list remotes; int nremotes; + char *allowed_signers_file; + char *revoked_signers_file; }; /* blob - 5030c3f38d4701f0b3d9f9ae5870ffda6d0826b1 blob + b5cc9b52658d608a3822360f94d09fd1b83050d2 --- libexec/got-read-gotconfig/parse.y +++ libexec/got-read-gotconfig/parse.y @@ -104,7 +104,8 @@ typedef struct { %token ERROR %token REMOTE REPOSITORY SERVER PORT PROTOCOL MIRROR_REFERENCES BRANCH -%token AUTHOR FETCH_ALL_BRANCHES REFERENCE FETCH SEND +%token AUTHOR ALLOWED_SIGNERS REVOKED_SIGNERS FETCH_ALL_BRANCHES REFERENCE +%token FETCH SEND %token STRING %token NUMBER %type boolean portplain @@ -118,6 +119,7 @@ grammar : /* empty */ | grammar '\n' | grammar author '\n' | grammar remote '\n' + | grammar allowed_signers '\n' ; boolean : STRING { if (strcasecmp($1, "true") == 0 || @@ -309,6 +311,14 @@ remote : REMOTE STRING { ; author : AUTHOR STRING { gotconfig.author = $2; + } + ; +allowed_signers : ALLOWED_SIGNERS STRING { + gotconfig.allowed_signers_file = $2; + } + ; +revoked_signers : REVOKED_SIGNERS STRING { + gotconfig.revoked_signers_file = $2; } ; optnl : '\n' optnl @@ -359,6 +369,7 @@ lookup(char *s) { /* This has to be sorted always. */ static const struct keywords keywords[] = { + {"allowed_signers", ALLOWED_SIGNERS}, {"author", AUTHOR}, {"branch", BRANCH}, {"fetch", FETCH}, @@ -369,6 +380,7 @@ lookup(char *s) {"reference", REFERENCE}, {"remote", REMOTE}, {"repository", REPOSITORY}, + {"revoked_signers", REVOKED_SIGNERS}, {"send", SEND}, {"server", SERVER}, }; @@ -796,6 +808,8 @@ gotconfig_free(struct gotconfig *conf) struct gotconfig_remote_repo *remote; free(conf->author); + free(conf->allowed_signers_file); + free(conf->revoked_signers_file); while (!TAILQ_EMPTY(&conf->remotes)) { remote = TAILQ_FIRST(&conf->remotes); TAILQ_REMOVE(&conf->remotes, remote, entry); blob - 85eeb97a2ce3a3d1ae9c6e6be4aabef220a38b41 blob + 65d7577f240b78881da8bf24d352cd05849fa247 --- regress/cmdline/tag.sh +++ regress/cmdline/tag.sh @@ -271,8 +271,169 @@ test_tag_list_lightweight() { fi test_done "$testroot" "$ret" } + +test_tag_create_ssh_signed() { + local testroot=`test_init tag_create` + local commit_id=`git_show_head $testroot/repo` + local tag=1.0.0 + local tag2=2.0.0 + + ssh-keygen -q -N '' -t ed25519 -f $testroot/id_ed25519 + ret=$? + if [ $ret -ne 0 ]; then + echo "ssh-keygen failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + touch $testroot/allowed_signers + echo "allowed_signers \"$testroot/allowed_signers\"" > \ + $testroot/repo/.git/got.conf + + # Create a signed tag based on repository's HEAD reference + got tag -s $testroot/id_ed25519 -m 'test' -r $testroot/repo -c HEAD \ + $tag > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "got tag command failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + tag_id=`got ref -r $testroot/repo -l \ + | grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2` + echo "Created tag $tag_id" > $testroot/stdout.expected + cmp -s $testroot/stdout $testroot/stdout.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # Ensure validation fails when the key is not allowed + echo "signature: Could not verify signature." > \ + $testroot/stdout.expected + VERIFY_STDOUT=$(got tag -r $testroot/repo -V $tag 2> $testroot/stderr) + ret=$? + echo "$VERIFY_STDOUT" | grep '^signature: ' > $testroot/stdout + if [ $ret -eq 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "1" + return 1 + fi + GOOD_SIG='Good "git" signature for flan_hacker@openbsd.org with ED25519 key SHA256:' + + # Validate the signature with the key allowed + echo -n 'flan_hacker@openbsd.org ' > $testroot/allowed_signers + cat $testroot/id_ed25519.pub >> $testroot/allowed_signers + GOT_STDOUT=$(got tag -r $testroot/repo -V $tag 2> $testroot/stderr) + ret=$? + if [ $ret -ne 0 ]; then + echo "got tag command failed unexpectedly" + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + if ! echo "$GOT_STDOUT" | grep -q "^signature: $GOOD_SIG"; then + echo "got tag command failed to validate signature" + test_done "$testroot" "1" + return 1 + fi + + # Ensure that Git recognizes and verifies the tag Got has created + (cd $testroot/repo && git checkout -q $tag) + ret=$? + if [ $ret -ne 0 ]; then + echo "git checkout command failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + (cd $testroot/repo && git config --local gpg.ssh.allowedSignersFile \ + $testroot/allowed_signers) + GIT_STDERR=$(cd $testroot/repo && git tag -v $tag 2>&1 1>/dev/null) + if ! echo "$GIT_STDERR" | grep -q "^$GOOD_SIG"; then + echo "git tag command failed to validate signature" + test_done "$testroot" "1" + return 1 + fi + + # Ensure Got recognizes the new tag + got checkout -c $tag $testroot/repo $testroot/wt >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "got checkout command failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + # Create a tag based on implied worktree HEAD ref + (cd $testroot/wt && got tag -m 'test' $tag2 > $testroot/stdout) + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + tag_id2=`got ref -r $testroot/repo -l \ + | grep "^refs/tags/$tag2" | tr -d ' ' | cut -d: -f2` + echo "Created tag $tag_id2" > $testroot/stdout.expected + cmp -s $testroot/stdout $testroot/stdout.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && git checkout -q $tag2) + ret=$? + if [ $ret -ne 0 ]; then + echo "git checkout command failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + # Attempt to create a tag pointing at a non-commit + local tree_id=`git_show_tree $testroot/repo` + (cd $testroot/wt && got tag -m 'test' -c $tree_id foobar \ + 2> $testroot/stderr) + ret=$? + if [ $ret -eq 0 ]; then + echo "git tag command succeeded unexpectedly" + test_done "$testroot" "1" + return 1 + fi + + echo "got: commit $tree_id: object not found" \ + > $testroot/stderr.expected + cmp -s $testroot/stderr $testroot/stderr.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + got ref -r $testroot/repo -l > $testroot/stdout + echo "HEAD: $commit_id" > $testroot/stdout.expected + echo -n "refs/got/worktree/base-" >> $testroot/stdout.expected + cat $testroot/wt/.got/uuid | tr -d '\n' >> $testroot/stdout.expected + echo ": $commit_id" >> $testroot/stdout.expected + echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected + echo "refs/tags/$tag: $tag_id" >> $testroot/stdout.expected + echo "refs/tags/$tag2: $tag_id2" >> $testroot/stdout.expected + cmp -s $testroot/stdout $testroot/stdout.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + test_parseargs "$@" run_test test_tag_create run_test test_tag_list run_test test_tag_list_lightweight +run_test test_tag_create_ssh_signed blob - 0215869fd1a3678fe92c416a609faf3e875f0a34 blob + f835a2398bf16bf81771722cceeaea81aaa9423b --- regress/fetch/Makefile +++ regress/fetch/Makefile @@ -4,7 +4,8 @@ PROG = fetch_test SRCS = error.c privsep.c reference.c sha1.c object.c object_parse.c path.c \ opentemp.c repository.c lockfile.c object_cache.c pack.c inflate.c \ deflate.c delta.c delta_cache.c object_idset.c object_create.c \ - fetch.c gotconfig.c dial.c fetch_test.c bloom.c murmurhash2.c + fetch.c gotconfig.c dial.c fetch_test.c bloom.c murmurhash2.c sigs.c \ + buf.c date.c CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib LDADD = -lutil -lz -lm