From owner-gameoftrees+M2377@openbsd.org Fri Jul 1 15:32:58 2022 Return-Path: Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by sloti51n17 (Cyrus 3.7.0-alpha0-713-g1f035dc716-fm-20220617.001-g1f035dc7) with LMTPA; Fri, 01 Jul 2022 17:32:58 -0400 X-Cyrus-Session-Id: sloti51n17-1656711178-2859225-2-6045727934994432308 X-Sieve: CMU Sieve 3.0 X-Spam-known-sender: no X-Spam-sender-reputation: 500 (none) X-Spam-score: 0.0 X-Spam-hits: BAYES_50 0.8, DCC_REPUT_70_89 0.1, HEADER_FROM_DIFFERENT_DOMAINS 0.25, MAILING_LIST_MULTI -1, ME_HAS_VSSU 0.001, ME_SENDERREP_NEUTRAL 0.001, RCVD_IN_DNSWL_MED -2.3, SPF_HELO_NONE 0.001, SPF_PASS -0.001, T_SCC_BODY_TEXT_LINE -0.01, LANGUAGES en, BAYES_USED user, SA_VERSION 3.4.6 X-Spam-source: IP='199.185.178.25', Host='mail.openbsd.org', Country='CA', FromHeader='com', MailFrom='org' X-Spam-charsets: plain='us-ascii' X-Resolved-to: qbit@fastmail.com X-Delivered-to: aaron@bolddaemon.com X-Mail-from: owner-gameoftrees+M2377@openbsd.org Received: from mx4 ([10.202.2.203]) by compute4.internal (LMTPProxy); Fri, 01 Jul 2022 17:32:58 -0400 Received: from mx4.messagingengine.com (localhost [127.0.0.1]) by mailmx.nyi.internal (Postfix) with ESMTP id 4F9E51F2015B for ; Fri, 1 Jul 2022 17:32:57 -0400 (EDT) Received: from mx4.messagingengine.com (localhost [127.0.0.1]) by mx4.messagingengine.com (Authentication Milter) with ESMTP id 1E136C19FFD; Fri, 1 Jul 2022 17:32:57 -0400 ARC-Seal: i=1; a=rsa-sha256; cv=none; d=messagingengine.com; s=fm2; t= 1656711177; b=MECs9vhH51TZw6R0i0ZmSqoUz/9WJw3n0hR1h0RQXJNTCEIzOk uAp9CC7hiENzB20jLY/XdRv8uquFgcsi/WGyB0Doh0COt6ZFqSSR3gXxUtcqq8sY Zp/gm5EcdmlxKgrj6VGUB9R2DxyMSKSaKDntt9THmvAgyQBwfMoMzgCO+8tDMtJ1 /JxK+pVO8/j66X3qJ9DfklfDMlC/FukJXcvZmrfH3+c544A+h3+jHE5itknD5D8B ju4qFSEqAs4OVC/qO9uSUCCPxaVH0IvPPhQJVTbo1kkaiTA9+XDsrd6OZvuBsqEB yjPeFsDb98chOTF8jP3qwK4WC2TheFpbBFLQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=date:from:to:subject:message-id :references:mime-version:content-type:in-reply-to:list-help :list-id:list-owner:list-post:list-subscribe:list-unsubscribe :sender; s=fm2; t=1656711177; bh=Eo9QrjLTSve5/c6ZYTKt8UqZNUWrN8y Pr0Y92XB6bLY=; b=g2yT9DM8+qgIbu8pi1s2kp05tmhUGLr+bHYoAJnfv1o1A/6 Y47l+aJoSAoorAGUtUhQl8AcYMvMOXdL2GI1Uq8Ft3F3pBoysvCuM+22EDkkHmKx aUQ1zBCNjFaUuJHAryNIlO0DOK+hipWozGji26qBG3j9NumkQ0gAqhjh8Y0QZgOc YY+ncg5c+1hNKEkzbP76rgHQJz1ajTYggXi/s4L8479i9Joq6IIuuUZcvQFwefqN N37CiCMfJb6kviCHpEsAUFibZvCQYOC4ayxHyNfZxFARuQfFk47wRRdQFbZ034Jq u6dgeeBJha9HM5MVH0mpkxk8srtAIu8pnTbOLKw== ARC-Authentication-Results: i=1; mx4.messagingengine.com; x-csa=none; x-me-sender=none; x-ptr=pass smtp.helo=mail.openbsd.org policy.ptr=mail.openbsd.org; bimi=skipped (DMARC did not pass); arc=none (no signatures found); dkim=none (no signatures found); dmarc=fail policy.published-domain-policy=quarantine policy.applied-disposition=none policy.evaluated-disposition=quarantine policy.override-reason=trusted_forwarder policy.arc-aware-result=fail (p=quarantine,has-list-id=yes,d=none,d.eval=quarantine,override=trusted_forwarder,arc_aware_result=fail) policy.policy-from=p header.from=zettaport.com; iprev=pass smtp.remote-ip=199.185.178.25 (mail.openbsd.org); spf=pass smtp.mailfrom=owner-gameoftrees+M2377@openbsd.org smtp.helo=mail.openbsd.org X-ME-Authentication-Results: mx4.messagingengine.com; x-aligned-from=fail; x-return-mx=pass header.domain=zettaport.com policy.is_org=yes (MX Records found: europa.zettaport.com); x-return-mx=pass smtp.domain=openbsd.org policy.is_org=yes (MX Records found: mail.openbsd.org); x-tls=pass smtp.version=TLSv1.3 smtp.cipher=TLS_AES_256_GCM_SHA384 smtp.bits=256/256; x-vs=clean score=0 state=0 Authentication-Results: mx4.messagingengine.com; x-csa=none; x-me-sender=none; x-ptr=pass smtp.helo=mail.openbsd.org policy.ptr=mail.openbsd.org Authentication-Results: mx4.messagingengine.com; bimi=skipped (DMARC did not pass) Authentication-Results: mx4.messagingengine.com; arc=none (no signatures found) Authentication-Results: mx4.messagingengine.com; dkim=none (no signatures found); dmarc=fail policy.published-domain-policy=quarantine policy.applied-disposition=none policy.evaluated-disposition=quarantine policy.override-reason=trusted_forwarder policy.arc-aware-result=fail (p=quarantine,has-list-id=yes,d=none,d.eval=quarantine,override=trusted_forwarder,arc_aware_result=fail) policy.policy-from=p header.from=zettaport.com; iprev=pass smtp.remote-ip=199.185.178.25 (mail.openbsd.org); spf=pass smtp.mailfrom=owner-gameoftrees+M2377@openbsd.org smtp.helo=mail.openbsd.org X-ME-VSSU: VW5zdWI9bWFpbHRvOm1ham9yZG9tb0BvcGVuYnNkLm9yZz9ib2R5PXVuc3ViJTIwZ2FtZW 9mdHJlZXM X-ME-VSCause: gggruggvucftvghtrhhoucdtuddrgedvfedrudehfedgudeifecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepfffhvf fukfhfgggtuggjfeejvddutdfjphhssehttddttddttddvnecuhfhrohhmpeflohhshhcu tfhitghkmhgrrhcuoehophgvnhgsshguodhlihhsthhsseiivghtthgrphhorhhtrdgtoh hmqeenucggtffrrghtthgvrhhnpefgvdeghfdtiedvteekjeeugfevgfdtgeffleelleff leevgeejheetffekkeejfeenucffohhmrghinhepfihtrdhgohhtnecukfhppeduleelrd dukeehrddujeekrddvhedpudegledrvdekrddvfedvrddvgedunecuvehluhhsthgvrhfu ihiivgeptdenucfrrghrrghmpehinhgvthepudelledrudekhedrudejkedrvdehpdhhvg hlohepmhgrihhlrdhophgvnhgsshgurdhorhhgpdhmrghilhhfrhhomhepoehofihnvghr qdhgrghmvghofhhtrhgvvghsodfovdefjeejsehophgvnhgsshgurdhorhhgqe X-ME-VSScore: 0 X-ME-VSCategory: clean X-ME-CSA: none Received-SPF: pass (openbsd.org: 199.185.178.25 is authorized to use 'owner-gameoftrees+M2377@openbsd.org' in 'mfrom' identity (mechanism 'mx' matched)) receiver=mx4.messagingengine.com; identity=mailfrom; envelope-from="owner-gameoftrees+M2377@openbsd.org"; helo=mail.openbsd.org; client-ip=199.185.178.25 Received: from mail.openbsd.org (mail.openbsd.org [199.185.178.25]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx4.messagingengine.com (Postfix) with ESMTPS for ; Fri, 1 Jul 2022 17:32:56 -0400 (EDT) Received: from openbsd.org (localhost [127.0.0.1]) by mail.openbsd.org (OpenSMTPD) with ESMTP id 12cabdb7; Fri, 1 Jul 2022 15:32:47 -0600 (MDT) Received: from europa.zettaport.com (europa.zettaport.com [149.28.232.241]) by mail.openbsd.org (OpenSMTPD) with ESMTPS id 9cf59a50 (TLSv1.3:AEAD-AES256-GCM-SHA384:256:NO) for ; Fri, 1 Jul 2022 15:32:20 -0600 (MDT) Received: by europa.zettaport.com (OpenSMTPD) with ESMTPSA id 774f76fa (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO) for ; Fri, 1 Jul 2022 21:32:19 +0000 (UTC) Received: from localhost (mail.i.zettaport.com [local]) by mail.i.zettaport.com (OpenSMTPD) with ESMTPA id 869f35b9 for ; Fri, 1 Jul 2022 17:32:15 -0400 (EDT) Date: Fri, 1 Jul 2022 17:32:15 -0400 From: Josh Rickmar To: gameoftrees@openbsd.org Subject: Re: Tag signing with SSH signatures Message-ID: References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Loop: gameoftrees@openbsd.org Precedence: list Sender: owner-gameoftrees@openbsd.org Updated diff with improvements and suggestions from stsp@ and tracey@ as well as a regress test. diff refs/heads/main refs/heads/ssh_sigs commit - 917d79a766c47414055c6901624816a41f13597b commit + bccbae8a0342ccf5043ac731e3b2c17135aabc02 blob - 8dfd844f3da472b6ed040a62acaf85403cbc07ea blob + 1b45b53a4efff9977dcd3c2e2e33c499adc94533 --- got/Makefile +++ got/Makefile @@ -13,7 +13,7 @@ SRCS= got.c blame.c commit_graph.c delta.c diff.c \ diff_myers.c diff_output.c diff_output_plain.c \ diff_output_unidiff.c diff_output_edscript.c \ diff_patience.c send.c deltify.c pack_create.c dial.c \ - bloom.c murmurhash2.c ratelimit.c patch.c + bloom.c murmurhash2.c ratelimit.c patch.c sigs.c date.c MAN = ${PROG}.1 got-worktree.5 git-repository.5 got.conf.5 blob - 5f7f00007937a048564e685aac0a8c6b9e98adab blob + 92561637b53431f406c97459c28b0ff81903a35d --- got/got.c +++ got/got.c @@ -58,6 +58,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])) @@ -687,6 +689,76 @@ get_author(char **author, struct got_repository *repo, } 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 * get_gitconfig_path(char **gitconfig_path) { const char *homedir = getenv("HOME"); @@ -6837,7 +6909,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); } @@ -6917,12 +6990,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); @@ -6946,7 +7021,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; @@ -6962,8 +7038,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) @@ -6996,6 +7070,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) @@ -7025,6 +7115,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) @@ -7050,6 +7153,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; } @@ -7098,9 +7204,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; @@ -7110,7 +7213,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; @@ -7166,10 +7270,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; @@ -7223,11 +7335,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; @@ -7245,6 +7359,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 */ @@ -7305,26 +7431,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) @@ -7344,6 +7484,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; @@ -7368,7 +7513,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) { @@ -7389,6 +7535,8 @@ done: free(gitconfig_path); free(commit_id_str); free(tagger); + free(allowed_signers); + free(revoked_signers); return error; } @@ -12418,22 +12566,6 @@ cat_tree(struct got_object_id *id, struct got_reposito 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) { @@ -12465,14 +12597,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), @@ -12531,7 +12663,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 - 781133bbc9837ad999231c521ae9da3239c0232b blob + bf9729a9216142455edfd253fb05cd98c0b4b1f1 --- gotadmin/Makefile +++ gotadmin/Makefile @@ -8,7 +8,8 @@ SRCS= gotadmin.c \ inflate.c lockfile.c object.c object_cache.c object_create.c \ object_idset.c object_parse.c opentemp.c pack.c pack_create.c \ path.c privsep.c reference.c repository.c repository_admin.c \ - worktree_open.c sha1.c bloom.c murmurhash2.c ratelimit.c + worktree_open.c sha1.c bloom.c murmurhash2.c ratelimit.c \ + sigs.c buf.c date.c MAN = ${PROG}.1 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib 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 - 22a9264b9f8d0c0b20b48895dd8ea59708e61d48 blob + 4bfaed588e2d81e1b0578a939dd96553de6bc11b --- include/got_error.h +++ include/got_error.h @@ -169,6 +169,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 - 3ffd653ef429fab490d06ba6a953185254e7c117 blob + 3c092e61bab70845c184eb10f359eb6df3ee01ce --- lib/error.c +++ lib/error.c @@ -217,6 +217,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 - 6ffe646e98676cf9a0d19fe3ad27f3e63ab04fcc blob + dac4ab973b68243e262fd1ae6482fffb6dc2bc57 --- 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 - 5b602c9f5513aee64b98ca608535d5b85280ec42 blob + 7fae8306f7aa444e25b71f0a95f8f151ec324a7f --- lib/gotconfig.c +++ lib/gotconfig.c @@ -101,6 +101,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; @@ -158,3 +176,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 - 5036de1b9a6b491a1fc7c0358a03dcd9574f6cf3 blob + 8f33d6ba0309e1d8f43ef3b0c35b661bd6045211 --- lib/object_create.c +++ lib/object_create.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,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 +47,8 @@ #include "got_lib_object_create.h" +#include "buf.h" + #ifndef nitems #define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) #endif @@ -608,19 +612,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 +687,79 @@ got_object_tag_create(struct got_object_id **id, while (isspace((unsigned char)msg[0])) msg++; + 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; - + 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 +840,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 +870,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 - c0bdac7221a79c5ec97d1728e862406152d51eb9 blob + 07acf70c2c9103549d8dd9f40e6a173d0bb20401 --- lib/privsep.c +++ lib/privsep.c @@ -2365,6 +2365,28 @@ got_privsep_send_gotconfig_author_req(struct imsgbuf * } 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); +} + +const struct got_error * got_privsep_send_gotconfig_remotes_req(struct imsgbuf *ibuf) { if (imsg_compose(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 - aa2c97552358174249a7361aba78c785626d6b7f blob + be0d93073a8d7779e487b6a2d12bad1e6c9721d4 --- libexec/got-read-gotconfig/got-read-gotconfig.c +++ libexec/got-read-gotconfig/got-read-gotconfig.c @@ -548,6 +548,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) { err = got_error(GOT_ERR_PRIVSEP_MSG); 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 - b9a0bd38cabe5d893cbbb04c482578a895a094ed blob + 85fc623c3bd3ebda367919af6ac405ae817a88fc --- libexec/got-read-gotconfig/parse.y +++ libexec/got-read-gotconfig/parse.y @@ -99,7 +99,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 @@ -113,6 +114,7 @@ grammar : /* empty */ | grammar '\n' | grammar author '\n' | grammar remote '\n' + | grammar allowed_signers '\n' ; boolean : STRING { if (strcasecmp($1, "true") == 0 || @@ -306,6 +308,14 @@ 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 | /* empty */ ; @@ -354,6 +364,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}, @@ -364,6 +375,7 @@ lookup(char *s) {"reference", REFERENCE}, {"remote", REMOTE}, {"repository", REPOSITORY}, + {"revoked_signers", REVOKED_SIGNERS}, {"send", SEND}, {"server", SERVER}, }; @@ -791,6 +803,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 - 53325e40ea937187e8814d7b18dd3a6a2f5c40f5 blob + b39af2be74c1e13b37e5bb89219e62eed8046e23 --- regress/cmdline/tag.sh +++ regress/cmdline/tag.sh @@ -257,7 +257,168 @@ test_tag_list_lightweight() { 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 blob - ba79d5e787ada9939dea4f62aae062cea501f845 blob + 7379d7e77fb9e190eb44211cdf722939696a6cbe --- tog/Makefile +++ tog/Makefile @@ -12,7 +12,7 @@ SRCS= tog.c blame.c commit_graph.c delta.c diff.c \ gotconfig.c 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 + diff_patience.c bloom.c murmurhash2.c sigs.c date.c MAN = ${PROG}.1 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib