From patchwork Tue Aug 3 13:45:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416395 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id F331FC4338F for ; Tue, 3 Aug 2021 13:46:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id DDA67603E9 for ; Tue, 3 Aug 2021 13:46:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236243AbhHCNqQ (ORCPT ); Tue, 3 Aug 2021 09:46:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47256 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232748AbhHCNqP (ORCPT ); Tue, 3 Aug 2021 09:46:15 -0400 Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A3890C06175F for ; Tue, 3 Aug 2021 06:46:04 -0700 (PDT) Received: by mail-wr1-x429.google.com with SMTP id b13so14518238wrs.3 for ; Tue, 03 Aug 2021 06:46:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=SamF6pB4gPy4v5Aig9Mr/SZJEFPShDcWoIFkKcInTd8=; b=CT19CYQh23otxKGWZ9YSDQca44h4b2qVrH3PYGFaDGm91JJVt0DvHPY6L7ErklyC8Z KppVppFxWWpE5se5F9GHFgSpuLhU/vVa0UM0MxNP8eNHjIkzibvAZGjQWdQAgTrl7M3t /COzIukR6LROdogkzmTF6I7FP02KAzxM8mPtQPOm/7ndDYhB2ssRPEyoYmz38OqHH9dR YTFlmG8nQFBchDlEz/bp88QWCduePkRDHY9y5TKAHJBWTG7SPV2VMfpV+XfDIRGMr+0b uvjR3ez0sFlHDatqPd19sC6NFkvNOPKp1+6vWfdEu4cK6QVoYBXT4qD0Nb+mljWNx/Nf PT6Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=SamF6pB4gPy4v5Aig9Mr/SZJEFPShDcWoIFkKcInTd8=; b=quVKSYYJ2I742C/Mo9BpUfTJQ51yzAgZOReHA17pOfSJI3moavuCj0ElG41fVCizUZ 3eeHsCnfJ6ygarnSCHf+TnWewqhWSDmvuklOFu6Ss3Up9VgKOBVkjbdsrCiI9qg2UFC7 yj8mubecNvP/sXSA9upJGCFKXdD5DGVldYxxy5RtKmwUJ51pCIjD3NsqMyfdvDJizDGa zZhjE9AuPnxyoQvD+euNgts8RXztk8hNCT4LqrZqXmo0zvFyf+L8gbWNQzJkExsPYtp+ qZg+rEIOmmlgn+S1imW6uTeKLyX/8yEp8IpIFUByf4MTv/qU4n9SLRWLsqqm8P7jiL8o xeqQ== X-Gm-Message-State: AOAM533TYZ9RIGaTgf7VcwNpCZ3IG7HrmsL1Ive1TDHMb+DjTdfzVnUK m1gTN5ZEFZOW1RKUFtoCqJrBlMap4ok= X-Google-Smtp-Source: ABdhPJzcPTjyCa3Dm7lXDe3fl3HMWaHXJpvf0ufYxabQ57RnoL/zR4CqcfHyZgdRX970gIV+27bJsA== X-Received: by 2002:adf:cd92:: with SMTP id q18mr23529131wrj.18.1627998363295; Tue, 03 Aug 2021 06:46:03 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id r129sm4607724wmr.7.2021.08.03.06.46.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:02 -0700 (PDT) Message-Id: <91fd0159e1f8d729c9144f61109496f018dcef47.1627998358.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:50 +0000 Subject: [PATCH v7 1/9] ssh signing: preliminary refactoring and clean-up Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Openssh v8.2p1 added some new options to ssh-keygen for signature creation and verification. These allow us to use ssh keys for git signatures easily. In our corporate environment we use PIV x509 Certs on Yubikeys for email signing/encryption and ssh keys which I think is quite common (at least for the email part). This way we can establish the correct trust for the SSH Keys without setting up a separate GPG Infrastructure (which is still quite painful for users) or implementing x509 signing support for git (which lacks good forwarding mechanisms). Using ssh agent forwarding makes this feature easily usable in todays development environments where code is often checked out in remote VMs / containers. In such a setup the keyring & revocationKeyring can be centrally generated from the x509 CA information and distributed to the users. To be able to implement new signing formats this commit: - makes the sigc structure more generic by renaming "gpg_output" to "output" - introduces function pointers in the gpg_format structure to call format specific signing and verification functions - moves format detection from verify_signed_buffer into the check_signature api function and calls the format specific verify - renames and wraps sign_buffer to handle format specific signing logic as well Signed-off-by: Fabian Stelzer --- fmt-merge-msg.c | 6 +-- gpg-interface.c | 104 +++++++++++++++++++++++++++++------------------- gpg-interface.h | 2 +- log-tree.c | 8 ++-- pretty.c | 4 +- 5 files changed, 74 insertions(+), 50 deletions(-) diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 0f66818e0f8..fb300bb4b67 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -526,11 +526,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out) buf = payload.buf; len = payload.len; if (check_signature(payload.buf, payload.len, sig.buf, - sig.len, &sigc) && - !sigc.gpg_output) + sig.len, &sigc) && + !sigc.output) strbuf_addstr(&sig, "gpg verification failed.\n"); else - strbuf_addstr(&sig, sigc.gpg_output); + strbuf_addstr(&sig, sigc.output); } signature_check_clear(&sigc); diff --git a/gpg-interface.c b/gpg-interface.c index 127aecfc2b0..db54b054162 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -15,6 +15,12 @@ struct gpg_format { const char *program; const char **verify_args; const char **sigs; + int (*verify_signed_buffer)(struct signature_check *sigc, + struct gpg_format *fmt, const char *payload, + size_t payload_size, const char *signature, + size_t signature_size); + int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key); }; static const char *openpgp_verify_args[] = { @@ -35,14 +41,29 @@ static const char *x509_sigs[] = { NULL }; +static int verify_gpg_signed_buffer(struct signature_check *sigc, + struct gpg_format *fmt, const char *payload, + size_t payload_size, const char *signature, + size_t signature_size); +static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key); + static struct gpg_format gpg_format[] = { - { .name = "openpgp", .program = "gpg", - .verify_args = openpgp_verify_args, - .sigs = openpgp_sigs + { + .name = "openpgp", + .program = "gpg", + .verify_args = openpgp_verify_args, + .sigs = openpgp_sigs, + .verify_signed_buffer = verify_gpg_signed_buffer, + .sign_buffer = sign_buffer_gpg, }, - { .name = "x509", .program = "gpgsm", - .verify_args = x509_verify_args, - .sigs = x509_sigs + { + .name = "x509", + .program = "gpgsm", + .verify_args = x509_verify_args, + .sigs = x509_sigs, + .verify_signed_buffer = verify_gpg_signed_buffer, + .sign_buffer = sign_buffer_gpg, }, }; @@ -72,7 +93,7 @@ static struct gpg_format *get_format_by_sig(const char *sig) void signature_check_clear(struct signature_check *sigc) { FREE_AND_NULL(sigc->payload); - FREE_AND_NULL(sigc->gpg_output); + FREE_AND_NULL(sigc->output); FREE_AND_NULL(sigc->gpg_status); FREE_AND_NULL(sigc->signer); FREE_AND_NULL(sigc->key); @@ -257,16 +278,16 @@ error: FREE_AND_NULL(sigc->key); } -static int verify_signed_buffer(const char *payload, size_t payload_size, - const char *signature, size_t signature_size, - struct strbuf *gpg_output, - struct strbuf *gpg_status) +static int verify_gpg_signed_buffer(struct signature_check *sigc, + struct gpg_format *fmt, const char *payload, + size_t payload_size, const char *signature, + size_t signature_size) { struct child_process gpg = CHILD_PROCESS_INIT; - struct gpg_format *fmt; struct tempfile *temp; int ret; - struct strbuf buf = STRBUF_INIT; + struct strbuf gpg_stdout = STRBUF_INIT; + struct strbuf gpg_stderr = STRBUF_INIT; temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); if (!temp) @@ -279,10 +300,6 @@ static int verify_signed_buffer(const char *payload, size_t payload_size, return -1; } - fmt = get_format_by_sig(signature); - if (!fmt) - BUG("bad signature '%s'", signature); - strvec_push(&gpg.args, fmt->program); strvec_pushv(&gpg.args, fmt->verify_args); strvec_pushl(&gpg.args, @@ -290,18 +307,22 @@ static int verify_signed_buffer(const char *payload, size_t payload_size, "--verify", temp->filename.buf, "-", NULL); - if (!gpg_status) - gpg_status = &buf; - sigchain_push(SIGPIPE, SIG_IGN); - ret = pipe_command(&gpg, payload, payload_size, - gpg_status, 0, gpg_output, 0); + ret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0, + &gpg_stderr, 0); sigchain_pop(SIGPIPE); delete_tempfile(&temp); - ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); - strbuf_release(&buf); /* no matter it was used or not */ + ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG "); + sigc->payload = xmemdupz(payload, payload_size); + sigc->output = strbuf_detach(&gpg_stderr, NULL); + sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL); + + parse_gpg_output(sigc); + + strbuf_release(&gpg_stdout); + strbuf_release(&gpg_stderr); return ret; } @@ -309,35 +330,32 @@ static int verify_signed_buffer(const char *payload, size_t payload_size, int check_signature(const char *payload, size_t plen, const char *signature, size_t slen, struct signature_check *sigc) { - struct strbuf gpg_output = STRBUF_INIT; - struct strbuf gpg_status = STRBUF_INIT; + struct gpg_format *fmt; int status; sigc->result = 'N'; sigc->trust_level = -1; - status = verify_signed_buffer(payload, plen, signature, slen, - &gpg_output, &gpg_status); - if (status && !gpg_output.len) - goto out; - sigc->payload = xmemdupz(payload, plen); - sigc->gpg_output = strbuf_detach(&gpg_output, NULL); - sigc->gpg_status = strbuf_detach(&gpg_status, NULL); - parse_gpg_output(sigc); + fmt = get_format_by_sig(signature); + if (!fmt) + die(_("bad/incompatible signature '%s'"), signature); + + status = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature, + slen); + + if (status && !sigc->output) + return !!status; + status |= sigc->result != 'G'; status |= sigc->trust_level < configured_min_trust_level; - out: - strbuf_release(&gpg_status); - strbuf_release(&gpg_output); - return !!status; } void print_signature_buffer(const struct signature_check *sigc, unsigned flags) { - const char *output = flags & GPG_VERIFY_RAW ? - sigc->gpg_status : sigc->gpg_output; + const char *output = flags & GPG_VERIFY_RAW ? sigc->gpg_status : + sigc->output; if (flags & GPG_VERIFY_VERBOSE && sigc->payload) fputs(sigc->payload, stdout); @@ -441,6 +459,12 @@ const char *get_signing_key(void) } int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) +{ + return use_format->sign_buffer(buffer, signature, signing_key); +} + +static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key) { struct child_process gpg = CHILD_PROCESS_INIT; int ret; diff --git a/gpg-interface.h b/gpg-interface.h index 80567e48948..feac4decf8b 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -17,7 +17,7 @@ enum signature_trust_level { struct signature_check { char *payload; - char *gpg_output; + char *output; char *gpg_status; /* diff --git a/log-tree.c b/log-tree.c index 6dc4412268b..644893fd8cf 100644 --- a/log-tree.c +++ b/log-tree.c @@ -515,10 +515,10 @@ static void show_signature(struct rev_info *opt, struct commit *commit) status = check_signature(payload.buf, payload.len, signature.buf, signature.len, &sigc); - if (status && !sigc.gpg_output) + if (status && !sigc.output) show_sig_lines(opt, status, "No signature\n"); else - show_sig_lines(opt, status, sigc.gpg_output); + show_sig_lines(opt, status, sigc.output); signature_check_clear(&sigc); out: @@ -585,8 +585,8 @@ static int show_one_mergetag(struct commit *commit, /* could have a good signature */ status = check_signature(payload.buf, payload.len, signature.buf, signature.len, &sigc); - if (sigc.gpg_output) - strbuf_addstr(&verify_message, sigc.gpg_output); + if (sigc.output) + strbuf_addstr(&verify_message, sigc.output); else strbuf_addstr(&verify_message, "No signature\n"); signature_check_clear(&sigc); diff --git a/pretty.c b/pretty.c index 9631529c10a..be477bd51f2 100644 --- a/pretty.c +++ b/pretty.c @@ -1432,8 +1432,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ check_commit_signature(c->commit, &(c->signature_check)); switch (placeholder[1]) { case 'G': - if (c->signature_check.gpg_output) - strbuf_addstr(sb, c->signature_check.gpg_output); + if (c->signature_check.output) + strbuf_addstr(sb, c->signature_check.output); break; case '?': switch (c->signature_check.result) { From patchwork Tue Aug 3 13:45:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416399 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8CA5CC4338F for ; Tue, 3 Aug 2021 13:46:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6C5BB60EE8 for ; Tue, 3 Aug 2021 13:46:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236245AbhHCNqY (ORCPT ); Tue, 3 Aug 2021 09:46:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47270 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236204AbhHCNqS (ORCPT ); Tue, 3 Aug 2021 09:46:18 -0400 Received: from mail-wr1-x430.google.com (mail-wr1-x430.google.com [IPv6:2a00:1450:4864:20::430]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 11D02C061757 for ; Tue, 3 Aug 2021 06:46:07 -0700 (PDT) Received: by mail-wr1-x430.google.com with SMTP id z4so25415627wrv.11 for ; Tue, 03 Aug 2021 06:46:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=G/IA2WOxOrK1uyk5drgZ3Smmg4we8wLdb4C/7+oruhI=; b=FqEYDr91EX3Zxp0YCnLFK37MnZdhGZz1BG5rj4bn11KPdsMWM9qMn4M5S81q9prB5D 9mOxJ0TQwYeLhBGSfI4yEs5VuqFiiHfsEX4SZ2cJFTmOBZtJmszQwG3VgiMI1f5v3QYS FVIJwIq+PDGnymytafmsDWnLZB0LfbMslir1DBVOPybHwOl/6+0bhXwTFP6bllzlQw60 lrSDg1d4WnEz4fB7ytwThDM5PT4tmVCbJXPgTIXI/PUhnqHwsEXrficxnOMfJmeGngZz hChJIy3jPWhToeDeEP/KOfiuzRaFYakcWXywdwdkaPePr+2yXto5YwGJ60d4seIJ+7mh 6yUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=G/IA2WOxOrK1uyk5drgZ3Smmg4we8wLdb4C/7+oruhI=; b=Gf4wrXhN6EA8LC4yVo49A/h+MNCvdfZ6lRdgBmH5LUcJ4n3QKhDxmkHb9dd4gzCcMK kdjgNZdXMdYFp1Cw5dzAmEYJaawSirRWXpF0HfqPwYy2SZG7kBBY755b2APxQ7P3Dp53 xoWNhq53Oo+bWs730XXfx8afB8yxKxGpH9JXEMsjAfHLVFtWF4/B+tMuWFKJ6KwAzQzN ZyV+IJ/5TYYB9/58E1ogJmnQoLuYuE26J+JyUaIoh/3J3WQVUFKP5cWnkHBib3VrSuh+ a9lo9pXPV5C+pxM3zacxD2vqeQUvA46uTbiyvOEt95nNGugTaumLoJkvVkvzRRk3sxhb KyuA== X-Gm-Message-State: AOAM532WO4UEaQhd+G8gVv5V9O+nSjjwNYgQU7h57ltlsdaCkPTqUSMc k8G9+Ppd5H6muQipP/ON6SVanKhlD+Y= X-Google-Smtp-Source: ABdhPJw1+w8c01L+8U/VgUdKPRKisfZ5+uhE5GFl0HrZimpjxW8T8ZRUylEEmVMMvKAxgpwxUlYiqQ== X-Received: by 2002:a5d:420c:: with SMTP id n12mr23403611wrq.58.1627998365695; Tue, 03 Aug 2021 06:46:05 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id s13sm2637594wmc.47.2021.08.03.06.46.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:03 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:51 +0000 Subject: [PATCH v7 2/9] ssh signing: add test prereqs Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Generate some ssh keys and a allowedSignersFile for testing Signed-off-by: Fabian Stelzer --- t/lib-gpg.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 9fc5241228e..f99ef3e859d 100644 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -87,6 +87,34 @@ test_lazy_prereq RFC1991 ' echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null ' +GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key" +GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +GPGSSH_KEY_PASSPHRASE="super_secret" +GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +GPGSSH_KEY_NOT_TRUSTED="No principal matched" +GPGSSH_BAD_SIGNATURE="Signature verification failed" + +test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null && + echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null && + echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null && + echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null +' + sanitize_pgp() { perl -ne ' /^-----END PGP/ and $in_pgp = 0; From patchwork Tue Aug 3 13:45:52 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416401 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EE5D9C432BE for ; Tue, 3 Aug 2021 13:46:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CF05760EE8 for ; Tue, 3 Aug 2021 13:46:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236253AbhHCNq0 (ORCPT ); Tue, 3 Aug 2021 09:46:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47278 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236254AbhHCNqT (ORCPT ); Tue, 3 Aug 2021 09:46:19 -0400 Received: from mail-wm1-x329.google.com (mail-wm1-x329.google.com [IPv6:2a00:1450:4864:20::329]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 90EEDC06175F for ; Tue, 3 Aug 2021 06:46:08 -0700 (PDT) Received: by mail-wm1-x329.google.com with SMTP id l34-20020a05600c1d22b02902573c214807so1786277wms.2 for ; Tue, 03 Aug 2021 06:46:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=b0eJkTewMzFu6XDC4tXfSIWvMZG72Wsu+DB2qkv61VI=; b=XdwgUFt2JqIEcLq2QYAYfa8MnWbxDzLppHHV1kvs4OI2BsIsdXUit25sZjz4UIkbFd a+l+8xb08vyCLdCWnO7UIuuGZy52kOLftc92ZyYB96r2eNAlRdesdaXbAu9B7xTzRQ/y rxX2CzmUrQRMX+81F61A114bHeLSYiLuCwuX8WKBZwEmHiSRfqORMeDXKZLWlqgoT0fb xuOlL+VTyrjoKBS836gqnEGji+lVb3ZxDjlJVwR/jKcPc6UCll7a79FddKi1tI0RiGdz I9ZP3vbUwKE9NpjJKziOCMIabaPw3S89QAg1BpsBSJqNwqS5Q4Ke2chVM8MB8MZwCOV/ oz5w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=b0eJkTewMzFu6XDC4tXfSIWvMZG72Wsu+DB2qkv61VI=; b=Yyci6FWQliFcINHfnQt7h4gk3Q61TaeLq7xoPVIPg9O1GVy2yAjsUyHn7MHQ5pZ/bd 72YK0HPfopsBoC2XkjaDX6o+zsKYjQsaFvnucxte4l+UzpofbSzqx+b/kOHTgvLy2y0d FwMygCPS/MXVGJaagVxb+aEcfHf8ldMVk04zuqm7TjiZlJUPOd8Q813myG4SCrTBdthT sXNbAItzapzImIRpkYEw5AJQkjOgte8Sg2sfalqQTlckSOXom64DqcQpyjpypk8wZsVt JyVB7SLqNSV1cS6vTRvqCGPVlcfwyIehzl95ct/uGtj4wYfHRVD5jUXHCjSRcDRgjRUg gSHw== X-Gm-Message-State: AOAM530RlAwmfL3bBZf2UhoSz9/d63owniSO2r1htuEndiz+pfB7T4Gy MOlIz5BWRGa7tEc5UI7Kr/VXKirVGOE= X-Google-Smtp-Source: ABdhPJyPwy2TlE7NSVurkk7oXq7dDfgzE+gNQVXjvqO8mXYu8SbQ9hu0Dc16SIO74APV+UUDVxer7w== X-Received: by 2002:a05:600c:206:: with SMTP id 6mr4374501wmi.57.1627998367133; Tue, 03 Aug 2021 06:46:07 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id a9sm15118232wrv.37.2021.08.03.06.46.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:06 -0700 (PDT) Message-Id: <80d2d55d22e4b424e798aff3993d9424bd2f9a02.1627998358.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:52 +0000 Subject: [PATCH v7 3/9] ssh signing: add ssh key format and signing code Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Implements the actual sign_buffer_ssh operation and move some shared cleanup code into a strbuf function Set gpg.format = ssh and user.signingkey to either a ssh public key string (like from an authorized_keys file), or a ssh key file. If the key file or the config value itself contains only a public key then the private key needs to be available via ssh-agent. gpg.ssh.program can be set to an alternative location of ssh-keygen. A somewhat recent openssh version (8.2p1+) of ssh-keygen is needed for this feature. Since only ssh-keygen is needed it can this way be installed seperately without upgrading your system openssh packages. Signed-off-by: Fabian Stelzer --- Documentation/config/gpg.txt | 4 +- Documentation/config/user.txt | 5 ++ gpg-interface.c | 138 ++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt index d94025cb368..88531b15f0f 100644 --- a/Documentation/config/gpg.txt +++ b/Documentation/config/gpg.txt @@ -11,13 +11,13 @@ gpg.program:: gpg.format:: Specifies which key format to use when signing with `--gpg-sign`. - Default is "openpgp" and another possible value is "x509". + Default is "openpgp". Other possible values are "x509", "ssh". gpg..program:: Use this to customize the program used for the signing format you chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still be used as a legacy synonym for `gpg.openpgp.program`. The default - value for `gpg.x509.program` is "gpgsm". + value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen". gpg.minTrustLevel:: Specifies a minimum trust level for signature verification. If diff --git a/Documentation/config/user.txt b/Documentation/config/user.txt index 59aec7c3aed..2155128957c 100644 --- a/Documentation/config/user.txt +++ b/Documentation/config/user.txt @@ -36,3 +36,8 @@ user.signingKey:: commit, you can override the default selection with this variable. This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. + If gpg.format is set to "ssh" this can contain the literal ssh public + key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and + corresponds to the private key used for signing. The private key + needs to be available via ssh-agent. Alternatively it can be set to + a file containing a private key directly. diff --git a/gpg-interface.c b/gpg-interface.c index db54b054162..7ca682ac6d6 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -41,12 +41,20 @@ static const char *x509_sigs[] = { NULL }; +static const char *ssh_verify_args[] = { NULL }; +static const char *ssh_sigs[] = { + "-----BEGIN SSH SIGNATURE-----", + NULL +}; + static int verify_gpg_signed_buffer(struct signature_check *sigc, struct gpg_format *fmt, const char *payload, size_t payload_size, const char *signature, size_t signature_size); static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); +static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key); static struct gpg_format gpg_format[] = { { @@ -65,6 +73,14 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, }, + { + .name = "ssh", + .program = "ssh-keygen", + .verify_args = ssh_verify_args, + .sigs = ssh_sigs, + .verify_signed_buffer = NULL, /* TODO */ + .sign_buffer = sign_buffer_ssh + }, }; static struct gpg_format *use_format = &gpg_format[0]; @@ -443,6 +459,9 @@ int git_gpg_config(const char *var, const char *value, void *cb) if (!strcmp(var, "gpg.x509.program")) fmtname = "x509"; + if (!strcmp(var, "gpg.ssh.program")) + fmtname = "ssh"; + if (fmtname) { fmt = get_format_by_name(fmtname); return git_config_string(&fmt->program, var, value); @@ -463,12 +482,30 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig return use_format->sign_buffer(buffer, signature, signing_key); } +/* + * Strip CR from the line endings, in case we are on Windows. + * NEEDSWORK: make it trim only CRs before LFs and rename + */ +static void remove_cr_after(struct strbuf *buffer, size_t offset) +{ + size_t i, j; + + for (i = j = offset; i < buffer->len; i++) { + if (buffer->buf[i] != '\r') { + if (i != j) + buffer->buf[j] = buffer->buf[i]; + j++; + } + } + strbuf_setlen(buffer, j); +} + static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { struct child_process gpg = CHILD_PROCESS_INIT; int ret; - size_t i, j, bottom; + size_t bottom; struct strbuf gpg_status = STRBUF_INIT; strvec_pushl(&gpg.args, @@ -494,13 +531,98 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, return error(_("gpg failed to sign the data")); /* Strip CR from the line endings, in case we are on Windows. */ - for (i = j = bottom; i < signature->len; i++) - if (signature->buf[i] != '\r') { - if (i != j) - signature->buf[j] = signature->buf[i]; - j++; - } - strbuf_setlen(signature, j); + remove_cr_after(signature, bottom); return 0; } + +static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key) +{ + struct child_process signer = CHILD_PROCESS_INIT; + int ret = -1; + size_t bottom, keylen; + struct strbuf signer_stderr = STRBUF_INIT; + struct tempfile *key_file = NULL, *buffer_file = NULL; + char *ssh_signing_key_file = NULL; + struct strbuf ssh_signature_filename = STRBUF_INIT; + + if (!signing_key || signing_key[0] == '\0') + return error( + _("user.signingkey needs to be set for ssh signing")); + + if (starts_with(signing_key, "ssh-")) { + /* A literal ssh key */ + key_file = mks_tempfile_t(".git_signing_key_tmpXXXXXX"); + if (!key_file) + return error_errno( + _("could not create temporary file")); + keylen = strlen(signing_key); + if (write_in_full(key_file->fd, signing_key, keylen) < 0 || + close_tempfile_gently(key_file) < 0) { + error_errno(_("failed writing ssh signing key to '%s'"), + key_file->filename.buf); + goto out; + } + ssh_signing_key_file = strbuf_detach(&key_file->filename, NULL); + } else { + /* We assume a file */ + ssh_signing_key_file = expand_user_path(signing_key, 1); + } + + buffer_file = mks_tempfile_t(".git_signing_buffer_tmpXXXXXX"); + if (!buffer_file) { + error_errno(_("could not create temporary file")); + goto out; + } + + if (write_in_full(buffer_file->fd, buffer->buf, buffer->len) < 0 || + close_tempfile_gently(buffer_file) < 0) { + error_errno(_("failed writing ssh signing key buffer to '%s'"), + buffer_file->filename.buf); + goto out; + } + + strvec_pushl(&signer.args, use_format->program, + "-Y", "sign", + "-n", "git", + "-f", ssh_signing_key_file, + buffer_file->filename.buf, + NULL); + + sigchain_push(SIGPIPE, SIG_IGN); + ret = pipe_command(&signer, NULL, 0, NULL, 0, &signer_stderr, 0); + sigchain_pop(SIGPIPE); + + if (ret) { + if (strstr(signer_stderr.buf, "usage:")) + error(_("ssh-keygen -Y sign is needed for ssh signing (available in openssh version 8.2p1+)")); + + error("%s", signer_stderr.buf); + goto out; + } + + bottom = signature->len; + + strbuf_addbuf(&ssh_signature_filename, &buffer_file->filename); + strbuf_addstr(&ssh_signature_filename, ".sig"); + if (strbuf_read_file(signature, ssh_signature_filename.buf, 0) < 0) { + error_errno( + _("failed reading ssh signing data buffer from '%s'"), + ssh_signature_filename.buf); + } + unlink_or_warn(ssh_signature_filename.buf); + + /* Strip CR from the line endings, in case we are on Windows. */ + remove_cr_after(signature, bottom); + +out: + if (key_file) + delete_tempfile(&key_file); + if (buffer_file) + delete_tempfile(&buffer_file); + strbuf_release(&signer_stderr); + strbuf_release(&ssh_signature_filename); + FREE_AND_NULL(ssh_signing_key_file); + return ret; +} From patchwork Tue Aug 3 13:45:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416403 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 10321C4320A for ; Tue, 3 Aug 2021 13:46:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E56E5603E9 for ; Tue, 3 Aug 2021 13:46:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236257AbhHCNq0 (ORCPT ); Tue, 3 Aug 2021 09:46:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47302 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236279AbhHCNqX (ORCPT ); Tue, 3 Aug 2021 09:46:23 -0400 Received: from mail-wm1-x32f.google.com (mail-wm1-x32f.google.com [IPv6:2a00:1450:4864:20::32f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 57AB6C06179A for ; Tue, 3 Aug 2021 06:46:11 -0700 (PDT) Received: by mail-wm1-x32f.google.com with SMTP id k4so12528557wms.3 for ; Tue, 03 Aug 2021 06:46:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=5wMfBYQQGn68BcMBLr5Rh1+p/RT8SGsQUyUXtfrs+5k=; b=V12n/1oS6jNre4+l6vDCDaWGRJ3rlfG02ZkJcGkH+YEC5paOjaTHNZAMEQFqfirMHg iY2tv/3qNLKduo576TCnuY0fgGfQo1dqZ15qIy+7hpWFj4md/IvRw5BfCcTJke+EJZAl QcIabsLIW5uu002TQ6vNQ9/MHJYZWw5/E2ooZiomg0OBBptPZtgdHQA2ZkGajp6ZsJk2 GSteznZ/u06CKd6Cx8c7S35v8UJXtfeGPLbBkEOsJeN9A3fmNxNszgR4LqBkXL6fFLDD xkYcI5g6jFZBX0Gjca5O+ycM7J5awsji+U17PPp4GJIRxrxpnwbuhVFAaHdSvP3PXJPg nDIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=5wMfBYQQGn68BcMBLr5Rh1+p/RT8SGsQUyUXtfrs+5k=; b=OfNkR7OJzUUSLEkgTll6E6/+B0cMsdlH7MjbTFW+YSggAIDdt3H1M7tP4S+FNB2c9T e3eBxMs/pkLKsmyL5sZVMuVGngqiylkv43h/omU4hRoo/AcPpiQRjUurjLeYOHY0wk2K duqZ+L31o3/03Cw3z3x8z0UzWX29aaorWNYnkJdiOsMSibVufqhUV97m3jr4YDYhUF8y CV4mo5DT8bghWKMA80DEsmXBjKYbjiJpc2tsCpzaAiLGBXn3yEabNmDJCSPx2dS547tB lq4sIfLCu3zO64volM6IIwOS8p0TCjhsXfHCer3MGVBTbQSg1kTizH98hSKt0t0A2K2V 0OFw== X-Gm-Message-State: AOAM532rSKoP3WvAhP8gArFI7Hv57n30JGGyIsNtXYkgm8C6c187TlwB KjYYR70Tr/wiq9KIni65OXv+4Bj8lVI= X-Google-Smtp-Source: ABdhPJyMajESWVb8QqQ9f2i537+VOiQPDr3Gco+tqSGgIHT1i9sGBSpYMZZYNIJwd5LDOrmoOh16jA== X-Received: by 2002:a7b:cb09:: with SMTP id u9mr4426224wmj.63.1627998369898; Tue, 03 Aug 2021 06:46:09 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id j14sm14649362wrr.16.2021.08.03.06.46.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:07 -0700 (PDT) Message-Id: <83ece42e1dee09968444abe808e030c9210a5ba8.1627998358.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:53 +0000 Subject: [PATCH v7 4/9] ssh signing: retrieve a default key from ssh-agent Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer If user.signingkey is not set and a ssh signature is requested we call gpg.ssh.defaultKeyCommand (typically "ssh-add -L") and use the first key we get Signed-off-by: Fabian Stelzer --- Documentation/config/gpg.txt | 6 +++ Documentation/config/user.txt | 4 +- gpg-interface.c | 70 ++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt index 88531b15f0f..9b95dd280c3 100644 --- a/Documentation/config/gpg.txt +++ b/Documentation/config/gpg.txt @@ -33,3 +33,9 @@ gpg.minTrustLevel:: * `marginal` * `fully` * `ultimate` + +gpg.ssh.defaultKeyCommand: + This command that will be run when user.signingkey is not set and a ssh + signature is requested. On successful exit a valid ssh public key is + expected in the first line of its output. To automatically use the first + available key from your ssh-agent set this to "ssh-add -L". diff --git a/Documentation/config/user.txt b/Documentation/config/user.txt index 2155128957c..ad78dce9ecb 100644 --- a/Documentation/config/user.txt +++ b/Documentation/config/user.txt @@ -40,4 +40,6 @@ user.signingKey:: key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and corresponds to the private key used for signing. The private key needs to be available via ssh-agent. Alternatively it can be set to - a file containing a private key directly. + a file containing a private key directly. If not set git will call + gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first + key available. diff --git a/gpg-interface.c b/gpg-interface.c index 7ca682ac6d6..3a0cca1b1d2 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -6,8 +6,10 @@ #include "gpg-interface.h" #include "sigchain.h" #include "tempfile.h" +#include "alias.h" static char *configured_signing_key; +static const char *ssh_default_key_command; static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { @@ -21,6 +23,7 @@ struct gpg_format { size_t signature_size); int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); + const char *(*get_default_key)(void); }; static const char *openpgp_verify_args[] = { @@ -56,6 +59,8 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); +static const char *get_default_ssh_signing_key(void); + static struct gpg_format gpg_format[] = { { .name = "openpgp", @@ -64,6 +69,7 @@ static struct gpg_format gpg_format[] = { .sigs = openpgp_sigs, .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, + .get_default_key = NULL, }, { .name = "x509", @@ -72,6 +78,7 @@ static struct gpg_format gpg_format[] = { .sigs = x509_sigs, .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, + .get_default_key = NULL, }, { .name = "ssh", @@ -79,7 +86,8 @@ static struct gpg_format gpg_format[] = { .verify_args = ssh_verify_args, .sigs = ssh_sigs, .verify_signed_buffer = NULL, /* TODO */ - .sign_buffer = sign_buffer_ssh + .sign_buffer = sign_buffer_ssh, + .get_default_key = get_default_ssh_signing_key, }, }; @@ -453,6 +461,12 @@ int git_gpg_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "gpg.ssh.defaultkeycommand")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&ssh_default_key_command, var, value); + } + if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program")) fmtname = "openpgp"; @@ -470,11 +484,63 @@ int git_gpg_config(const char *var, const char *value, void *cb) return 0; } +/* Returns the first public key from an ssh-agent to use for signing */ +static const char *get_default_ssh_signing_key(void) +{ + struct child_process ssh_default_key = CHILD_PROCESS_INIT; + int ret = -1; + struct strbuf key_stdout = STRBUF_INIT, key_stderr = STRBUF_INIT; + struct strbuf **keys; + char *key_command = NULL; + const char **argv; + int n; + char *default_key = NULL; + + if (!ssh_default_key_command) + die(_("either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured")); + + key_command = xstrdup(ssh_default_key_command); + n = split_cmdline(key_command, &argv); + + if (n < 0) + die("malformed build-time gpg.ssh.defaultKeyCommand: %s", + split_cmdline_strerror(n)); + + strvec_pushv(&ssh_default_key.args, argv); + ret = pipe_command(&ssh_default_key, NULL, 0, &key_stdout, 0, + &key_stderr, 0); + + if (!ret) { + keys = strbuf_split_max(&key_stdout, '\n', 2); + if (keys[0] && starts_with(keys[0]->buf, "ssh-")) { + default_key = strbuf_detach(keys[0], NULL); + } else { + warning(_("gpg.ssh.defaultKeycommand succeeded but returned no keys: %s %s"), + key_stderr.buf, key_stdout.buf); + } + + strbuf_list_free(keys); + } else { + warning(_("gpg.ssh.defaultKeyCommand failed: %s %s"), + key_stderr.buf, key_stdout.buf); + } + + free(key_command); + free(argv); + strbuf_release(&key_stdout); + + return default_key; +} + const char *get_signing_key(void) { if (configured_signing_key) return configured_signing_key; - return git_committer_info(IDENT_STRICT|IDENT_NO_DATE); + if (use_format->get_default_key) { + return use_format->get_default_key(); + } + + return git_committer_info(IDENT_STRICT | IDENT_NO_DATE); } int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) From patchwork Tue Aug 3 13:45:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416405 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C8A6FC4338F for ; Tue, 3 Aug 2021 13:46:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B0D9B603E9 for ; Tue, 3 Aug 2021 13:46:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236281AbhHCNq1 (ORCPT ); Tue, 3 Aug 2021 09:46:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47308 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236260AbhHCNqZ (ORCPT ); Tue, 3 Aug 2021 09:46:25 -0400 Received: from mail-wr1-x435.google.com (mail-wr1-x435.google.com [IPv6:2a00:1450:4864:20::435]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 52AE8C06179C for ; Tue, 3 Aug 2021 06:46:12 -0700 (PDT) Received: by mail-wr1-x435.google.com with SMTP id m12so20603487wru.12 for ; Tue, 03 Aug 2021 06:46:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=mYYoLLs82wfDPkXZYjwM5QHY5QNqlpVjf9ujMmu4ZK8=; b=MZ23pWaN6gZoQAagqoyjSzb/v50FCBZwoWJzxIw9luMKJowTdu4PQpvP3QDJzZC3kR faTkvW9i4WzOfOmFLo9tV997NmEKrDSlpc/F+1sQZSaG2WHMdU5mc4evMfriGto996JM 07JntfIPz6Lelmm+71NL7si2xFDSgej0CT4DJMq6X1f1KXpOzFmrWx+YuogkxbDnnlUj vHumLZO6RppQCxtKNut3PwHf4TNZtXMP0iCH3N52H2OFedbzHCk6+orUhJlnRAeBw29s XgYKt+hMk71Ndn/mfELyfPJRA4IcyIlR7QvW8NRbaRgvdyklDtfpPf3gdaGgTKzZDWhg OBdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=mYYoLLs82wfDPkXZYjwM5QHY5QNqlpVjf9ujMmu4ZK8=; b=gI1UzPcAcfmUjx0Tw5/VD2POaGLuHqH81xg0H8aEhWE7ClPBr6r9LZY4IdrUncZNq1 SCyF7/MW6EMBks0OLbkib4eA5dmuY/Hru+mvjAZMCRp3V5vqJlStgfF2AbanHr2tVsTk 5AimbEKJO0o9UiuxH+1BkZDYOuUx6C1RbfROZeauIuZ6+LHLKOqIFXYdaJxD4KGiBzaS lCnILTQl9tCKxzVIruCf5hNxh97aUQARBfvHIp6wf04zXxv+gHweHEzOmllAbsAYQ3Ox BvIwdVLpVvFEH37KgqGdxIl94UO/mwAMXaK6drtHnwyLJO13EcW0fIwNCUIZ8HaymjdT H12g== X-Gm-Message-State: AOAM530VKfQPQxZzyuWQipcyd3E4mW+OlxoX8zEqXULOfwzZrl4rONGs e/LskGE4/WofRDR7YJ0PQNJUNGeLYMQ= X-Google-Smtp-Source: ABdhPJyu9tLGth8SjT1n5RnOnvLL/BTgODiE052tS1y8kfQOOF9Fw1oiNfESOhL/w57NRpKKkEIapQ== X-Received: by 2002:adf:e587:: with SMTP id l7mr22824773wrm.411.1627998370950; Tue, 03 Aug 2021 06:46:10 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id t1sm13330704wma.28.2021.08.03.06.46.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:10 -0700 (PDT) Message-Id: <76bc9eb407969a6d99a86072ef33894ed5948272.1627998358.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:54 +0000 Subject: [PATCH v7 5/9] ssh signing: provide a textual signing_key_id Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer For ssh the user.signingkey can be a filename/path or even a literal ssh pubkey. In push certs and textual output we prefer the ssh fingerprint instead. Signed-off-by: Fabian Stelzer --- gpg-interface.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ gpg-interface.h | 6 ++++++ send-pack.c | 8 +++---- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/gpg-interface.c b/gpg-interface.c index 3a0cca1b1d2..0f1c6a02e53 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -24,6 +24,7 @@ struct gpg_format { int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); const char *(*get_default_key)(void); + const char *(*get_key_id)(void); }; static const char *openpgp_verify_args[] = { @@ -61,6 +62,8 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, static const char *get_default_ssh_signing_key(void); +static const char *get_ssh_key_id(void); + static struct gpg_format gpg_format[] = { { .name = "openpgp", @@ -70,6 +73,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "x509", @@ -79,6 +83,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "ssh", @@ -88,6 +93,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = NULL, /* TODO */ .sign_buffer = sign_buffer_ssh, .get_default_key = get_default_ssh_signing_key, + .get_key_id = get_ssh_key_id, }, }; @@ -484,6 +490,41 @@ int git_gpg_config(const char *var, const char *value, void *cb) return 0; } +static char *get_ssh_key_fingerprint(const char *signing_key) +{ + struct child_process ssh_keygen = CHILD_PROCESS_INIT; + int ret = -1; + struct strbuf fingerprint_stdout = STRBUF_INIT; + struct strbuf **fingerprint; + + /* + * With SSH Signing this can contain a filename or a public key + * For textual representation we usually want a fingerprint + */ + if (starts_with(signing_key, "ssh-")) { + strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", "-", NULL); + ret = pipe_command(&ssh_keygen, signing_key, + strlen(signing_key), &fingerprint_stdout, 0, + NULL, 0); + } else { + strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", + configured_signing_key, NULL); + ret = pipe_command(&ssh_keygen, NULL, 0, &fingerprint_stdout, 0, + NULL, 0); + } + + if (!!ret) + die_errno(_("failed to get the ssh fingerprint for key '%s'"), + signing_key); + + fingerprint = strbuf_split_max(&fingerprint_stdout, ' ', 3); + if (!fingerprint[1]) + die_errno(_("failed to get the ssh fingerprint for key '%s'"), + signing_key); + + return strbuf_detach(fingerprint[1], NULL); +} + /* Returns the first public key from an ssh-agent to use for signing */ static const char *get_default_ssh_signing_key(void) { @@ -532,6 +573,21 @@ static const char *get_default_ssh_signing_key(void) return default_key; } +static const char *get_ssh_key_id(void) { + return get_ssh_key_fingerprint(get_signing_key()); +} + +/* Returns a textual but unique representation of the signing key */ +const char *get_signing_key_id(void) +{ + if (use_format->get_key_id) { + return use_format->get_key_id(); + } + + /* GPG/GPGSM only store a key id on this variable */ + return get_signing_key(); +} + const char *get_signing_key(void) { if (configured_signing_key) diff --git a/gpg-interface.h b/gpg-interface.h index feac4decf8b..beefacbb1e9 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -64,6 +64,12 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, int git_gpg_config(const char *, const char *, void *); void set_signing_key(const char *); const char *get_signing_key(void); + +/* + * Returns a textual unique representation of the signing key in use + * Either a GPG KeyID or a SSH Key Fingerprint + */ +const char *get_signing_key_id(void); int check_signature(const char *payload, size_t plen, const char *signature, size_t slen, struct signature_check *sigc); diff --git a/send-pack.c b/send-pack.c index 5a79e0e7110..50cca7e439b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -341,13 +341,13 @@ static int generate_push_cert(struct strbuf *req_buf, { const struct ref *ref; struct string_list_item *item; - char *signing_key = xstrdup(get_signing_key()); + char *signing_key_id = xstrdup(get_signing_key_id()); const char *cp, *np; struct strbuf cert = STRBUF_INIT; int update_seen = 0; strbuf_addstr(&cert, "certificate version 0.1\n"); - strbuf_addf(&cert, "pusher %s ", signing_key); + strbuf_addf(&cert, "pusher %s ", signing_key_id); datestamp(&cert); strbuf_addch(&cert, '\n'); if (args->url && *args->url) { @@ -374,7 +374,7 @@ static int generate_push_cert(struct strbuf *req_buf, if (!update_seen) goto free_return; - if (sign_buffer(&cert, &cert, signing_key)) + if (sign_buffer(&cert, &cert, get_signing_key())) die(_("failed to sign the push certificate")); packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string); @@ -386,7 +386,7 @@ static int generate_push_cert(struct strbuf *req_buf, packet_buf_write(req_buf, "push-cert-end\n"); free_return: - free(signing_key); + free(signing_key_id); strbuf_release(&cert); return update_seen; } From patchwork Tue Aug 3 13:45:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416409 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C8702C432BE for ; Tue, 3 Aug 2021 13:46:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B0DB6603E9 for ; Tue, 3 Aug 2021 13:46:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236349AbhHCNqc (ORCPT ); Tue, 3 Aug 2021 09:46:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47316 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236271AbhHCNq0 (ORCPT ); Tue, 3 Aug 2021 09:46:26 -0400 Received: from mail-wm1-x32e.google.com (mail-wm1-x32e.google.com [IPv6:2a00:1450:4864:20::32e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8848BC061757 for ; Tue, 3 Aug 2021 06:46:14 -0700 (PDT) Received: by mail-wm1-x32e.google.com with SMTP id h24-20020a1ccc180000b029022e0571d1a0so2163746wmb.5 for ; Tue, 03 Aug 2021 06:46:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=3HhjTTdGfVP8AJ7sW+kq75MNnpwl0Hk8UixdL1hvsjU=; b=Twle34fumiV/8EyQ1CBfn6HG7o2i+nuS9j9laCN5rD1Mu5Z/8MpS5B0+tUWRARPoi9 /0c8PdOTNG99FpgdI9Id9ArASTTJ5IRhkg7ZHZQXC4+qFhBMGvi3zwp0R8W6tyIbkCXi F/VxILWMUGn5E1jI7+ZFtnrYl3DEraQHP53DKF2xSEPLVd6ZBe6qC0Alut99AoZnOkQE 3pFTc+pWNagY7PvrkPShnRaYv0txIeIRyNvN8EYvtlqPH0aqYdvUfofIUwPcdxT+7/jn bz3iJzBUeAnictZa7sOYtMyeZEwSOKC8TasSgWmcP7KypbrKMashZvbiWoLLGUUmL+jD FhhA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=3HhjTTdGfVP8AJ7sW+kq75MNnpwl0Hk8UixdL1hvsjU=; b=YuLdL2a09ZMS/AbY/Yoe4ziZq2a7pK1SYFAFT6L6Tq+tp2Y5XmZXANs+j2KKoN12ax zj0bcE/+VmAsrbKDE7ljqt0CFDNp80Ud90Lo4JezfigjNVUHykKwQEBgi88Q9m7AiMHm 7ZqjkvY61UrUGaxGvwsHzp7M2IpiKKYOPAz2KtRp8yfHjDdqfMQ+0wbQjBV9qciuwA7g FzDaiJJdCVkv0NB+cvK613C0DCo8khU68aqH6CL79//pm43RZTr2FHQBl5c+RQSo1s5T 5hP4ynzNYFQPIGC3yBKDf5T2ACF48Ub6Hn5RMyseiMowenebqMvY2tZImRgp3OORscQz kVAQ== X-Gm-Message-State: AOAM531JeVBfhVROZrO30jRemXNTFpU64KK+2f08Bb/8xFXadqtnwIRE 4klFiAyaDnbeRU8CPcyWgfOY63Y//ig= X-Google-Smtp-Source: ABdhPJzhgb6G0Oihn27TwTMdxr/3EBQkyOakuuxFhchpcDZbdSekKvqez8RqEL613O130BzVXvTmIQ== X-Received: by 2002:a1c:2784:: with SMTP id n126mr2835152wmn.32.1627998373133; Tue, 03 Aug 2021 06:46:13 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id n8sm14435648wrx.46.2021.08.03.06.46.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:12 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:55 +0000 Subject: [PATCH v7 6/9] ssh signing: verify signatures using ssh-keygen Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer To verify a ssh signature we first call ssh-keygen -Y find-principal to look up the signing principal by their public key from the allowedSignersFile. If the key is found then we do a verify. Otherwise we only validate the signature but can not verify the signers identity. Verification uses the gpg.ssh.allowedSignersFile (see ssh-keygen(1) "ALLOWED SIGNERS") which contains valid public keys and a principal (usually user@domain). Depending on the environment this file can be managed by the individual developer or for example generated by the central repository server from known ssh keys with push access. This file is usually stored outside the repository, but if the repository only allows signed commits/pushes, the user might choose to store it in the repository. To revoke a key put the public key without the principal prefix into gpg.ssh.revocationKeyring or generate a KRL (see ssh-keygen(1) "KEY REVOCATION LISTS"). The same considerations about who to trust for verification as with the allowedSignersFile apply. Using SSH CA Keys with these files is also possible. Add "cert-authority" as key option between the principal and the key to mark it as a CA and all keys signed by it as valid for this CA. See "CERTIFICATES" in ssh-keygen(1). Signed-off-by: Fabian Stelzer --- Documentation/config/gpg.txt | 35 ++++++ builtin/receive-pack.c | 4 + gpg-interface.c | 209 ++++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 2 deletions(-) diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt index 9b95dd280c3..51a756b2f15 100644 --- a/Documentation/config/gpg.txt +++ b/Documentation/config/gpg.txt @@ -39,3 +39,38 @@ gpg.ssh.defaultKeyCommand: signature is requested. On successful exit a valid ssh public key is expected in the first line of its output. To automatically use the first available key from your ssh-agent set this to "ssh-add -L". + +gpg.ssh.allowedSignersFile:: + A file containing ssh public keys which you are willing to trust. + The file consists of one or more lines of principals followed by an ssh + public key. + e.g.: user1@example.com,user2@example.com ssh-rsa AAAAX1... + See ssh-keygen(1) "ALLOWED SIGNERS" for details. + The principal is only used to identify the key and is available when + verifying a signature. ++ +SSH has no concept of trust levels like gpg does. To be able to differentiate +between valid signatures and trusted signatures the trust level of a signature +verification is set to `fully` when the public key is present in the allowedSignersFile. +Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`. +Otherwise valid but untrusted signatures will still verify but show no principal +name of the signer. ++ +This file can be set to a location outside of the repository and every developer +maintains their own trust store. A central repository server could generate this +file automatically from ssh keys with push access to verify the code against. +In a corporate setting this file is probably generated at a global location +from automation that already handles developer ssh keys. ++ +A repository that only allows signed commits can store the file +in the repository itself using a path relative to the top-level of the working tree. +This way only committers with an already valid key can add or change keys in the keyring. ++ +Using a SSH CA key with the cert-authority option +(see ssh-keygen(1) "CERTIFICATES") is also valid. + +gpg.ssh.revocationFile:: + Either a SSH KRL or a list of revoked public keys (without the principal prefix). + See ssh-keygen(1) for details. + If a public key is found in this file then it will always be treated + as having trust level "never" and signatures will show as invalid. diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 2d1f97e1ca7..05dc8e160f8 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -131,6 +131,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb) { int status = parse_hide_refs_config(var, value, "receive"); + if (status) + return status; + + status = git_gpg_config(var, value, NULL); if (status) return status; diff --git a/gpg-interface.c b/gpg-interface.c index 0f1c6a02e53..9c1ef11a563 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -3,13 +3,14 @@ #include "config.h" #include "run-command.h" #include "strbuf.h" +#include "dir.h" #include "gpg-interface.h" #include "sigchain.h" #include "tempfile.h" #include "alias.h" static char *configured_signing_key; -static const char *ssh_default_key_command; +static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file; static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { @@ -55,6 +56,10 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc, struct gpg_format *fmt, const char *payload, size_t payload_size, const char *signature, size_t signature_size); +static int verify_ssh_signed_buffer(struct signature_check *sigc, + struct gpg_format *fmt, const char *payload, + size_t payload_size, const char *signature, + size_t signature_size); static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, @@ -90,7 +95,7 @@ static struct gpg_format gpg_format[] = { .program = "ssh-keygen", .verify_args = ssh_verify_args, .sigs = ssh_sigs, - .verify_signed_buffer = NULL, /* TODO */ + .verify_signed_buffer = verify_ssh_signed_buffer, .sign_buffer = sign_buffer_ssh, .get_default_key = get_default_ssh_signing_key, .get_key_id = get_ssh_key_id, @@ -357,6 +362,194 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc, return ret; } +static void parse_ssh_output(struct signature_check *sigc) +{ + const char *line, *principal, *search; + char *key = NULL; + + /* + * ssh-keygen output should be: + * Good "git" signature for PRINCIPAL with RSA key SHA256:FINGERPRINT + * + * or for valid but unknown keys: + * Good "git" signature with RSA key SHA256:FINGERPRINT + * + * Note that "PRINCIPAL" can contain whitespace, "RSA" and + * "SHA256" part could be a different token that names of + * the algorithms used, and "FINGERPRINT" is a hexadecimal + * string. By finding the last occurence of " with ", we can + * reliably parse out the PRINCIPAL. + */ + sigc->result = 'B'; + sigc->trust_level = TRUST_NEVER; + + line = xmemdupz(sigc->output, strcspn(sigc->output, "\n")); + + if (skip_prefix(line, "Good \"git\" signature for ", &line)) { + /* Valid signature and known principal */ + sigc->result = 'G'; + sigc->trust_level = TRUST_FULLY; + + /* Search for the last "with" to get the full principal */ + principal = line; + do { + search = strstr(line, " with "); + if (search) + line = search + 1; + } while (search != NULL); + sigc->signer = xmemdupz(principal, line - principal - 1); + } else if (skip_prefix(line, "Good \"git\" signature with ", &line)) { + /* Valid signature, but key unknown */ + sigc->result = 'G'; + sigc->trust_level = TRUST_UNDEFINED; + } else { + return; + } + + key = strstr(line, "key"); + if (key) { + sigc->fingerprint = xstrdup(strstr(line, "key") + 4); + sigc->key = xstrdup(sigc->fingerprint); + } else { + /* + * Output did not match what we expected + * Treat the signature as bad + */ + sigc->result = 'B'; + } +} + +static int verify_ssh_signed_buffer(struct signature_check *sigc, + struct gpg_format *fmt, const char *payload, + size_t payload_size, const char *signature, + size_t signature_size) +{ + struct child_process ssh_keygen = CHILD_PROCESS_INIT; + struct tempfile *buffer_file; + int ret = -1; + const char *line; + size_t trust_size; + char *principal; + struct strbuf ssh_keygen_out = STRBUF_INIT; + struct strbuf ssh_keygen_err = STRBUF_INIT; + + if (!ssh_allowed_signers) { + error(_("gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification")); + return -1; + } + + buffer_file = mks_tempfile_t(".git_vtag_tmpXXXXXX"); + if (!buffer_file) + return error_errno(_("could not create temporary file")); + if (write_in_full(buffer_file->fd, signature, signature_size) < 0 || + close_tempfile_gently(buffer_file) < 0) { + error_errno(_("failed writing detached signature to '%s'"), + buffer_file->filename.buf); + delete_tempfile(&buffer_file); + return -1; + } + + /* Find the principal from the signers */ + strvec_pushl(&ssh_keygen.args, fmt->program, + "-Y", "find-principals", + "-f", ssh_allowed_signers, + "-s", buffer_file->filename.buf, + NULL); + ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_keygen_out, 0, + &ssh_keygen_err, 0); + if (ret && strstr(ssh_keygen_err.buf, "usage:")) { + error(_("ssh-keygen -Y find-principals/verify is needed for ssh signature verification (available in openssh version 8.2p1+)")); + goto out; + } + if (ret || !ssh_keygen_out.len) { + /* + * We did not find a matching principal in the allowedSigners + * Check without validation + */ + child_process_init(&ssh_keygen); + strvec_pushl(&ssh_keygen.args, fmt->program, + "-Y", "check-novalidate", + "-n", "git", + "-s", buffer_file->filename.buf, + NULL); + pipe_command(&ssh_keygen, payload, payload_size, + &ssh_keygen_out, 0, &ssh_keygen_err, 0); + + /* + * Fail on unknown keys + * we still call check-novalidate to display the signature info + */ + ret = -1; + } else { + /* Check every principal we found (one per line) */ + for (line = ssh_keygen_out.buf; *line; + line = strchrnul(line + 1, '\n')) { + while (*line == '\n') + line++; + if (!*line) + break; + + trust_size = strcspn(line, "\n"); + principal = xmemdupz(line, trust_size); + + child_process_init(&ssh_keygen); + strbuf_release(&ssh_keygen_out); + strbuf_release(&ssh_keygen_err); + strvec_push(&ssh_keygen.args, fmt->program); + /* + * We found principals + * Try with each until we find a match + */ + strvec_pushl(&ssh_keygen.args, "-Y", "verify", + "-n", "git", + "-f", ssh_allowed_signers, + "-I", principal, + "-s", buffer_file->filename.buf, + NULL); + + if (ssh_revocation_file) { + if (file_exists(ssh_revocation_file)) { + strvec_pushl(&ssh_keygen.args, "-r", + ssh_revocation_file, NULL); + } else { + warning(_("ssh signing revocation file configured but not found: %s"), + ssh_revocation_file); + } + } + + sigchain_push(SIGPIPE, SIG_IGN); + ret = pipe_command(&ssh_keygen, payload, payload_size, + &ssh_keygen_out, 0, &ssh_keygen_err, 0); + sigchain_pop(SIGPIPE); + + FREE_AND_NULL(principal); + + if (!ret) + ret = !starts_with(ssh_keygen_out.buf, "Good"); + + if (!ret) + break; + } + } + + sigc->payload = xmemdupz(payload, payload_size); + strbuf_stripspace(&ssh_keygen_out, 0); + strbuf_stripspace(&ssh_keygen_err, 0); + strbuf_add(&ssh_keygen_out, ssh_keygen_err.buf, ssh_keygen_err.len); + sigc->output = strbuf_detach(&ssh_keygen_out, NULL); + sigc->gpg_status = xstrdup(sigc->output); + + parse_ssh_output(sigc); + +out: + if (buffer_file) + delete_tempfile(&buffer_file); + strbuf_release(&ssh_keygen_out); + strbuf_release(&ssh_keygen_err); + + return ret; +} + int check_signature(const char *payload, size_t plen, const char *signature, size_t slen, struct signature_check *sigc) { @@ -473,6 +666,18 @@ int git_gpg_config(const char *var, const char *value, void *cb) return git_config_string(&ssh_default_key_command, var, value); } + if (!strcmp(var, "gpg.ssh.allowedsignersfile")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&ssh_allowed_signers, var, value); + } + + if (!strcmp(var, "gpg.ssh.revocationfile")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&ssh_revocation_file, var, value); + } + if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program")) fmtname = "openpgp"; From patchwork Tue Aug 3 13:45:56 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416407 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 044C6C4338F for ; Tue, 3 Aug 2021 13:46:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D626060725 for ; Tue, 3 Aug 2021 13:46:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236345AbhHCNqb (ORCPT ); Tue, 3 Aug 2021 09:46:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47320 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236269AbhHCNq0 (ORCPT ); Tue, 3 Aug 2021 09:46:26 -0400 Received: from mail-wr1-x434.google.com (mail-wr1-x434.google.com [IPv6:2a00:1450:4864:20::434]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B5828C06175F for ; Tue, 3 Aug 2021 06:46:15 -0700 (PDT) Received: by mail-wr1-x434.google.com with SMTP id m12so20603711wru.12 for ; Tue, 03 Aug 2021 06:46:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=91/YnpIgds1l6Rpesnu1OP8h8gSRO2w5adP7F5/vA5E=; b=c2V99V4t9p+J0NUFyIrtrt+0EH0FbrVMVjmedlCTqwTpIN+TqEI0JqqqfE0gJDY3RX OlA5rO+53J7dwHVaJ5X0Le2thPOdoHHxIMGNFnnCcx9RcFNRvlAdDoipP/Xl3Vh35T0m nSHyj537NZvi2A+qpVSgbcA/qMpJAPvYygTPTJkaubiCvE0u0CbWdlLY/WDGtaBvW48r gCBK8pBfJ5SBJCnPzh/GAKKNnYVAIU7vGac+f9AcWiMwudMrVFsSLTuEbi9pwnxAI815 wWHiMKcZJ1zabzQSsoywl2tXp5MTI3Wkc3EOPccnxBjMgDm75v/WhCYcIW7WCP2Gv44d rvBw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=91/YnpIgds1l6Rpesnu1OP8h8gSRO2w5adP7F5/vA5E=; b=sdjuF2kDe/Ke14pKk+MKXR3jnvP19BpPdrpandbwyR8kPSvyyl4jG3oVRt/lXgiXdC l4hTg4lsVO5AvA0VCKLN0YlUuiulcL9CNbn8dppEsIl8Nbl7E9E3deYm7yWLitVhQkO+ a05i9zRfhBtASesaUdBDjVzQ+yxKlG+qQ2Mq+O13E0Uvs9U9Fn7qcGUDDRA4yi4n3Me0 ps7LOv5JcQurL8SGhsFJZLqENi/7nHYw8dTAZR/uaiHy6J4k7c/9tdpcgpt1NyQ6gQOf pV6piDwSYOFg+PBK6t7CwpDLlPIZvmEWF2TFZ1C5mfFu7k0YgH88LA69EEHPPCwxzC1f fuuA== X-Gm-Message-State: AOAM533ETpN0a+WGcjZymKIXH4f7ue0wmmDsjxaW40/C4o92FEWhtdcE froHMuMKHFS1oP8/A77uB6DW4MnXeSk= X-Google-Smtp-Source: ABdhPJzqCa1fVwkpuEaARXmFIc4lUPa0TDiiaJqlKR4C4Fn1OuYh8PjcKJiFYQZt5QHMHT6E9bEfog== X-Received: by 2002:adf:fb05:: with SMTP id c5mr22974110wrr.55.1627998374277; Tue, 03 Aug 2021 06:46:14 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id t8sm15473593wmj.5.2021.08.03.06.46.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:13 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:56 +0000 Subject: [PATCH v7 7/9] ssh signing: duplicate t7510 tests for commits Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Signed-off-by: Fabian Stelzer --- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100755 t/t7528-signed-commit-ssh.sh diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh new file mode 100755 index 00000000000..3e093168eef --- /dev/null +++ b/t/t7528-signed-commit-ssh.sh @@ -0,0 +1,398 @@ +#!/bin/sh + +test_description='ssh signed commit tests' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPGSSH 'create signed commits' ' + test_oid_cache <<-\EOF && + header sha1:gpgsig + header sha256:gpgsig-sha256 + EOF + + test_when_finished "test_unconfig commit.gpgsign" && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + + echo 1 >file && git add file && + test_tick && git commit -S -m initial && + git tag initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -S -m second && + git tag second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout main && + test_tick && git merge -S side && + git tag merge && + + echo 4 >file && test_tick && git commit -a -m "fourth unsigned" && + git tag fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag fourth-signed && + + git config commit.gpgsign true && + echo 5 >file && test_tick && git commit -a -m "fifth signed" && + git tag fifth-signed && + + git config commit.gpgsign false && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag sixth-unsigned && + + git config commit.gpgsign true && + echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign && + git tag seventh-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ && + git tag seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" && + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + echo 9 | git commit-tree HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag ninth-unsigned $(cat oid) && + # explicit -S of course must sign. + echo 10 | git commit-tree -S HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag tenth-signed $(cat oid) && + + # --gpg-sign[=] must sign. + echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag eleventh-signed $(cat oid) && + echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag twelfth-signed-alt $(cat oid) +' + +test_expect_success GPGSSH 'verify and show signatures' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.mintrustlevel UNDEFINED && + ( + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed \ + eleventh-signed + do + git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned + do + test_must_fail git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt twelfth-signed-alt + do + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_must_fail git verify-commit eighth-signed-alt 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual +' + +test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel fully && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel marginal && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' ' + test_config gpg.minTrustLevel ultimate && + test_must_fail git verify-commit eighth-signed-alt +' + +test_expect_success GPGSSH 'verify signatures with --raw' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ( + for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + do + git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + do + test_must_fail git verify-commit --raw $commit 2>actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt + do + test_must_fail git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'proper header is used for hash algorithm' ' + git cat-file commit fourth-signed >output && + grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output +' + +test_expect_success GPGSSH 'show signed commit with signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git show -s initial >commit && + git show -s --show-signature initial >show && + git verify-commit -v initial >verify.1 2>verify.2 && + git cat-file commit initial >cat && + grep -v -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit && + grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg && + grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit && + test_cmp show.commit commit && + test_cmp show.gpg verify.2 && + test_cmp cat.commit verify.1 +' + +test_expect_success GPGSSH 'detect fudged signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + sed -e "s/^seventh/7th forged/" raw >forged1 && + git hash-object -w -t commit forged1 >forged1.commit && + test_must_fail git verify-commit $(cat forged1.commit) && + git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && + grep "${GPGSSH_BAD_SIGNATURE}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1 +' + +test_expect_success GPGSSH 'detect fudged signature with NUL' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + cat raw >forged2 && + echo Qwik | tr "Q" "\000" >>forged2 && + git hash-object -w -t commit forged2 >forged2.commit && + test_must_fail git verify-commit $(cat forged2.commit) && + git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && + grep "${GPGSSH_BAD_SIGNATURE}" actual2 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2 +' + +test_expect_success GPGSSH 'amending already signed commit' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git checkout fourth-signed^0 && + git commit --amend -S --no-edit && + git verify-commit HEAD && + git show -s --show-signature HEAD >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual +' + +test_expect_success GPGSSH 'show good signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + cat >expect.tmpl <<-\EOF && + G + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show bad signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect <<-\EOF && + B + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + U + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with undefined trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + undefined + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + fully + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show lack of signature with custom format' ' + cat >expect <<-\EOF && + N + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config log.showsignature true && + git show initial >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual +' + +test_expect_success GPGSSH 'check config gpg.format values' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.format ssh && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + +test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' ' + sed -e "/gpgsig/,/END PGP/d" forged1 >double-base && + sed -n -e "/gpgsig/,/END PGP/p" forged1 | \ + sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig && + gpg -o double-sig2.sig -u 29472784 --detach-sign double-base && + cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc && + sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \ + double-combined.asc > double-gpgsig && + sed -e "/committer/r double-gpgsig" double-base >double-commit && + git hash-object -w -t commit double-commit >double-commit.commit && + test_must_fail git verify-commit $(cat double-commit.commit) && + git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual && + grep "BAD signature from" double-actual && + grep "Good signature from" double-actual +' + +test_expect_failure GPGSSH 'show double signature with custom format (TODO)' ' + cat >expect <<-\EOF && + E + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual && + test_cmp expect actual +' + + +test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' ' + git init multiply-signed && + cd multiply-signed && + test_commit first && + echo 1 >second && + git add second && + tree=$(git write-tree) && + parent=$(git rev-parse HEAD^{commit}) && + git commit --gpg-sign -m second && + git cat-file commit HEAD && + # Avoid trailing whitespace. + sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF && + Qtree $tree + Qparent $parent + Qauthor A U Thor 1112912653 -0700 + Qcommitter C O Mitter 1112912653 -0700 + Qgpgsig -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC + Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ== + Q =tQ0N + Q -----END PGP SIGNATURE----- + Qgpgsig-sha256 -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO + Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw== + Q =pIwP + Q -----END PGP SIGNATURE----- + Q + Qsecond + EOF + head=$(git hash-object -t commit -w commit) && + git reset --hard $head && + git verify-commit $head 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual +' + +test_done From patchwork Tue Aug 3 13:45:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416411 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E4CEAC432BE for ; Tue, 3 Aug 2021 13:46:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CDF2B60F94 for ; Tue, 3 Aug 2021 13:46:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236396AbhHCNrA (ORCPT ); Tue, 3 Aug 2021 09:47:00 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47330 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236260AbhHCNq2 (ORCPT ); Tue, 3 Aug 2021 09:46:28 -0400 Received: from mail-wm1-x336.google.com (mail-wm1-x336.google.com [IPv6:2a00:1450:4864:20::336]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D20AFC0613D5 for ; Tue, 3 Aug 2021 06:46:16 -0700 (PDT) Received: by mail-wm1-x336.google.com with SMTP id n11so12528483wmd.2 for ; Tue, 03 Aug 2021 06:46:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=I7hZ23nIfuZkY09MRRxhDxKyARMZ+R9cVCg/5TvsapA=; b=tiSWQS68F2YUvTfpEXo9hCGUWkRs12cUlekBm8U52DbMLcNNnKtazAnmWsDGIlYl8Z L6TTkUKmNfhCC6KaeYfAJqCemIGscWJls6Sto1S7wrOGaZ5oMCs5VIOARB0nreX+wKAJ a2fWMzW50sqYC9N7hQ3dUrLCaU85tJlYcPVm3NrNfLeGVZ9u3HDIGvHn80X/9gHIwbes /eK/arCCCFwcSvSA+xC+OFA+OfLalUHByCkAL40t7H0cNjYvYn52P6Yxm+La0u0l9DFx uKbIePvy1ZHLGQ6mZ66rnkLUMU9aLoQoCavdPoo+5DJOZ2z+gUZi8H8q79ZdIoLj2kxY VrPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=I7hZ23nIfuZkY09MRRxhDxKyARMZ+R9cVCg/5TvsapA=; b=DkOF/I9omaogx8pNUEl/5kZjeHYPWy/J/spVTjaTFxgMTohiUZQkb55STMeNu9P/ep fogfk28YZqN1fwTLW7833XLYSLx49o+8GzSKF/JDvBF/vZaKj00eDTaM4IAkthPlRT9Y mMCA/Ih4yeq9FLbsVDZw0DspVU4d6d0v3OEwUBohK9Zof9f8tq0hVwv+s9CXcP9+4RDa zQMiFuiD2PBNNc9Zd8DniZc8e7FpKX70ktVar3quvMkxENoPrIeV/nrb/E7YlQwEa58I REzT6Ev4UgcRPEqkSTc1edwuBloEp0BTU3Cq+pJAHueXenWBH/DJhFmzAOXWX8pkwzX7 4hoA== X-Gm-Message-State: AOAM5301varW1rIxdysMuJpDh3gzCFVsEsvTMWdOuF2pBZkmkJ+ksxuL mQaOd9BG0nyu2IOpisH6t3+IaYrV7xQ= X-Google-Smtp-Source: ABdhPJwZApw2HMJc9tr3B8A4x+L/3TXeYbn+/T0rg3dqzpnjWaaG0RRfrmq//NMGrJvOXRTETg5AFg== X-Received: by 2002:a05:600c:2306:: with SMTP id 6mr22257254wmo.115.1627998375403; Tue, 03 Aug 2021 06:46:15 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id q5sm15144819wrx.33.2021.08.03.06.46.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:14 -0700 (PDT) Message-Id: <0763517d62d25c4ddb907570628ae64db5e14e41.1627998358.git.gitgitgadget@gmail.com> In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:57 +0000 Subject: [PATCH v7 8/9] ssh signing: tests for logs, tags & push certs Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Signed-off-by: Fabian Stelzer --- t/t4202-log.sh | 23 +++++ t/t5534-push-signed.sh | 101 +++++++++++++++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100755 t/t7031-verify-tag-signed-ssh.sh diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 9dfead936b7..6a650dacd6e 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' ' git commit -S -m signed_commit ' +test_expect_success GPGSSH 'setup sshkey signed branch' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_when_finished "git reset --hard && git checkout main" && + git checkout -b signed-ssh main && + echo foo >foo && + git add foo && + git commit -S -m signed_commit +' + test_expect_success GPGSM 'log x509 fingerprint' ' echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect && git log -n1 --format="%GF | %GP" signed-x509 >actual && @@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' ' test_cmp expect actual ' +test_expect_success GPGSSH 'log ssh key fingerprint' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect && + git log -n1 --format="%GF | %GP" signed-ssh >actual && + test_cmp expect actual +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual && @@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' ' grep "^| gpgsm: Good signature" actual ' +test_expect_success GPGSSH 'log --graph --show-signature ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git log --graph --show-signature -n1 signed-ssh >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout main" && git checkout -b plain main && diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index bba768f5ded..24d374adbae 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -137,6 +137,53 @@ test_expect_success GPG 'signed push sends push certificate' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPGSSH 'ssh signed push sends push certificate' ' + prepare_dst && + mkdir -p dst/.git/hooks && + git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git -C dst config receive.certnonceseed sekrit && + write_script dst/.git/hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + + cat >../push-cert-status <expect && + + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && + test_cmp expect dst/push-cert-status +' + test_expect_success GPG 'inconsistent push options in signed push not allowed' ' # First, invoke receive-pack with dummy input to obtain its preamble. prepare_dst && @@ -276,6 +323,60 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPGSSH 'fail without key and heed user.signingkey ssh' ' + test_config gpg.format ssh && + prepare_dst && + mkdir -p dst/.git/hooks && + git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git -C dst config receive.certnonceseed sekrit && + write_script dst/.git/hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + + cat >../push-cert-status <expect && + + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && + test_cmp expect dst/push-cert-status +' + test_expect_success GPG 'failed atomic push does not execute GPG' ' prepare_dst && git -C dst config receive.certnonceseed sekrit && diff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh new file mode 100755 index 00000000000..06c9dd6c933 --- /dev/null +++ b/t/t7031-verify-tag-signed-ssh.sh @@ -0,0 +1,161 @@ +#!/bin/sh + +test_description='signed tag tests' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPGSSH 'create signed tags ssh' ' + test_when_finished "test_unconfig commit.gpgsign" && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + + echo 1 >file && git add file && + test_tick && git commit -m initial && + git tag -s -m initial initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -m second && + git tag -s -m second second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout main && + test_tick && git merge -S side && + git tag -s -m merge merge && + + echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" && + git tag -a -m fourth-unsigned fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag -s -m fourth fourth-signed && + + echo 5 >file && test_tick && git commit -a -m "fifth" && + git tag fifth-unsigned && + + git config commit.gpgsign true && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag -a -m sixth sixth-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ && + git tag -m seventh -s seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth && + git tag -u"${GPGSSH_KEY_UNTRUSTED}" -m eighth eighth-signed-alt +' + +test_expect_success GPGSSH 'verify and show ssh signatures' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ( + for tag in initial second merge fourth-signed sixth-signed seventh-signed + do + git verify-tag $tag 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in fourth-unsigned fifth-unsigned sixth-unsigned + do + test_must_fail git verify-tag $tag 2>actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in eighth-signed-alt + do + test_must_fail git verify-tag $tag 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${GPGSSH_KEY_NOT_TRUSTED}" actual && + echo $tag OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'detect fudged ssh signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file tag seventh-signed >raw && + sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && + git hash-object -w -t tag forged1 >forged1.tag && + test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && + grep "${GPGSSH_BAD_SIGNATURE}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1 +' + +test_expect_success GPGSSH 'verify ssh signatures with --raw' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ( + for tag in initial second merge fourth-signed sixth-signed seventh-signed + do + git verify-tag --raw $tag 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in fourth-unsigned fifth-unsigned sixth-unsigned + do + test_must_fail git verify-tag --raw $tag 2>actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in eighth-signed-alt + do + test_must_fail git verify-tag --raw $tag 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $tag OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'verify signatures with --raw ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git verify-tag --raw sixth-signed 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo sixth-signed OK +' + +test_expect_success GPGSSH 'verify multiple tags ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + tags="seventh-signed sixth-signed" && + for i in $tags + do + git verify-tag -v --raw $i || return 1 + done >expect.stdout 2>expect.stderr.1 && + grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" expect.stderr && + git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 && + grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual.stderr && + test_cmp expect.stdout actual.stdout && + test_cmp expect.stderr actual.stderr +' + +test_expect_success GPGSSH 'verifying tag with --format - ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect <<-\EOF && + tagname : fourth-signed + EOF + git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'verifying a forged tag with --format should fail silently - ssh' ' + test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && + test_must_be_empty actual-forged +' + +test_done From patchwork Tue Aug 3 13:45:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabian Stelzer X-Patchwork-Id: 12416413 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-12.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D9B9DC4338F for ; Tue, 3 Aug 2021 13:46:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C1C7E60F46 for ; Tue, 3 Aug 2021 13:46:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236406AbhHCNrE (ORCPT ); Tue, 3 Aug 2021 09:47:04 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47344 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236342AbhHCNqb (ORCPT ); Tue, 3 Aug 2021 09:46:31 -0400 Received: from mail-wm1-x335.google.com (mail-wm1-x335.google.com [IPv6:2a00:1450:4864:20::335]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0DA66C06179A for ; Tue, 3 Aug 2021 06:46:18 -0700 (PDT) Received: by mail-wm1-x335.google.com with SMTP id b128so12519729wmb.4 for ; Tue, 03 Aug 2021 06:46:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=message-id:in-reply-to:references:from:date:subject:fcc :content-transfer-encoding:mime-version:to:cc; bh=FYc7gP4ouZ+tmGy3TTaguZ1NmPWmn5xw+ch3KO8SFuM=; b=JBRIhjRpddTDpaPoltMBSd+WlickRneIX8cQdSw9GnahOcVGmtQ4FK2NpeKxD1TFQ6 QhNj6tb3H9EKX8WJ2v2plQgcZNFd5Rxq3kEdCuzoT8DYnv09MMF+nWU1NF9LaPPWIr+/ 1IxrUVyYlMeTD0AeP5dcMGrFwKm+H/xw4bmTYVnUaeFAcum2dOZzx/wkI1wiAvzHLuqK N9GXk+D2Nh16FKr5ubMzl3EqUMDIKOiDBguYMwfSTP9DWbo4sBPwEXBpLqPSGVgO7H9q e7zY4Hd8NqeidKie74Ycgrs2kkZXsxiDb1vFIKHQVHf5ST7dtY6ZyPT2sdlNSscCPd3D 3caA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:in-reply-to:references:from:date :subject:fcc:content-transfer-encoding:mime-version:to:cc; bh=FYc7gP4ouZ+tmGy3TTaguZ1NmPWmn5xw+ch3KO8SFuM=; b=UfOWocBX42dPy/RUM5swmphwt+bXy7cJeI12cPq4RvQe2sfHoIwbqLt+Zix5+x30nS Im1oS9DP7SWQLM1+6cs/HCFIIRmf82ep35cSF3Fs/Hb2ufRnGvpX4iY8oQpXOcVDaBEi qdzcP3fV1Fxf3vHtMSYuJH/EWDlQnNBENl+IURXln23QbOeEDROlRkjqRfalV61R1P3d QSpTi+KQxpLLMBGSTl7H5Jx1SnBSlJ68TZE5BPHeoT3nZoVs/kH9Lf+zNPVrNa3p+Q0G 0hjRC/QbEo391ie/rqCOSuBERkiJgQxYXcASoH32A4L71omURegt1dbHSLvG6hsgUmri sHlA== X-Gm-Message-State: AOAM533QNverbuZAPwoMig/5vKXRi+cl5mV/7NX/AoZ0G/olOILkxEsU BKhG6klPKfoffHQcNIxmJUZqsLTgrb8= X-Google-Smtp-Source: ABdhPJwWOZaajc/aceMe/1TJ7z8vgCkZ5/MPJIZZYU35x1AjHi+mxtddhNEgQOcwNolbBEode/v6dw== X-Received: by 2002:a1c:7506:: with SMTP id o6mr22680659wmc.155.1627998376718; Tue, 03 Aug 2021 06:46:16 -0700 (PDT) Received: from [127.0.0.1] ([13.74.141.28]) by smtp.gmail.com with ESMTPSA id f3sm14771606wro.30.2021.08.03.06.46.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Aug 2021 06:46:15 -0700 (PDT) Message-Id: In-Reply-To: References: Date: Tue, 03 Aug 2021 13:45:58 +0000 Subject: [PATCH v7 9/9] ssh signing: test that gpg fails for unkown keys Fcc: Sent MIME-Version: 1.0 To: git@vger.kernel.org Cc: Han-Wen Nienhuys , Fabian Stelzer , "brian m. carlson" , "Randall S. Becker" , Bagas Sanjaya , Hans Jerry Illikainen , =?utf-8?b?w4Z2YXIgQXJuZmrDtnI=?= =?utf-8?b?w7A=?= Bjarmason , Felipe Contreras , Eric Sunshine , Gwyneth Morgan , Jonathan Tan , Josh Steadmon , Fabian Stelzer , Fabian Stelzer Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org From: Fabian Stelzer From: Fabian Stelzer Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely. Signed-off-by: Fabian Stelzer --- t/t7510-signed-commit.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 8df5a74f1db..d65a0171f29 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' ' git tag eleventh-signed $(cat oid) && echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && test_line_count = 1 oid && - git tag twelfth-signed-alt $(cat oid) + git tag twelfth-signed-alt $(cat oid) && + + cat >keydetails <<-\EOF && + Key-Type: RSA + Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: unknown@git.com + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails && + echo 13 >file && git commit -a -S"unknown@git.com" -m thirteenth && + git tag thirteenth-signed && + DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch unknown@git.com | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && + gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && + gpg --batch --yes --delete-keys unknown@git.com ' test_expect_success GPG 'verify and show signatures' ' @@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPG 'verify-commit exits failure on unknown signature' ' + test_must_fail git verify-commit thirteenth-signed 2>actual && + ! grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep -q -F -e "No public key" -e "public key not found" actual +' + test_expect_success GPG 'verify-commit exits success on untrusted signature' ' git verify-commit eighth-signed-alt 2>actual && grep "Good signature from" actual && @@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' ' ' +# NEEDSWORK: This test relies on the test_tick commit/author dates from the first +# 'create signed commits' test even though it creates its own test_expect_success GPG 'verify-commit verifies multiply signed commits' ' git init multiply-signed && cd multiply-signed &&