{"thread":{"id":"56793","subject":"[PATCH v2 0/6] ssh signing: verify key lifetime","startedAt":"2021-10-27T08:06:22Z","lastAt":"2021-12-09T08:53:15Z","messageCount":60,"participants":["Fabian Stelzer","Junio C Hamano","Adam Dinwoodie","Ævar Arnfjörð Bjarmason","SZEDER Gábor"],"isPatch":true,"patchVersion":2,"patchTotal":6},"messages":[{"id":"439720","messageId":"20211027080616.619956-1-fs@gigacodes.de","threadId":"56793","inReplyTo":null,"subject":"[PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:10Z","receivedAt":"2021-10-27T08:06:22Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"This series adds key lifetime validity checks by parsing commit/tag\ndates from the paylod and passing them to the ssh-keygen operations.\n\nchanges since v1:\n - struct signature_check is now used to input payload data into\n   check_function\n - payload metadata parsing is completely internal to check_signature.\n   the caller only need to set the payload type in the sigc struct\n - small nits and readability fixes\n - removed payload_signer parameter. since we now use the struct we can extend\n   this later.\n\nSince the last 3 patches now only contain a minimal code change and the\ntests for the seperate callers we could merge them together. I don't\nmind either way.\n\nFabian Stelzer (6):\n  ssh signing: use sigc struct to pass payload\n  ssh signing: add key lifetime test prereqs\n  ssh signing: make verify-commit consider key lifetime\n  ssh signing: make git log verify key lifetime\n  ssh signing: make verify-tag consider key lifetime\n  ssh signing: make fmt-merge-msg consider key lifetime\n\n Documentation/config/gpg.txt     |  5 ++\n builtin/receive-pack.c           |  6 ++-\n commit.c                         |  6 ++-\n fmt-merge-msg.c                  |  5 +-\n gpg-interface.c                  | 87 ++++++++++++++++++++++++--------\n gpg-interface.h                  | 15 ++++--\n log-tree.c                       | 10 ++--\n t/lib-gpg.sh                     | 19 ++++++-\n t/t4202-log.sh                   | 43 ++++++++++++++++\n t/t6200-fmt-merge-msg.sh         | 54 ++++++++++++++++++++\n t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n tag.c                            |  5 +-\n 13 files changed, 303 insertions(+), 36 deletions(-)\n\n\nbase-commit: e9e5ba39a78c8f5057262d49e261b42a8660d5b9\n-- \n2.31.1\n\n"},{"id":"439721","messageId":"20211027080616.619956-2-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 1/6] ssh signing: use sigc struct to pass payload","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:11Z","receivedAt":"2021-10-27T08:06:23Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"To be able to extend the payload metadata with things like its creation\ntimestamp or the creators ident we remove the payload parameters to\ncheck_signature() and use the already existing sigc->payload field\ninstead, only adding the length field to the struct. This also allows\nus to get rid of the xmemdupz() calls in the verify functions. Since\nsigc is now used to input data as well as output the result move it to\nthe front of the function list.\n\n - Add payload_length to struct signature_check\n - Populate sigc.payload/payload_len on all call sites\n - Remove payload parameters to check_signature()\n - Remove payload parameters to internal verify_* functions and use sigc\n   instead\n - Remove xmemdupz() used for verbose output since payload is now already\n   populated.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n builtin/receive-pack.c |  6 ++++--\n commit.c               |  5 +++--\n fmt-merge-msg.c        |  4 ++--\n gpg-interface.c        | 37 +++++++++++++++++--------------------\n gpg-interface.h        |  6 +++---\n log-tree.c             |  8 ++++----\n tag.c                  |  4 ++--\n 7 files changed, 35 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d960..61ab63c2ea 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -769,8 +769,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n \n \t\tbogs = parse_signed_buffer(push_cert.buf, push_cert.len);\n-\t\tcheck_signature(push_cert.buf, bogs, push_cert.buf + bogs,\n-\t\t\t\tpush_cert.len - bogs, &sigcheck);\n+\t\tsigcheck.payload = xmemdupz(push_cert.buf, bogs);\n+\t\tsigcheck.payload_len = bogs;\n+\t\tcheck_signature(&sigcheck, push_cert.buf + bogs,\n+\t\t\t\tpush_cert.len - bogs);\n \n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\ndiff --git a/commit.c b/commit.c\nindex 551de4903c..64e040a99b 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1212,8 +1212,9 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\tsignature.len, sigc);\n+\n+\tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n+\tret = check_signature(sigc, signature.buf, signature.len);\n \n  out:\n \tstrbuf_release(&payload);\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5216191488..deca1ea3a3 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,8 +533,8 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n-\t\t\tif (check_signature(payload.buf, payload.len, sig.buf,\n-\t\t\t\t\t    sig.len, &sigc) &&\n+\t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\n \t\t\t\tstrbuf_addstr(&sig, \"gpg verification failed.\\n\");\n \t\t\telse\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 800d8caa67..760cf136db 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -19,8 +19,8 @@ struct gpg_format {\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n \tint (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {\n };\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -314,8 +314,8 @@ static void parse_gpg_output(struct signature_check *sigc)\n }\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process gpg = CHILD_PROCESS_INIT;\n@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,\n \t\t     NULL);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n-\tret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,\n+\tret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,\n \t\t\t   &gpg_stderr, 0);\n \tsigchain_pop(SIGPIPE);\n \n \tdelete_tempfile(&temp);\n \n \tret |= !strstr(gpg_stdout.buf, \"\\n[GNUPG:] GOODSIG \");\n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tsigc->output = strbuf_detach(&gpg_stderr, NULL);\n \tsigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);\n \n@@ -424,8 +423,8 @@ static void parse_ssh_output(struct signature_check *sigc)\n }\n \n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process ssh_keygen = CHILD_PROCESS_INIT;\n@@ -478,7 +477,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n \t\t\t     NULL);\n-\t\tpipe_command(&ssh_keygen, payload, payload_size,\n+\t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \n \t\t/*\n@@ -524,7 +523,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t}\n \n \t\t\tsigchain_push(SIGPIPE, SIG_IGN);\n-\t\t\tret = pipe_command(&ssh_keygen, payload, payload_size,\n+\t\t\tret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \t\t\tsigchain_pop(SIGPIPE);\n \n@@ -538,7 +537,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t}\n \t}\n \n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tstrbuf_stripspace(&ssh_keygen_out, 0);\n \tstrbuf_stripspace(&ssh_keygen_err, 0);\n \t/* Add stderr outputs to show the user actual ssh-keygen errors */\n@@ -560,8 +558,8 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \treturn ret;\n }\n \n-int check_signature(const char *payload, size_t plen, const char *signature,\n-\tsize_t slen, struct signature_check *sigc)\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen)\n {\n \tstruct gpg_format *fmt;\n \tint status;\n@@ -573,8 +571,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n-\tstatus = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,\n-\t\t\t\t\t   slen);\n+\tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\n \t\treturn !!status;\n@@ -591,7 +588,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)\n \t\t\t\t\t\t\t    sigc->output;\n \n \tif (flags & GPG_VERIFY_VERBOSE && sigc->payload)\n-\t\tfputs(sigc->payload, stdout);\n+\t\tfwrite(sigc->payload, 1, sigc->payload_len, stdout);\n \n \tif (output)\n \t\tfputs(output, stderr);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex beefacbb1e..5ee7d8b6b9 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -17,6 +17,7 @@ enum signature_trust_level {\n \n struct signature_check {\n \tchar *payload;\n+\tsize_t payload_len;\n \tchar *output;\n \tchar *gpg_status;\n \n@@ -70,9 +71,8 @@ const char *get_signing_key(void);\n  * Either a GPG KeyID or a SSH Key Fingerprint\n  */\n const char *get_signing_key_id(void);\n-int check_signature(const char *payload, size_t plen,\n-\t\t    const char *signature, size_t slen,\n-\t\t    struct signature_check *sigc);\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen);\n void print_signature_buffer(const struct signature_check *sigc,\n \t\t\t    unsigned flags);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 644893fd8c..a46cf60e1e 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,8 +513,8 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n-\tstatus = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\t signature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n \t\tshow_sig_lines(opt, status, \"No signature\\n\");\n \telse\n@@ -583,8 +583,8 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n-\t\tstatus = check_signature(payload.buf, payload.len,\n-\t\t\t\t\t signature.buf, signature.len, &sigc);\n+\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\n \t\t\tstrbuf_addstr(&verify_message, sigc.output);\n \t\telse\ndiff --git a/tag.c b/tag.c\nindex 3e18a41841..62fb09f5a5 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,8 +25,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\tsignature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tret = check_signature(&sigc, signature.buf, signature.len);\n \n \tif (!(flags & GPG_VERIFY_OMIT_STATUS))\n \t\tprint_signature_buffer(&sigc, flags);\n-- \n2.31.1\n\n"},{"id":"439722","messageId":"20211027080616.619956-3-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 2/6] ssh signing: add key lifetime test prereqs","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:12Z","receivedAt":"2021-10-27T08:06:26Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"if ssh-keygen supports -Overify-time, add test keys marked as expired,\nnot yet valid and valid both within the test_tick timeframe and outside of it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex f99ef3e859..139e7a45ea 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -90,6 +90,10 @@ test_lazy_prereq RFC1991 '\n GPGSSH_KEY_PRIMARY=\"${GNUPGHOME}/ed25519_ssh_signing_key\"\n GPGSSH_KEY_SECONDARY=\"${GNUPGHOME}/rsa_2048_ssh_signing_key\"\n GPGSSH_KEY_UNTRUSTED=\"${GNUPGHOME}/untrusted_ssh_signing_key\"\n+GPGSSH_KEY_EXPIRED=\"${GNUPGHOME}/expired_ssh_signing_key\"\n+GPGSSH_KEY_NOTYETVALID=\"${GNUPGHOME}/notyetvalid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDVALID=\"${GNUPGHOME}/timeboxed_valid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDINVALID=\"${GNUPGHOME}/timeboxed_invalid_ssh_signing_key\"\n GPGSSH_KEY_WITH_PASSPHRASE=\"${GNUPGHOME}/protected_ssh_signing_key\"\n GPGSSH_KEY_PASSPHRASE=\"super_secret\"\n GPGSSH_ALLOWED_SIGNERS=\"${GNUPGHOME}/ssh.all_valid.allowedSignersFile\"\n@@ -112,7 +116,20 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+'\n+\n+test_lazy_prereq GPGSSH_VERIFYTIME '\n+\t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n+\tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n+\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n+\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"439723","messageId":"20211027080616.619956-4-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 3/6] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:13Z","receivedAt":"2021-10-27T08:06:31Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"If valid-before/after dates are configured for this signatures key in the\nallowedSigners file then the verification should check if the key was valid at\nthe time the commit was made. This allows for graceful key rollover and\nrevoking keys without invalidating all previous commits.\nThis feature needs openssh > 8.8. Older ssh-keygen versions will simply\nignore this flag and use the current time.\nStrictly speaking this feature is available in 8.7, but since 8.7 has a\nbug that makes it unusable in another needed call we require 8.8.\n\nTimestamp information is present on most invocations of check_signature.\nHowever signer ident is not. We will need the signer email / name to be able\nto implement \"Trust on first use\" functionality later.\nSince the payload contains all necessary information we can parse it\nfrom there. The caller only needs to provide us some info about the\npayload by setting payload_type in the signature_check struct.\n\n - Add payload_type field & enum and payload_timestamp to struct\n   signature_check\n - Populate the timestamp when not already set if we know about the\n   payload type\n - Pass -Overify-time={payload_timestamp} in the users timezone to all\n   ssh-keygen verification calls\n - Set the payload type when verifying commits\n - Add tests for expired, not yet valid and keys having a commit date\n   outside of key validity as well as within\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n Documentation/config/gpg.txt |  5 ++++\n commit.c                     |  1 +\n gpg-interface.c              | 50 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h              |  9 +++++++\n t/t7528-signed-commit-ssh.sh | 42 ++++++++++++++++++++++++++++++\n 5 files changed, 107 insertions(+)\n\ndiff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt\nindex 4f30c7dbdd..c9be554c73 100644\n--- a/Documentation/config/gpg.txt\n+++ b/Documentation/config/gpg.txt\n@@ -64,6 +64,11 @@ A repository that only allows signed commits can store the file\n in the repository itself using a path relative to the top-level of the working tree.\n This way only committers with an already valid key can add or change keys in the keyring.\n +\n+Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &\n+valid-before options. Git will mark signatures as valid if the signing key was\n+valid at the time of the signatures creation. This allows users to change a\n+signing key without invalidating all previously made signatures.\n++\n Using a SSH CA key with the cert-authority option\n (see ssh-keygen(1) \"CERTIFICATES\") is also valid.\n \ndiff --git a/commit.c b/commit.c\nindex 64e040a99b..a348f085b2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1213,6 +1213,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n \tret = check_signature(sigc, signature.buf, signature.len);\n \ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 760cf136db..bb2d5b2c67 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -437,6 +437,13 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstruct strbuf ssh_principals_err = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_out = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_err = STRBUF_INIT;\n+\tstruct strbuf verify_time = STRBUF_INIT;\n+\tconst struct date_mode verify_date_mode = {\n+\t\t.type = DATE_STRFTIME,\n+\t\t.strftime_fmt = \"%Y%m%d%H%M%S\",\n+\t\t/* SSH signing key validity has no timezone information - Use the local timezone */\n+\t\t.local = 1,\n+\t};\n \n \tif (!ssh_allowed_signers) {\n \t\terror(_(\"gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification\"));\n@@ -454,11 +461,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\treturn -1;\n \t}\n \n+\tif (sigc->payload_timestamp)\n+\t\tstrbuf_addf(&verify_time, \"-Overify-time=%s\",\n+\t\t\tshow_date(sigc->payload_timestamp, 0, &verify_date_mode));\n+\n \t/* Find the principal from the signers */\n \tstrvec_pushl(&ssh_keygen.args, fmt->program,\n \t\t     \"-Y\", \"find-principals\",\n \t\t     \"-f\", ssh_allowed_signers,\n \t\t     \"-s\", buffer_file->filename.buf,\n+\t\t     verify_time.buf,\n \t\t     NULL);\n \tret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,\n \t\t\t   &ssh_principals_err, 0);\n@@ -476,6 +488,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-Y\", \"check-novalidate\",\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t     verify_time.buf,\n \t\t\t     NULL);\n \t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n@@ -510,6 +523,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t\t     \"-f\", ssh_allowed_signers,\n \t\t\t\t     \"-I\", principal,\n \t\t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t\t     verify_time.buf,\n \t\t\t\t     NULL);\n \n \t\t\tif (ssh_revocation_file) {\n@@ -554,10 +568,43 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstrbuf_release(&ssh_principals_err);\n \tstrbuf_release(&ssh_keygen_out);\n \tstrbuf_release(&ssh_keygen_err);\n+\tstrbuf_release(&verify_time);\n \n \treturn ret;\n }\n \n+static int parse_payload_metadata(struct signature_check *sigc)\n+{\n+\tconst char *ident_line = NULL;\n+\tsize_t ident_len;\n+\tstruct ident_split ident;\n+\tconst char *signer_header;\n+\n+\tswitch(sigc->payload_type) {\n+\t\tcase SIGNATURE_PAYLOAD_COMMIT:\n+\t\t\tsigner_header = \"committer\";\n+\t\t\tbreak;\n+\t\tcase SIGNATURE_PAYLOAD_TAG:\n+\t\t\tsigner_header = \"tagger\";\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\t/* Ignore unknown payload types */\n+\t\t\treturn 0;\n+\t}\n+\n+\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n+\tif (!ident_line || !ident_len)\n+\t\treturn 1;\n+\n+\tif (split_ident_line(&ident, ident_line, ident_len))\n+\t\treturn 1;\n+\n+\tif (!sigc->payload_timestamp && ident.date_begin && ident.date_end)\n+\t\tsigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n int check_signature(struct signature_check *sigc,\n \t\t    const char *signature, size_t slen)\n {\n@@ -571,6 +618,9 @@ int check_signature(struct signature_check *sigc,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n+\tif (parse_payload_metadata(sigc))\n+\t\treturn 1;\n+\n \tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 5ee7d8b6b9..b30cbdcd3d 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -15,9 +15,18 @@ enum signature_trust_level {\n \tTRUST_ULTIMATE,\n };\n \n+enum payload_type {\n+\tSIGNATURE_PAYLOAD_UNDEFINED,\n+\tSIGNATURE_PAYLOAD_COMMIT,\n+\tSIGNATURE_PAYLOAD_TAG,\n+\tSIGNATURE_PAYLOAD_PUSH_CERT,\n+};\n+\n struct signature_check {\n \tchar *payload;\n \tsize_t payload_len;\n+\tenum payload_type payload_type;\n+\ttimestamp_t payload_timestamp;\n \tchar *output;\n \tchar *gpg_status;\n \ndiff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh\nindex badf3ed320..dae76ded0c 100755\n--- a/t/t7528-signed-commit-ssh.sh\n+++ b/t/t7528-signed-commit-ssh.sh\n@@ -76,6 +76,23 @@ test_expect_success GPGSSH 'create signed commits' '\n \tgit tag twelfth-signed-alt $(cat oid)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.mintrustlevel UNDEFINED &&\n@@ -122,6 +139,31 @@ test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature'\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit succeeds with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-commit timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.minTrustLevel fully &&\n-- \n2.31.1\n\n"},{"id":"439724","messageId":"20211027080616.619956-7-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 6/6] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:16Z","receivedAt":"2021-10-27T08:06:33Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when generating merge messages to\nverify merged tags signatures key lifetimes.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n fmt-merge-msg.c          |  1 +\n t/t6200-fmt-merge-msg.sh | 54 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+)\n\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex deca1ea3a3..e4f7810be2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,6 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n+\t\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 06c5fb5615..2dd2423643 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\tgit checkout -b signed-expiry-ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success 'message for merging local branch' '\n \techo \"Merge branch ${apos}left${apos}\" >expected &&\n \n@@ -137,6 +157,40 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . expired-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . notyetvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedinvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success 'message for merging external branch' '\n \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n \n-- \n2.31.1\n\n"},{"id":"439725","messageId":"20211027080616.619956-5-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 4/6] ssh signing: make git log verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:14Z","receivedAt":"2021-10-27T08:06:40Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling git log.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n log-tree.c     |  2 ++\n t/t4202-log.sh | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex a46cf60e1e..d3e7a40b64 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,6 +513,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n@@ -583,6 +584,7 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n+\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 6a650dacd6..2b12baab77 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -1626,6 +1626,24 @@ test_expect_success GPGSSH 'setup sshkey signed branch' '\n \tgit commit -S -m signed_commit\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_config gpg.format ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSM 'log x509 fingerprint' '\n \techo \"F8BF62E0693D0694816377099909C779FA23FD65 | \" >expect &&\n \tgit log -n1 --format=\"%GF | %GP\" signed-x509 >actual &&\n@@ -1663,6 +1681,31 @@ test_expect_success GPGSSH 'log --graph --show-signature ssh' '\n \tgrep \"${GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 expired-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 notyetvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log show success with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedvalid-signed >actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedinvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPG 'log --graph --show-signature for merged tag' '\n \ttest_when_finished \"git reset --hard && git checkout main\" &&\n \tgit checkout -b plain main &&\n-- \n2.31.1\n\n"},{"id":"439726","messageId":"20211027080616.619956-6-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"[PATCH v2 5/6] ssh signing: make verify-tag consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-27T08:06:15Z","receivedAt":"2021-10-27T08:06:48Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling verify-tag.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t7031-verify-tag-signed-ssh.sh | 42 ++++++++++++++++++++++++++++++++\n tag.c                            |  1 +\n 2 files changed, 43 insertions(+)\n\ndiff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh\nindex 06c9dd6c93..1cb36b9ab8 100755\n--- a/t/t7031-verify-tag-signed-ssh.sh\n+++ b/t/t7031-verify-tag-signed-ssh.sh\n@@ -48,6 +48,23 @@ test_expect_success GPGSSH 'create signed tags ssh' '\n \tgit tag -u\"${GPGSSH_KEY_UNTRUSTED}\" -m eighth eighth-signed-alt\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show ssh signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \t(\n@@ -80,6 +97,31 @@ test_expect_success GPGSSH 'verify and show ssh signatures' '\n \t)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag succeeds with tag date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-tag timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'detect fudged ssh signature' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit cat-file tag seventh-signed >raw &&\ndiff --git a/tag.c b/tag.c\nindex 62fb09f5a5..dfbcd7fcc2 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,6 +25,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tret = check_signature(&sigc, signature.buf, signature.len);\n \n-- \n2.31.1\n\n"},{"id":"439829","messageId":"xmqqsfwmus5g.fsf@gitster.g","threadId":"56793","inReplyTo":"20211027080616.619956-4-fs@gigacodes.de","subject":"Re: [PATCH v2 3/6] ssh signing: make verify-commit consider key lifetime","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-27T20:30:51Z","receivedAt":"2021-10-27T20:30:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fabian Stelzer <fs@gigacodes.de> writes:\n\n> +static int parse_payload_metadata(struct signature_check *sigc)\n> +{\n> +\tconst char *ident_line = NULL;\n> +\tsize_t ident_len;\n> +\tstruct ident_split ident;\n> +\tconst char *signer_header;\n> +\n> +\tswitch(sigc->payload_type) {\n> +\t\tcase SIGNATURE_PAYLOAD_COMMIT:\n> +\t\t\tsigner_header = \"committer\";\n> +\t\t\tbreak;\n> +\t\tcase SIGNATURE_PAYLOAD_TAG:\n> +\t\t\tsigner_header = \"tagger\";\n> +\t\t\tbreak;\n> +\t\tdefault:\n> +\t\t\t/* Ignore unknown payload types */\n> +\t\t\treturn 0;\n> +\t}\n\nThe case arms should be indented to the same level as opening\nswitch().  Have SP between keyword \"switch\" and the expression\nthe statement switches on.\n\nMore importantly, can you explain why it is necessary to allow\ncallers to call this function with a random value in payload_type\nand have it silently succeed?  Isn't it a programming error that\ndeserves a call to BUG(\"...\")?\n\nThanks.\n"},{"id":"439860","messageId":"6a8748aa-9ed9-8aec-860b-a7f9b0ce9504@gigacodes.de","threadId":"56793","inReplyTo":"xmqqsfwmus5g.fsf@gitster.g","subject":"Re: [PATCH v2 3/6] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-10-28T08:01:56Z","receivedAt":"2021-10-28T08:02:18Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 27.10.21 22:30, Junio C Hamano wrote:\n> Fabian Stelzer <fs@gigacodes.de> writes:\n> \n>> +static int parse_payload_metadata(struct signature_check *sigc)\n>> +{\n>> +\tconst char *ident_line = NULL;\n>> +\tsize_t ident_len;\n>> +\tstruct ident_split ident;\n>> +\tconst char *signer_header;\n>> +\n>> +\tswitch(sigc->payload_type) {\n>> +\t\tcase SIGNATURE_PAYLOAD_COMMIT:\n>> +\t\t\tsigner_header = \"committer\";\n>> +\t\t\tbreak;\n>> +\t\tcase SIGNATURE_PAYLOAD_TAG:\n>> +\t\t\tsigner_header = \"tagger\";\n>> +\t\t\tbreak;\n>> +\t\tdefault:\n>> +\t\t\t/* Ignore unknown payload types */\n>> +\t\t\treturn 0;\n>> +\t}\n> \n> The case arms should be indented to the same level as opening\n> switch().  Have SP between keyword \"switch\" and the expression\n> the statement switches on.\n> \n\nThanks, will fix.\n\n> More importantly, can you explain why it is necessary to allow\n> callers to call this function with a random value in payload_type\n> and have it silently succeed?  Isn't it a programming error that\n> deserves a call to BUG(\"...\")?\n> \n> Thanks.\n> \n\nNot really. I will add a BUG() to the default case and only ignore the\nknown cases that we don't want to handle.\n\nThanks\n"},{"id":"440395","messageId":"CA+kUOakzQ5aEQf81rQULiz6FRdmCLnbhspt9e6behPS3sbZT8w@mail.gmail.com","threadId":"56793","inReplyTo":"20211027080616.619956-1-fs@gigacodes.de","subject":"Re: [PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Adam Dinwoodie","fromEmail":"adam@dinwoodie.org","sentAt":"2021-11-03T19:27:20Z","receivedAt":"2021-11-03T19:27:50Z","isPatch":true,"sender":{"key":"adam@dinwoodie.org","avatar":"https://avatars.githubusercontent.com/u/1397507?v=4"},"body":"On Wed, 27 Oct 2021 at 09:06, Fabian Stelzer <fs@gigacodes.de> wrote:\n> This series adds key lifetime validity checks by parsing commit/tag\n> dates from the paylod and passing them to the ssh-keygen operations.\n>\n> changes since v1:\n>  - struct signature_check is now used to input payload data into\n>    check_function\n>  - payload metadata parsing is completely internal to check_signature.\n>    the caller only need to set the payload type in the sigc struct\n>  - small nits and readability fixes\n>  - removed payload_signer parameter. since we now use the struct we can extend\n>    this later.\n>\n> Since the last 3 patches now only contain a minimal code change and the\n> tests for the seperate callers we could merge them together. I don't\n> mind either way.\n>\n> Fabian Stelzer (6):\n>   ssh signing: use sigc struct to pass payload\n>   ssh signing: add key lifetime test prereqs\n>   ssh signing: make verify-commit consider key lifetime\n>   ssh signing: make git log verify key lifetime\n>   ssh signing: make verify-tag consider key lifetime\n>   ssh signing: make fmt-merge-msg consider key lifetime\n>\n>  Documentation/config/gpg.txt     |  5 ++\n>  builtin/receive-pack.c           |  6 ++-\n>  commit.c                         |  6 ++-\n>  fmt-merge-msg.c                  |  5 +-\n>  gpg-interface.c                  | 87 ++++++++++++++++++++++++--------\n>  gpg-interface.h                  | 15 ++++--\n>  log-tree.c                       | 10 ++--\n>  t/lib-gpg.sh                     | 19 ++++++-\n>  t/t4202-log.sh                   | 43 ++++++++++++++++\n>  t/t6200-fmt-merge-msg.sh         | 54 ++++++++++++++++++++\n>  t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n>  t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n>  tag.c                            |  5 +-\n>  13 files changed, 303 insertions(+), 36 deletions(-)\n>\n>\n> base-commit: e9e5ba39a78c8f5057262d49e261b42a8660d5b9\n\nAs part of testing v2.34-rc0 on Cygwin, I've found this patch series\nis breaking t4202, t5534, and t6200.\n\nSpecifically, bisecting points to f265f2d630 (ssh signing: tests for\nlogs, tags & push certs, 2021-09-10) as breaking t4202 and t5534,\nwhile responsibility for t6200 seems to be 9d12546de9 (ssh signing:\nfmt-merge-msg tests & config parse, 2021-10-12).\n\nI've not yet done any investigation into the specifics of these\nfailures, but I wanted to report them early so other folks could get\non with investigating as appropriate.\n"},{"id":"440396","messageId":"20211103194558.xqcsaljvcoontnal@fs","threadId":"56793","inReplyTo":"CA+kUOakzQ5aEQf81rQULiz6FRdmCLnbhspt9e6behPS3sbZT8w@mail.gmail.com","subject":"Re: [PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-03T19:45:58Z","receivedAt":"2021-11-03T19:46:04Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 03.11.2021 19:27, Adam Dinwoodie wrote:\n>On Wed, 27 Oct 2021 at 09:06, Fabian Stelzer <fs@gigacodes.de> wrote:\n>> This series adds key lifetime validity checks by parsing commit/tag\n>> dates from the paylod and passing them to the ssh-keygen operations.\n>>\n>> changes since v1:\n>>  - struct signature_check is now used to input payload data into\n>>    check_function\n>>  - payload metadata parsing is completely internal to check_signature.\n>>    the caller only need to set the payload type in the sigc struct\n>>  - small nits and readability fixes\n>>  - removed payload_signer parameter. since we now use the struct we can extend\n>>    this later.\n>>\n>As part of testing v2.34-rc0 on Cygwin, I've found this patch series\n>is breaking t4202, t5534, and t6200.\n>\n>Specifically, bisecting points to f265f2d630 (ssh signing: tests for\n>logs, tags & push certs, 2021-09-10) as breaking t4202 and t5534,\n>while responsibility for t6200 seems to be 9d12546de9 (ssh signing:\n>fmt-merge-msg tests & config parse, 2021-10-12).\n>\n>I've not yet done any investigation into the specifics of these\n>failures, but I wanted to report them early so other folks could get\n>on with investigating as appropriate.\n\nWhat exactly are you testing? This patch series is not patch of the rc\nand will not be in v2.34. I even have a small reroll of this for after\nthe release.\nI assume the breaking tests are the new ones added in these commits.\nWhat openssh version are you using? (ssh -V)\nEither the feature detecion for the lifetime checks is not\nworking in cygwin or maybe you have the broken openssh 8.7 version.\n"},{"id":"440464","messageId":"20211104163142.GA1419@dinwoodie.org","threadId":"56793","inReplyTo":"20211103194558.xqcsaljvcoontnal@fs","subject":"Re: [PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Adam Dinwoodie","fromEmail":"adam@dinwoodie.org","sentAt":"2021-11-04T16:31:42Z","receivedAt":"2021-11-04T16:32:00Z","isPatch":true,"sender":{"key":"adam@dinwoodie.org","avatar":"https://avatars.githubusercontent.com/u/1397507?v=4"},"body":"On Wednesday 03 November 2021 at 08:45 pm +0100, Fabian Stelzer wrote:\n> On 03.11.2021 19:27, Adam Dinwoodie wrote:\n> > On Wed, 27 Oct 2021 at 09:06, Fabian Stelzer <fs@gigacodes.de> wrote:\n> > > This series adds key lifetime validity checks by parsing commit/tag\n> > > dates from the paylod and passing them to the ssh-keygen operations.\n> > > \n> > > changes since v1:\n> > >  - struct signature_check is now used to input payload data into\n> > >    check_function\n> > >  - payload metadata parsing is completely internal to check_signature.\n> > >    the caller only need to set the payload type in the sigc struct\n> > >  - small nits and readability fixes\n> > >  - removed payload_signer parameter. since we now use the struct we can extend\n> > >    this later.\n> > > \n> > As part of testing v2.34-rc0 on Cygwin, I've found this patch series\n> > is breaking t4202, t5534, and t6200.\n> > \n> > Specifically, bisecting points to f265f2d630 (ssh signing: tests for\n> > logs, tags & push certs, 2021-09-10) as breaking t4202 and t5534,\n> > while responsibility for t6200 seems to be 9d12546de9 (ssh signing:\n> > fmt-merge-msg tests & config parse, 2021-10-12).\n> > \n> > I've not yet done any investigation into the specifics of these\n> > failures, but I wanted to report them early so other folks could get\n> > on with investigating as appropriate.\n> \n> What exactly are you testing?\n\nI was initially running the tests using the Cygwin packaging\nparaphernalia, as I want to check the full build infrastructure is\nworking as expected.  When this failed, I reproduced the problems on a\nclean checkout of the main Git repository.  In particular, I identified\nthe failing commits with the following bisect command (subject to\nobvious modifications to select the correct script):\n\n     git bisect start v2.34.0-rc0 v2.33.1 && git bisect run bash -c 'make -j 20 || exit 125; cd t || exit 125; ./t4202-*.sh'\n\n> This patch series is not patch of the rc\n> and will not be in v2.34. I even have a small reroll of this for after\n> the release.\n\nThat's odd. `git tag --contains 9d12546de9`, with a fresh pull of the\nGit repo, lists v2.34.0-rc0 and v2.34.0-rc1.  It sounds like that's not\nexpected?\n\n> I assume the breaking tests are the new ones added in these commits.\n\nFor t4202-log.sh, the failing tests are \"72 - setup sshkey signed\nbranch\" and \"75 - log ssh key fingerprint\".\n\nFor t5534-push-signed.sh, the failing tests are \"8 - ssh signed push\nsends push certificate\" and \"12 - fail without key and heed\nuser.signingkey ssh\".\n\nFor t6200-fmt-merge-msg.sh, the failing tests are \"3 - created ssh\nsigned commit and tag\", \"7 - message for merging local tag signed by\ngood ssh key\" and \"8 - message for merging local tag signed by unknown\nssh key\".\n\n> What openssh version are you using? (ssh -V)\n\n    OpenSSH_8.8p1, OpenSSL 1.1.1l  24 Aug 2021\n\n> Either the feature detecion for the lifetime checks is not\n> working in cygwin or maybe you have the broken openssh 8.7 version.\n"},{"id":"440467","messageId":"20211104165458.zphsk7jgdnyh7ybs@fs","threadId":"56793","inReplyTo":"20211104163142.GA1419@dinwoodie.org","subject":"Re: [PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-04T16:54:58Z","receivedAt":"2021-11-04T16:55:03Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 04.11.2021 16:31, Adam Dinwoodie wrote:\n>On Wednesday 03 November 2021 at 08:45 pm +0100, Fabian Stelzer wrote:\n>> On 03.11.2021 19:27, Adam Dinwoodie wrote:\n>> > On Wed, 27 Oct 2021 at 09:06, Fabian Stelzer <fs@gigacodes.de> wrote:\n>> > > This series adds key lifetime validity checks by parsing commit/tag\n>> > > dates from the paylod and passing them to the ssh-keygen operations.\n>> > >\n>> > > changes since v1:\n>> > >  - struct signature_check is now used to input payload data into\n>> > >    check_function\n>> > >  - payload metadata parsing is completely internal to check_signature.\n>> > >    the caller only need to set the payload type in the sigc struct\n>> > >  - small nits and readability fixes\n>> > >  - removed payload_signer parameter. since we now use the struct we can extend\n>> > >    this later.\n>> > >\n>> > As part of testing v2.34-rc0 on Cygwin, I've found this patch series\n>> > is breaking t4202, t5534, and t6200.\n>> >\n>> > Specifically, bisecting points to f265f2d630 (ssh signing: tests for\n>> > logs, tags & push certs, 2021-09-10) as breaking t4202 and t5534,\n>> > while responsibility for t6200 seems to be 9d12546de9 (ssh signing:\n>> > fmt-merge-msg tests & config parse, 2021-10-12).\n\nOk, i should have read this closer / checked the commit. The commit you are\nreferring to is not part of 'this' patch series, but an earlier one which was\nindeed merged and part of the rc.\n\n>For t4202-log.sh, the failing tests are \"72 - setup sshkey signed\n>branch\" and \"75 - log ssh key fingerprint\".\n>\n>For t5534-push-signed.sh, the failing tests are \"8 - ssh signed push\n>sends push certificate\" and \"12 - fail without key and heed\n>user.signingkey ssh\".\n>\n>For t6200-fmt-merge-msg.sh, the failing tests are \"3 - created ssh\n>signed commit and tag\", \"7 - message for merging local tag signed by\n>good ssh key\" and \"8 - message for merging local tag signed by unknown\n>ssh key\".\n\nCould you send the full output of these tests directly to me?\nBest would be sth like the full output of\n\"GIT_TRACE=1 sh t4202-log.sh -vx\"\nand maybe for one test with the trash directory in a zip file (just run\nthe test with -vix, it will stop at the first failure and leave the test\nfiles in place)\n\nI don't have much experience on windows (especially not cygwin) but\nmaybe i can spot the problem.\n\n>\n>> What openssh version are you using? (ssh -V)\n>\n>    OpenSSH_8.8p1, OpenSSL 1.1.1l  24 Aug 2021\n\nThat should be recent enough for this to work.\n"},{"id":"440471","messageId":"20211104172248.GB1419@dinwoodie.org","threadId":"56793","inReplyTo":"20211104165458.zphsk7jgdnyh7ybs@fs","subject":"Re: [PATCH v2 0/6] ssh signing: verify key lifetime","fromName":"Adam Dinwoodie","fromEmail":"adam@dinwoodie.org","sentAt":"2021-11-04T17:22:48Z","receivedAt":"2021-11-04T17:24:06Z","isPatch":true,"sender":{"key":"adam@dinwoodie.org","avatar":"https://avatars.githubusercontent.com/u/1397507?v=4"},"body":"On Thursday 04 November 2021 at 05:54 pm +0100, Fabian Stelzer wrote:\n> On 04.11.2021 16:31, Adam Dinwoodie wrote:\n> > On Wednesday 03 November 2021 at 08:45 pm +0100, Fabian Stelzer wrote:\n> > > On 03.11.2021 19:27, Adam Dinwoodie wrote:\n> > > > On Wed, 27 Oct 2021 at 09:06, Fabian Stelzer <fs@gigacodes.de> wrote:\n> > > > > This series adds key lifetime validity checks by parsing commit/tag\n> > > > > dates from the paylod and passing them to the ssh-keygen operations.\n> > > > >\n> > > > > changes since v1:\n> > > > >  - struct signature_check is now used to input payload data into\n> > > > >    check_function\n> > > > >  - payload metadata parsing is completely internal to check_signature.\n> > > > >    the caller only need to set the payload type in the sigc struct\n> > > > >  - small nits and readability fixes\n> > > > >  - removed payload_signer parameter. since we now use the struct we can extend\n> > > > >    this later.\n> > > > >\n> > > > As part of testing v2.34-rc0 on Cygwin, I've found this patch series\n> > > > is breaking t4202, t5534, and t6200.\n> > > >\n> > > > Specifically, bisecting points to f265f2d630 (ssh signing: tests for\n> > > > logs, tags & push certs, 2021-09-10) as breaking t4202 and t5534,\n> > > > while responsibility for t6200 seems to be 9d12546de9 (ssh signing:\n> > > > fmt-merge-msg tests & config parse, 2021-10-12).\n> \n> Ok, i should have read this closer / checked the commit. The commit you are\n> referring to is not part of 'this' patch series, but an earlier one which was\n> indeed merged and part of the rc.\n\nAh!  That's at least in part on me then; I'd found the most recent\nthread that looked like it was related to this patch series, and assumed\nit'd be the one that was included, without checking the actual commit\ncontents.  Apologies!\n\n> > For t4202-log.sh, the failing tests are \"72 - setup sshkey signed\n> > branch\" and \"75 - log ssh key fingerprint\".\n> > \n> > For t5534-push-signed.sh, the failing tests are \"8 - ssh signed push\n> > sends push certificate\" and \"12 - fail without key and heed\n> > user.signingkey ssh\".\n> > \n> > For t6200-fmt-merge-msg.sh, the failing tests are \"3 - created ssh\n> > signed commit and tag\", \"7 - message for merging local tag signed by\n> > good ssh key\" and \"8 - message for merging local tag signed by unknown\n> > ssh key\".\n> \n> Could you send the full output of these tests directly to me?\n> Best would be sth like the full output of\n> \"GIT_TRACE=1 sh t4202-log.sh -vx\"\n> and maybe for one test with the trash directory in a zip file (just run\n> the test with -vix, it will stop at the first failure and leave the test\n> files in place)\n> \n> I don't have much experience on windows (especially not cygwin) but\n> maybe i can spot the problem.\n\nI'll send these to you shortly.\n\nI don't have much experience with any of Git's signing functions, so\nperhaps between us we might be able to get somewhere :)\n\n> > > What openssh version are you using? (ssh -V)\n> > \n> >    OpenSSH_8.8p1, OpenSSL 1.1.1l  24 Aug 2021\n> \n> That should be recent enough for this to work.\n"},{"id":"441431","messageId":"20211117093529.13953-2-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 1/7] ssh signing: use sigc struct to pass payload","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:23Z","receivedAt":"2021-11-17T09:35:54Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"To be able to extend the payload metadata with things like its creation\ntimestamp or the creators ident we remove the payload parameters to\ncheck_signature() and use the already existing sigc->payload field\ninstead, only adding the length field to the struct. This also allows\nus to get rid of the xmemdupz() calls in the verify functions. Since\nsigc is now used to input data as well as output the result move it to\nthe front of the function list.\n\n - Add payload_length to struct signature_check\n - Populate sigc.payload/payload_len on all call sites\n - Remove payload parameters to check_signature()\n - Remove payload parameters to internal verify_* functions and use sigc\n   instead\n - Remove xmemdupz() used for verbose output since payload is now already\n   populated.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n builtin/receive-pack.c |  6 ++++--\n commit.c               |  5 +++--\n fmt-merge-msg.c        |  4 ++--\n gpg-interface.c        | 37 +++++++++++++++++--------------------\n gpg-interface.h        |  6 +++---\n log-tree.c             |  8 ++++----\n tag.c                  |  4 ++--\n 7 files changed, 35 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d960..61ab63c2ea 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -769,8 +769,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n \n \t\tbogs = parse_signed_buffer(push_cert.buf, push_cert.len);\n-\t\tcheck_signature(push_cert.buf, bogs, push_cert.buf + bogs,\n-\t\t\t\tpush_cert.len - bogs, &sigcheck);\n+\t\tsigcheck.payload = xmemdupz(push_cert.buf, bogs);\n+\t\tsigcheck.payload_len = bogs;\n+\t\tcheck_signature(&sigcheck, push_cert.buf + bogs,\n+\t\t\t\tpush_cert.len - bogs);\n \n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\ndiff --git a/commit.c b/commit.c\nindex 551de4903c..64e040a99b 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1212,8 +1212,9 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\tsignature.len, sigc);\n+\n+\tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n+\tret = check_signature(sigc, signature.buf, signature.len);\n \n  out:\n \tstrbuf_release(&payload);\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5216191488..deca1ea3a3 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,8 +533,8 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n-\t\t\tif (check_signature(payload.buf, payload.len, sig.buf,\n-\t\t\t\t\t    sig.len, &sigc) &&\n+\t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\n \t\t\t\tstrbuf_addstr(&sig, \"gpg verification failed.\\n\");\n \t\t\telse\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3e7255a2a9..75ab6faacb 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -19,8 +19,8 @@ struct gpg_format {\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n \tint (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {\n };\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -314,8 +314,8 @@ static void parse_gpg_output(struct signature_check *sigc)\n }\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process gpg = CHILD_PROCESS_INIT;\n@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,\n \t\t     NULL);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n-\tret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,\n+\tret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,\n \t\t\t   &gpg_stderr, 0);\n \tsigchain_pop(SIGPIPE);\n \n \tdelete_tempfile(&temp);\n \n \tret |= !strstr(gpg_stdout.buf, \"\\n[GNUPG:] GOODSIG \");\n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tsigc->output = strbuf_detach(&gpg_stderr, NULL);\n \tsigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);\n \n@@ -426,8 +425,8 @@ static void parse_ssh_output(struct signature_check *sigc)\n }\n \n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process ssh_keygen = CHILD_PROCESS_INIT;\n@@ -480,7 +479,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n \t\t\t     NULL);\n-\t\tpipe_command(&ssh_keygen, payload, payload_size,\n+\t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \n \t\t/*\n@@ -526,7 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t}\n \n \t\t\tsigchain_push(SIGPIPE, SIG_IGN);\n-\t\t\tret = pipe_command(&ssh_keygen, payload, payload_size,\n+\t\t\tret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \t\t\tsigchain_pop(SIGPIPE);\n \n@@ -540,7 +539,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t}\n \t}\n \n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tstrbuf_stripspace(&ssh_keygen_out, 0);\n \tstrbuf_stripspace(&ssh_keygen_err, 0);\n \t/* Add stderr outputs to show the user actual ssh-keygen errors */\n@@ -562,8 +560,8 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \treturn ret;\n }\n \n-int check_signature(const char *payload, size_t plen, const char *signature,\n-\tsize_t slen, struct signature_check *sigc)\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen)\n {\n \tstruct gpg_format *fmt;\n \tint status;\n@@ -575,8 +573,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n-\tstatus = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,\n-\t\t\t\t\t   slen);\n+\tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\n \t\treturn !!status;\n@@ -593,7 +590,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)\n \t\t\t\t\t\t\t    sigc->output;\n \n \tif (flags & GPG_VERIFY_VERBOSE && sigc->payload)\n-\t\tfputs(sigc->payload, stdout);\n+\t\tfwrite(sigc->payload, 1, sigc->payload_len, stdout);\n \n \tif (output)\n \t\tfputs(output, stderr);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex beefacbb1e..5ee7d8b6b9 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -17,6 +17,7 @@ enum signature_trust_level {\n \n struct signature_check {\n \tchar *payload;\n+\tsize_t payload_len;\n \tchar *output;\n \tchar *gpg_status;\n \n@@ -70,9 +71,8 @@ const char *get_signing_key(void);\n  * Either a GPG KeyID or a SSH Key Fingerprint\n  */\n const char *get_signing_key_id(void);\n-int check_signature(const char *payload, size_t plen,\n-\t\t    const char *signature, size_t slen,\n-\t\t    struct signature_check *sigc);\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen);\n void print_signature_buffer(const struct signature_check *sigc,\n \t\t\t    unsigned flags);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 644893fd8c..a46cf60e1e 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,8 +513,8 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n-\tstatus = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\t signature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n \t\tshow_sig_lines(opt, status, \"No signature\\n\");\n \telse\n@@ -583,8 +583,8 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n-\t\tstatus = check_signature(payload.buf, payload.len,\n-\t\t\t\t\t signature.buf, signature.len, &sigc);\n+\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\n \t\t\tstrbuf_addstr(&verify_message, sigc.output);\n \t\telse\ndiff --git a/tag.c b/tag.c\nindex 3e18a41841..62fb09f5a5 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,8 +25,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\tsignature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tret = check_signature(&sigc, signature.buf, signature.len);\n \n \tif (!(flags & GPG_VERIFY_OMIT_STATUS))\n \t\tprint_signature_buffer(&sigc, flags);\n-- \n2.31.1\n\n"},{"id":"441432","messageId":"20211117093529.13953-3-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 2/7] ssh signing: add key lifetime test prereqs","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:24Z","receivedAt":"2021-11-17T09:35:56Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"if ssh-keygen supports -Overify-time, add test keys marked as expired,\nnot yet valid and valid both within the test_tick timeframe and outside of it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex a3f285f515..fc03c8f89b 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -90,6 +90,10 @@ test_lazy_prereq RFC1991 '\n GPGSSH_KEY_PRIMARY=\"${GNUPGHOME}/ed25519_ssh_signing_key\"\n GPGSSH_KEY_SECONDARY=\"${GNUPGHOME}/rsa_2048_ssh_signing_key\"\n GPGSSH_KEY_UNTRUSTED=\"${GNUPGHOME}/untrusted_ssh_signing_key\"\n+GPGSSH_KEY_EXPIRED=\"${GNUPGHOME}/expired_ssh_signing_key\"\n+GPGSSH_KEY_NOTYETVALID=\"${GNUPGHOME}/notyetvalid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDVALID=\"${GNUPGHOME}/timeboxed_valid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDINVALID=\"${GNUPGHOME}/timeboxed_invalid_ssh_signing_key\"\n GPGSSH_KEY_WITH_PASSPHRASE=\"${GNUPGHOME}/protected_ssh_signing_key\"\n GPGSSH_KEY_PASSPHRASE=\"super_secret\"\n GPGSSH_ALLOWED_SIGNERS=\"${GNUPGHOME}/ssh.all_valid.allowedSignersFile\"\n@@ -119,7 +123,20 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+'\n+\n+test_lazy_prereq GPGSSH_VERIFYTIME '\n+\t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n+\tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n+\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n+\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"441433","messageId":"20211117093529.13953-1-fs@gigacodes.de","threadId":"56793","inReplyTo":"xmqqsfwmus5g.fsf@gitster.g","subject":"[PATCH v3 0/7] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:22Z","receivedAt":"2021-11-17T09:35:57Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"changes since v2:\n - fix swich/case indentation\n - BUG() on unknown payload types\n - improve test prereq by actually validating ssh-keygen functionality\n\nchanges since v1:\n - struct signature_check is now used to input payload data into\n   check_function\n - payload metadata parsing is completely internal to check_signature.\n   the caller only need to set the payload type in the sigc struct\n - small nits and readability fixes\n - removed payload_signer parameter. since we now use the struct we can extend\n   this later.\n\nFabian Stelzer (7):\n  ssh signing: use sigc struct to pass payload\n  ssh signing: add key lifetime test prereqs\n  ssh signing: make verify-commit consider key lifetime\n  ssh signing: make git log verify key lifetime\n  ssh signing: make verify-tag consider key lifetime\n  ssh signing: make fmt-merge-msg consider key lifetime\n  ssh signing: verify ssh-keygen in test prereq\n\n Documentation/config/gpg.txt     |  5 ++\n builtin/receive-pack.c           |  6 ++-\n commit.c                         |  6 ++-\n fmt-merge-msg.c                  |  5 +-\n gpg-interface.c                  | 90 +++++++++++++++++++++++++-------\n gpg-interface.h                  | 15 ++++--\n log-tree.c                       | 10 ++--\n t/lib-gpg.sh                     | 36 ++++++++++---\n t/t4202-log.sh                   | 43 +++++++++++++++\n t/t6200-fmt-merge-msg.sh         | 54 +++++++++++++++++++\n t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n tag.c                            |  5 +-\n 13 files changed, 318 insertions(+), 41 deletions(-)\n\nRange-diff against v2:\n1:  11d275b53f = 1:  9f71fd8639 ssh signing: use sigc struct to pass payload\n2:  35ee285887 = 2:  5ee143bc38 ssh signing: add key lifetime test prereqs\n3:  ccfba23934 ! 3:  3183e84e2e ssh signing: make verify-commit consider key lifetime\n    @@ gpg-interface.c: static int verify_ssh_signed_buffer(struct signature_check *sig\n     +\tstruct ident_split ident;\n     +\tconst char *signer_header;\n     +\n    -+\tswitch(sigc->payload_type) {\n    -+\t\tcase SIGNATURE_PAYLOAD_COMMIT:\n    -+\t\t\tsigner_header = \"committer\";\n    -+\t\t\tbreak;\n    -+\t\tcase SIGNATURE_PAYLOAD_TAG:\n    -+\t\t\tsigner_header = \"tagger\";\n    -+\t\t\tbreak;\n    -+\t\tdefault:\n    -+\t\t\t/* Ignore unknown payload types */\n    -+\t\t\treturn 0;\n    ++\tswitch (sigc->payload_type) {\n    ++\tcase SIGNATURE_PAYLOAD_COMMIT:\n    ++\t\tsigner_header = \"committer\";\n    ++\t\tbreak;\n    ++\tcase SIGNATURE_PAYLOAD_TAG:\n    ++\t\tsigner_header = \"tagger\";\n    ++\t\tbreak;\n    ++\tcase SIGNATURE_PAYLOAD_UNDEFINED:\n    ++\tcase SIGNATURE_PAYLOAD_PUSH_CERT:\n    ++\t\t/* Ignore payloads we don't want to parse */\n    ++\t\treturn 0;\n    ++\tdefault:\n    ++\t\tBUG(\"invalid value for sigc->payload_type\");\n     +\t}\n     +\n     +\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n4:  ef8178f88b = 4:  e35515867c ssh signing: make git log verify key lifetime\n5:  c12457020e = 5:  e20177d950 ssh signing: make verify-tag consider key lifetime\n6:  fe17c60276 = 6:  2af2b6d098 ssh signing: make fmt-merge-msg consider key lifetime\n-:  ---------- > 7:  e6e2236a52 ssh signing: verify ssh-keygen in test prereq\n\nbase-commit: cd3e606211bb1cf8bc57f7d76bab98cc17a150bc\n-- \n2.31.1\n\n"},{"id":"441434","messageId":"20211117093529.13953-4-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 3/7] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:25Z","receivedAt":"2021-11-17T09:35:58Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"If valid-before/after dates are configured for this signatures key in the\nallowedSigners file then the verification should check if the key was valid at\nthe time the commit was made. This allows for graceful key rollover and\nrevoking keys without invalidating all previous commits.\nThis feature needs openssh > 8.8. Older ssh-keygen versions will simply\nignore this flag and use the current time.\nStrictly speaking this feature is available in 8.7, but since 8.7 has a\nbug that makes it unusable in another needed call we require 8.8.\n\nTimestamp information is present on most invocations of check_signature.\nHowever signer ident is not. We will need the signer email / name to be able\nto implement \"Trust on first use\" functionality later.\nSince the payload contains all necessary information we can parse it\nfrom there. The caller only needs to provide us some info about the\npayload by setting payload_type in the signature_check struct.\n\n - Add payload_type field & enum and payload_timestamp to struct\n   signature_check\n - Populate the timestamp when not already set if we know about the\n   payload type\n - Pass -Overify-time={payload_timestamp} in the users timezone to all\n   ssh-keygen verification calls\n - Set the payload type when verifying commits\n - Add tests for expired, not yet valid and keys having a commit date\n   outside of key validity as well as within\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n Documentation/config/gpg.txt |  5 ++++\n commit.c                     |  1 +\n gpg-interface.c              | 53 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h              |  9 ++++++\n t/t7528-signed-commit-ssh.sh | 42 ++++++++++++++++++++++++++++\n 5 files changed, 110 insertions(+)\n\ndiff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt\nindex 4f30c7dbdd..c9be554c73 100644\n--- a/Documentation/config/gpg.txt\n+++ b/Documentation/config/gpg.txt\n@@ -64,6 +64,11 @@ A repository that only allows signed commits can store the file\n in the repository itself using a path relative to the top-level of the working tree.\n This way only committers with an already valid key can add or change keys in the keyring.\n +\n+Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &\n+valid-before options. Git will mark signatures as valid if the signing key was\n+valid at the time of the signatures creation. This allows users to change a\n+signing key without invalidating all previously made signatures.\n++\n Using a SSH CA key with the cert-authority option\n (see ssh-keygen(1) \"CERTIFICATES\") is also valid.\n \ndiff --git a/commit.c b/commit.c\nindex 64e040a99b..a348f085b2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1213,6 +1213,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n \tret = check_signature(sigc, signature.buf, signature.len);\n \ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 75ab6faacb..330cfc5845 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -439,6 +439,13 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstruct strbuf ssh_principals_err = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_out = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_err = STRBUF_INIT;\n+\tstruct strbuf verify_time = STRBUF_INIT;\n+\tconst struct date_mode verify_date_mode = {\n+\t\t.type = DATE_STRFTIME,\n+\t\t.strftime_fmt = \"%Y%m%d%H%M%S\",\n+\t\t/* SSH signing key validity has no timezone information - Use the local timezone */\n+\t\t.local = 1,\n+\t};\n \n \tif (!ssh_allowed_signers) {\n \t\terror(_(\"gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification\"));\n@@ -456,11 +463,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\treturn -1;\n \t}\n \n+\tif (sigc->payload_timestamp)\n+\t\tstrbuf_addf(&verify_time, \"-Overify-time=%s\",\n+\t\t\tshow_date(sigc->payload_timestamp, 0, &verify_date_mode));\n+\n \t/* Find the principal from the signers */\n \tstrvec_pushl(&ssh_keygen.args, fmt->program,\n \t\t     \"-Y\", \"find-principals\",\n \t\t     \"-f\", ssh_allowed_signers,\n \t\t     \"-s\", buffer_file->filename.buf,\n+\t\t     verify_time.buf,\n \t\t     NULL);\n \tret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,\n \t\t\t   &ssh_principals_err, 0);\n@@ -478,6 +490,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-Y\", \"check-novalidate\",\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t     verify_time.buf,\n \t\t\t     NULL);\n \t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n@@ -512,6 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t\t     \"-f\", ssh_allowed_signers,\n \t\t\t\t     \"-I\", principal,\n \t\t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t\t     verify_time.buf,\n \t\t\t\t     NULL);\n \n \t\t\tif (ssh_revocation_file) {\n@@ -556,10 +570,46 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstrbuf_release(&ssh_principals_err);\n \tstrbuf_release(&ssh_keygen_out);\n \tstrbuf_release(&ssh_keygen_err);\n+\tstrbuf_release(&verify_time);\n \n \treturn ret;\n }\n \n+static int parse_payload_metadata(struct signature_check *sigc)\n+{\n+\tconst char *ident_line = NULL;\n+\tsize_t ident_len;\n+\tstruct ident_split ident;\n+\tconst char *signer_header;\n+\n+\tswitch (sigc->payload_type) {\n+\tcase SIGNATURE_PAYLOAD_COMMIT:\n+\t\tsigner_header = \"committer\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_TAG:\n+\t\tsigner_header = \"tagger\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_UNDEFINED:\n+\tcase SIGNATURE_PAYLOAD_PUSH_CERT:\n+\t\t/* Ignore payloads we don't want to parse */\n+\t\treturn 0;\n+\tdefault:\n+\t\tBUG(\"invalid value for sigc->payload_type\");\n+\t}\n+\n+\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n+\tif (!ident_line || !ident_len)\n+\t\treturn 1;\n+\n+\tif (split_ident_line(&ident, ident_line, ident_len))\n+\t\treturn 1;\n+\n+\tif (!sigc->payload_timestamp && ident.date_begin && ident.date_end)\n+\t\tsigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n int check_signature(struct signature_check *sigc,\n \t\t    const char *signature, size_t slen)\n {\n@@ -573,6 +623,9 @@ int check_signature(struct signature_check *sigc,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n+\tif (parse_payload_metadata(sigc))\n+\t\treturn 1;\n+\n \tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 5ee7d8b6b9..b30cbdcd3d 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -15,9 +15,18 @@ enum signature_trust_level {\n \tTRUST_ULTIMATE,\n };\n \n+enum payload_type {\n+\tSIGNATURE_PAYLOAD_UNDEFINED,\n+\tSIGNATURE_PAYLOAD_COMMIT,\n+\tSIGNATURE_PAYLOAD_TAG,\n+\tSIGNATURE_PAYLOAD_PUSH_CERT,\n+};\n+\n struct signature_check {\n \tchar *payload;\n \tsize_t payload_len;\n+\tenum payload_type payload_type;\n+\ttimestamp_t payload_timestamp;\n \tchar *output;\n \tchar *gpg_status;\n \ndiff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh\nindex badf3ed320..dae76ded0c 100755\n--- a/t/t7528-signed-commit-ssh.sh\n+++ b/t/t7528-signed-commit-ssh.sh\n@@ -76,6 +76,23 @@ test_expect_success GPGSSH 'create signed commits' '\n \tgit tag twelfth-signed-alt $(cat oid)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.mintrustlevel UNDEFINED &&\n@@ -122,6 +139,31 @@ test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature'\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit succeeds with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-commit timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.minTrustLevel fully &&\n-- \n2.31.1\n\n"},{"id":"441435","messageId":"20211117093529.13953-5-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 4/7] ssh signing: make git log verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:26Z","receivedAt":"2021-11-17T09:36:00Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling git log.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n log-tree.c     |  2 ++\n t/t4202-log.sh | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex a46cf60e1e..d3e7a40b64 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,6 +513,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n@@ -583,6 +584,7 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n+\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 7884e3d46b..ba855ec893 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -1677,6 +1677,24 @@ test_expect_success GPGSSH 'setup sshkey signed branch' '\n \tgit commit -S -m signed_commit\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_config gpg.format ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSM 'log x509 fingerprint' '\n \techo \"F8BF62E0693D0694816377099909C779FA23FD65 | \" >expect &&\n \tgit log -n1 --format=\"%GF | %GP\" signed-x509 >actual &&\n@@ -1714,6 +1732,31 @@ test_expect_success GPGSSH 'log --graph --show-signature ssh' '\n \tgrep \"${GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 expired-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 notyetvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log show success with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedvalid-signed >actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedinvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPG 'log --graph --show-signature for merged tag' '\n \ttest_when_finished \"git reset --hard && git checkout main\" &&\n \tgit checkout -b plain main &&\n-- \n2.31.1\n\n"},{"id":"441436","messageId":"20211117093529.13953-6-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 5/7] ssh signing: make verify-tag consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:27Z","receivedAt":"2021-11-17T09:36:10Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling verify-tag.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t7031-verify-tag-signed-ssh.sh | 42 ++++++++++++++++++++++++++++++++\n tag.c                            |  1 +\n 2 files changed, 43 insertions(+)\n\ndiff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh\nindex 06c9dd6c93..1cb36b9ab8 100755\n--- a/t/t7031-verify-tag-signed-ssh.sh\n+++ b/t/t7031-verify-tag-signed-ssh.sh\n@@ -48,6 +48,23 @@ test_expect_success GPGSSH 'create signed tags ssh' '\n \tgit tag -u\"${GPGSSH_KEY_UNTRUSTED}\" -m eighth eighth-signed-alt\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show ssh signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \t(\n@@ -80,6 +97,31 @@ test_expect_success GPGSSH 'verify and show ssh signatures' '\n \t)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag succeeds with tag date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-tag timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'detect fudged ssh signature' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit cat-file tag seventh-signed >raw &&\ndiff --git a/tag.c b/tag.c\nindex 62fb09f5a5..dfbcd7fcc2 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,6 +25,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tret = check_signature(&sigc, signature.buf, signature.len);\n \n-- \n2.31.1\n\n"},{"id":"441437","messageId":"20211117093529.13953-7-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 6/7] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:28Z","receivedAt":"2021-11-17T09:36:14Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when generating merge messages to\nverify merged tags signatures key lifetimes.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n fmt-merge-msg.c          |  1 +\n t/t6200-fmt-merge-msg.sh | 54 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+)\n\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex deca1ea3a3..e4f7810be2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,6 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n+\t\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 06c5fb5615..2dd2423643 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\tgit checkout -b signed-expiry-ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success 'message for merging local branch' '\n \techo \"Merge branch ${apos}left${apos}\" >expected &&\n \n@@ -137,6 +157,40 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . expired-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . notyetvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedinvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success 'message for merging external branch' '\n \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n \n-- \n2.31.1\n\n"},{"id":"441438","messageId":"20211117093529.13953-8-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v3 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-17T09:35:29Z","receivedAt":"2021-11-17T09:36:19Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Do a full ssh signing, find-principals and verify operation in the test\nprereq's to make sure ssh-keygen works as expected. Only generating the\nkeys and verifying its presence is not sufficient in some situations.\nOne example was ssh-keygen creating unusable ssh keys in cygwin because\nof unsafe default permissions for the key files. The other a broken\nopenssh 8.7 that segfaulted on any find-principals operation. This\nextended prereq check avoids future test breakages in case ssh-keygen or\nany environment behaviour changes.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 +++++++++++++------\n 1 file changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex fc03c8f89b..96cf0efebc 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -109,11 +109,7 @@ test_lazy_prereq GPGSSH '\n \techo $ssh_version | grep -q \"find-principals:missing signature file\"\n \ttest $? = 0 || exit 1;\n \n-\t# some broken versions of ssh-keygen segfault on find-principals;\n-\t# avoid testing with them.\n-\tssh-keygen -Y find-principals -f /dev/null -s /dev/null\n-\ttest $? = 139 && exit 1\n-\n+\t# Setup some keys and an allowed signers file\n \tmkdir -p \"${GNUPGHOME}\" &&\n \tchmod 0700 \"${GNUPGHOME}\" &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n@@ -123,12 +119,19 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n+\n+\t# Verify if at least one key and ssh-keygen works as expected\n+\techo \"testpayload\" | ssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" > gpgssh_prereq.sig &&\n+\tssh-keygen -Y find-principals -f \"${GPGSSH_ALLOWED_SIGNERS}\" -s gpgssh_prereq.sig &&\n+\techo \"testpayload\" | ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with number 1\" -s gpgssh_prereq.sig\n '\n \n test_lazy_prereq GPGSSH_VERIFYTIME '\n \t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n \tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\n+\t# Set up keys with key lifetimes\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n \techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n@@ -137,6 +140,10 @@ test_lazy_prereq GPGSSH_VERIFYTIME '\n \techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n \techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n+\n+\t# and verify ssh-keygen verifies the key lifetime\n+\techo \"testpayload\" | ssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" > gpgssh_verifytime_prereq.sig &&\n+\t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"441694","messageId":"xmqqfsrssmar.fsf@gitster.g","threadId":"56793","inReplyTo":"20211117093529.13953-8-fs@gigacodes.de","subject":"Re: [PATCH v3 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T06:15:56Z","receivedAt":"2021-11-19T06:16:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fabian Stelzer <fs@gigacodes.de> writes:\n\n> @@ -123,12 +119,19 @@ test_lazy_prereq GPGSSH '\n>  \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n>  \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n> -\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n> +\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n> +\n> +\t# Verify if at least one key and ssh-keygen works as expected\n> +\techo \"testpayload\" | ssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" > gpgssh_prereq.sig &&\n\nStyle:\n . Avoid overlong line by breaking the line after pipe.\n . No SP between redirection operator '>' and redirection target.\n\n\techo \"testpayload\" |\n\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" >gpgssh_prereq.sig &&\n\nAlso I wonder if preparation of GPGSSH_ALLOWED_SIGNERS file can be\nsimplified and made easier to read.  Instead of adding one line at a\ntime like this:\n\n>  \techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n\nif you define variables with meaningful name and used here document,\ne.g.\n\n\tts2005a=200504070000 ts2005b=200504100000 &&\n\tkey_timeboxedvalid=$(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\") &&\n\n\tts2999=299901010000 &&\n\tkey_notyetvalid=$(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\") &&\n\n\tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n\t\"principal with not yet valid key\" valid-after=\"$ts2999\" $key_notyetvalid\n\t\"timeboxed valid key\" valid-after=\"$ts2005a\",valid-before=\"$ts2005b\" $key_timeboxedvalid\n\t...\n\tEOF\n\n"},{"id":"442653","messageId":"20211130141112.78193-1-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211117093529.13953-1-fs@gigacodes.de","subject":"[PATCH v4 0/7] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:05Z","receivedAt":"2021-11-30T14:11:34Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"changes since v3:\n - improve readability of prereq setup code by using heredoc and some \n   variables\n\nchanges since v2:\n - fix swich/case indentation\n - BUG() on unknown payload types\n - improve test prereq by actually validating ssh-keygen functionality\n\nchanges since v1:\n - struct signature_check is now used to input payload data into\n   check_function\n - payload metadata parsing is completely internal to check_signature.\n   the caller only need to set the payload type in the sigc struct\n - small nits and readability fixes\n - removed payload_signer parameter. since we now use the struct we can \n   extend\n   this later.\n\nFabian Stelzer (7):\n  ssh signing: use sigc struct to pass payload\n  ssh signing: add key lifetime test prereqs\n  ssh signing: make verify-commit consider key lifetime\n  ssh signing: make git log verify key lifetime\n  ssh signing: make verify-tag consider key lifetime\n  ssh signing: make fmt-merge-msg consider key lifetime\n  ssh signing: verify ssh-keygen in test prereq\n\n Documentation/config/gpg.txt     |  5 ++\n builtin/receive-pack.c           |  6 ++-\n commit.c                         |  6 ++-\n fmt-merge-msg.c                  |  5 +-\n gpg-interface.c                  | 90 +++++++++++++++++++++++++-------\n gpg-interface.h                  | 15 ++++--\n log-tree.c                       | 10 ++--\n t/lib-gpg.sh                     | 62 ++++++++++++++++++----\n t/t4202-log.sh                   | 43 +++++++++++++++\n t/t6200-fmt-merge-msg.sh         | 54 +++++++++++++++++++\n t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n tag.c                            |  5 +-\n 13 files changed, 341 insertions(+), 44 deletions(-)\n\nRange-diff against v3:\n1:  9f71fd8639 = 1:  c4447d30f2 ssh signing: use sigc struct to pass payload\n2:  5ee143bc38 = 2:  0bb1617529 ssh signing: add key lifetime test prereqs\n3:  3183e84e2e = 3:  f60bd1efd0 ssh signing: make verify-commit consider key lifetime\n4:  e35515867c = 4:  5fc0ad5c37 ssh signing: make git log verify key lifetime\n5:  e20177d950 = 5:  f1c225871f ssh signing: make verify-tag consider key lifetime\n6:  2af2b6d098 = 6:  1cbd4dbb6b ssh signing: make fmt-merge-msg consider key lifetime\n7:  e6e2236a52 < -:  ---------- ssh signing: verify ssh-keygen in test prereq\n-:  ---------- > 7:  d60f4ec82c ssh signing: verify ssh-keygen in test prereq\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\n-- \n2.31.1\n\n"},{"id":"442654","messageId":"20211130141112.78193-2-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 1/7] ssh signing: use sigc struct to pass payload","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:06Z","receivedAt":"2021-11-30T14:11:35Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"To be able to extend the payload metadata with things like its creation\ntimestamp or the creators ident we remove the payload parameters to\ncheck_signature() and use the already existing sigc->payload field\ninstead, only adding the length field to the struct. This also allows\nus to get rid of the xmemdupz() calls in the verify functions. Since\nsigc is now used to input data as well as output the result move it to\nthe front of the function list.\n\n - Add payload_length to struct signature_check\n - Populate sigc.payload/payload_len on all call sites\n - Remove payload parameters to check_signature()\n - Remove payload parameters to internal verify_* functions and use sigc\n   instead\n - Remove xmemdupz() used for verbose output since payload is now already\n   populated.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n builtin/receive-pack.c |  6 ++++--\n commit.c               |  5 +++--\n fmt-merge-msg.c        |  4 ++--\n gpg-interface.c        | 37 +++++++++++++++++--------------------\n gpg-interface.h        |  6 +++---\n log-tree.c             |  8 ++++----\n tag.c                  |  4 ++--\n 7 files changed, 35 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d960..61ab63c2ea 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -769,8 +769,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n \n \t\tbogs = parse_signed_buffer(push_cert.buf, push_cert.len);\n-\t\tcheck_signature(push_cert.buf, bogs, push_cert.buf + bogs,\n-\t\t\t\tpush_cert.len - bogs, &sigcheck);\n+\t\tsigcheck.payload = xmemdupz(push_cert.buf, bogs);\n+\t\tsigcheck.payload_len = bogs;\n+\t\tcheck_signature(&sigcheck, push_cert.buf + bogs,\n+\t\t\t\tpush_cert.len - bogs);\n \n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\ndiff --git a/commit.c b/commit.c\nindex 551de4903c..64e040a99b 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1212,8 +1212,9 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\tsignature.len, sigc);\n+\n+\tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n+\tret = check_signature(sigc, signature.buf, signature.len);\n \n  out:\n \tstrbuf_release(&payload);\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5216191488..deca1ea3a3 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,8 +533,8 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n-\t\t\tif (check_signature(payload.buf, payload.len, sig.buf,\n-\t\t\t\t\t    sig.len, &sigc) &&\n+\t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\n \t\t\t\tstrbuf_addstr(&sig, \"gpg verification failed.\\n\");\n \t\t\telse\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3e7255a2a9..75ab6faacb 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -19,8 +19,8 @@ struct gpg_format {\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n \tint (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {\n };\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -314,8 +314,8 @@ static void parse_gpg_output(struct signature_check *sigc)\n }\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process gpg = CHILD_PROCESS_INIT;\n@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,\n \t\t     NULL);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n-\tret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,\n+\tret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,\n \t\t\t   &gpg_stderr, 0);\n \tsigchain_pop(SIGPIPE);\n \n \tdelete_tempfile(&temp);\n \n \tret |= !strstr(gpg_stdout.buf, \"\\n[GNUPG:] GOODSIG \");\n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tsigc->output = strbuf_detach(&gpg_stderr, NULL);\n \tsigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);\n \n@@ -426,8 +425,8 @@ static void parse_ssh_output(struct signature_check *sigc)\n }\n \n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process ssh_keygen = CHILD_PROCESS_INIT;\n@@ -480,7 +479,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n \t\t\t     NULL);\n-\t\tpipe_command(&ssh_keygen, payload, payload_size,\n+\t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \n \t\t/*\n@@ -526,7 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t}\n \n \t\t\tsigchain_push(SIGPIPE, SIG_IGN);\n-\t\t\tret = pipe_command(&ssh_keygen, payload, payload_size,\n+\t\t\tret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \t\t\tsigchain_pop(SIGPIPE);\n \n@@ -540,7 +539,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t}\n \t}\n \n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tstrbuf_stripspace(&ssh_keygen_out, 0);\n \tstrbuf_stripspace(&ssh_keygen_err, 0);\n \t/* Add stderr outputs to show the user actual ssh-keygen errors */\n@@ -562,8 +560,8 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \treturn ret;\n }\n \n-int check_signature(const char *payload, size_t plen, const char *signature,\n-\tsize_t slen, struct signature_check *sigc)\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen)\n {\n \tstruct gpg_format *fmt;\n \tint status;\n@@ -575,8 +573,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n-\tstatus = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,\n-\t\t\t\t\t   slen);\n+\tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\n \t\treturn !!status;\n@@ -593,7 +590,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)\n \t\t\t\t\t\t\t    sigc->output;\n \n \tif (flags & GPG_VERIFY_VERBOSE && sigc->payload)\n-\t\tfputs(sigc->payload, stdout);\n+\t\tfwrite(sigc->payload, 1, sigc->payload_len, stdout);\n \n \tif (output)\n \t\tfputs(output, stderr);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex beefacbb1e..5ee7d8b6b9 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -17,6 +17,7 @@ enum signature_trust_level {\n \n struct signature_check {\n \tchar *payload;\n+\tsize_t payload_len;\n \tchar *output;\n \tchar *gpg_status;\n \n@@ -70,9 +71,8 @@ const char *get_signing_key(void);\n  * Either a GPG KeyID or a SSH Key Fingerprint\n  */\n const char *get_signing_key_id(void);\n-int check_signature(const char *payload, size_t plen,\n-\t\t    const char *signature, size_t slen,\n-\t\t    struct signature_check *sigc);\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen);\n void print_signature_buffer(const struct signature_check *sigc,\n \t\t\t    unsigned flags);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 644893fd8c..a46cf60e1e 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,8 +513,8 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n-\tstatus = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\t signature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n \t\tshow_sig_lines(opt, status, \"No signature\\n\");\n \telse\n@@ -583,8 +583,8 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n-\t\tstatus = check_signature(payload.buf, payload.len,\n-\t\t\t\t\t signature.buf, signature.len, &sigc);\n+\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\n \t\t\tstrbuf_addstr(&verify_message, sigc.output);\n \t\telse\ndiff --git a/tag.c b/tag.c\nindex 3e18a41841..62fb09f5a5 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,8 +25,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\tsignature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tret = check_signature(&sigc, signature.buf, signature.len);\n \n \tif (!(flags & GPG_VERIFY_OMIT_STATUS))\n \t\tprint_signature_buffer(&sigc, flags);\n-- \n2.31.1\n\n"},{"id":"442655","messageId":"20211130141112.78193-3-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 2/7] ssh signing: add key lifetime test prereqs","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:07Z","receivedAt":"2021-11-30T14:11:40Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"if ssh-keygen supports -Overify-time, add test keys marked as expired,\nnot yet valid and valid both within the test_tick timeframe and outside of it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex a3f285f515..fc03c8f89b 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -90,6 +90,10 @@ test_lazy_prereq RFC1991 '\n GPGSSH_KEY_PRIMARY=\"${GNUPGHOME}/ed25519_ssh_signing_key\"\n GPGSSH_KEY_SECONDARY=\"${GNUPGHOME}/rsa_2048_ssh_signing_key\"\n GPGSSH_KEY_UNTRUSTED=\"${GNUPGHOME}/untrusted_ssh_signing_key\"\n+GPGSSH_KEY_EXPIRED=\"${GNUPGHOME}/expired_ssh_signing_key\"\n+GPGSSH_KEY_NOTYETVALID=\"${GNUPGHOME}/notyetvalid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDVALID=\"${GNUPGHOME}/timeboxed_valid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDINVALID=\"${GNUPGHOME}/timeboxed_invalid_ssh_signing_key\"\n GPGSSH_KEY_WITH_PASSPHRASE=\"${GNUPGHOME}/protected_ssh_signing_key\"\n GPGSSH_KEY_PASSPHRASE=\"super_secret\"\n GPGSSH_ALLOWED_SIGNERS=\"${GNUPGHOME}/ssh.all_valid.allowedSignersFile\"\n@@ -119,7 +123,20 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+'\n+\n+test_lazy_prereq GPGSSH_VERIFYTIME '\n+\t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n+\tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n+\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n+\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"442656","messageId":"20211130141112.78193-4-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 3/7] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:08Z","receivedAt":"2021-11-30T14:11:54Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"If valid-before/after dates are configured for this signatures key in the\nallowedSigners file then the verification should check if the key was valid at\nthe time the commit was made. This allows for graceful key rollover and\nrevoking keys without invalidating all previous commits.\nThis feature needs openssh > 8.8. Older ssh-keygen versions will simply\nignore this flag and use the current time.\nStrictly speaking this feature is available in 8.7, but since 8.7 has a\nbug that makes it unusable in another needed call we require 8.8.\n\nTimestamp information is present on most invocations of check_signature.\nHowever signer ident is not. We will need the signer email / name to be able\nto implement \"Trust on first use\" functionality later.\nSince the payload contains all necessary information we can parse it\nfrom there. The caller only needs to provide us some info about the\npayload by setting payload_type in the signature_check struct.\n\n - Add payload_type field & enum and payload_timestamp to struct\n   signature_check\n - Populate the timestamp when not already set if we know about the\n   payload type\n - Pass -Overify-time={payload_timestamp} in the users timezone to all\n   ssh-keygen verification calls\n - Set the payload type when verifying commits\n - Add tests for expired, not yet valid and keys having a commit date\n   outside of key validity as well as within\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n Documentation/config/gpg.txt |  5 ++++\n commit.c                     |  1 +\n gpg-interface.c              | 53 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h              |  9 ++++++\n t/t7528-signed-commit-ssh.sh | 42 ++++++++++++++++++++++++++++\n 5 files changed, 110 insertions(+)\n\ndiff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt\nindex 4f30c7dbdd..c9be554c73 100644\n--- a/Documentation/config/gpg.txt\n+++ b/Documentation/config/gpg.txt\n@@ -64,6 +64,11 @@ A repository that only allows signed commits can store the file\n in the repository itself using a path relative to the top-level of the working tree.\n This way only committers with an already valid key can add or change keys in the keyring.\n +\n+Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &\n+valid-before options. Git will mark signatures as valid if the signing key was\n+valid at the time of the signatures creation. This allows users to change a\n+signing key without invalidating all previously made signatures.\n++\n Using a SSH CA key with the cert-authority option\n (see ssh-keygen(1) \"CERTIFICATES\") is also valid.\n \ndiff --git a/commit.c b/commit.c\nindex 64e040a99b..a348f085b2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1213,6 +1213,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n \tret = check_signature(sigc, signature.buf, signature.len);\n \ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 75ab6faacb..330cfc5845 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -439,6 +439,13 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstruct strbuf ssh_principals_err = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_out = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_err = STRBUF_INIT;\n+\tstruct strbuf verify_time = STRBUF_INIT;\n+\tconst struct date_mode verify_date_mode = {\n+\t\t.type = DATE_STRFTIME,\n+\t\t.strftime_fmt = \"%Y%m%d%H%M%S\",\n+\t\t/* SSH signing key validity has no timezone information - Use the local timezone */\n+\t\t.local = 1,\n+\t};\n \n \tif (!ssh_allowed_signers) {\n \t\terror(_(\"gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification\"));\n@@ -456,11 +463,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\treturn -1;\n \t}\n \n+\tif (sigc->payload_timestamp)\n+\t\tstrbuf_addf(&verify_time, \"-Overify-time=%s\",\n+\t\t\tshow_date(sigc->payload_timestamp, 0, &verify_date_mode));\n+\n \t/* Find the principal from the signers */\n \tstrvec_pushl(&ssh_keygen.args, fmt->program,\n \t\t     \"-Y\", \"find-principals\",\n \t\t     \"-f\", ssh_allowed_signers,\n \t\t     \"-s\", buffer_file->filename.buf,\n+\t\t     verify_time.buf,\n \t\t     NULL);\n \tret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,\n \t\t\t   &ssh_principals_err, 0);\n@@ -478,6 +490,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-Y\", \"check-novalidate\",\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t     verify_time.buf,\n \t\t\t     NULL);\n \t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n@@ -512,6 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t\t     \"-f\", ssh_allowed_signers,\n \t\t\t\t     \"-I\", principal,\n \t\t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t\t     verify_time.buf,\n \t\t\t\t     NULL);\n \n \t\t\tif (ssh_revocation_file) {\n@@ -556,10 +570,46 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstrbuf_release(&ssh_principals_err);\n \tstrbuf_release(&ssh_keygen_out);\n \tstrbuf_release(&ssh_keygen_err);\n+\tstrbuf_release(&verify_time);\n \n \treturn ret;\n }\n \n+static int parse_payload_metadata(struct signature_check *sigc)\n+{\n+\tconst char *ident_line = NULL;\n+\tsize_t ident_len;\n+\tstruct ident_split ident;\n+\tconst char *signer_header;\n+\n+\tswitch (sigc->payload_type) {\n+\tcase SIGNATURE_PAYLOAD_COMMIT:\n+\t\tsigner_header = \"committer\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_TAG:\n+\t\tsigner_header = \"tagger\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_UNDEFINED:\n+\tcase SIGNATURE_PAYLOAD_PUSH_CERT:\n+\t\t/* Ignore payloads we don't want to parse */\n+\t\treturn 0;\n+\tdefault:\n+\t\tBUG(\"invalid value for sigc->payload_type\");\n+\t}\n+\n+\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n+\tif (!ident_line || !ident_len)\n+\t\treturn 1;\n+\n+\tif (split_ident_line(&ident, ident_line, ident_len))\n+\t\treturn 1;\n+\n+\tif (!sigc->payload_timestamp && ident.date_begin && ident.date_end)\n+\t\tsigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n int check_signature(struct signature_check *sigc,\n \t\t    const char *signature, size_t slen)\n {\n@@ -573,6 +623,9 @@ int check_signature(struct signature_check *sigc,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n+\tif (parse_payload_metadata(sigc))\n+\t\treturn 1;\n+\n \tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 5ee7d8b6b9..b30cbdcd3d 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -15,9 +15,18 @@ enum signature_trust_level {\n \tTRUST_ULTIMATE,\n };\n \n+enum payload_type {\n+\tSIGNATURE_PAYLOAD_UNDEFINED,\n+\tSIGNATURE_PAYLOAD_COMMIT,\n+\tSIGNATURE_PAYLOAD_TAG,\n+\tSIGNATURE_PAYLOAD_PUSH_CERT,\n+};\n+\n struct signature_check {\n \tchar *payload;\n \tsize_t payload_len;\n+\tenum payload_type payload_type;\n+\ttimestamp_t payload_timestamp;\n \tchar *output;\n \tchar *gpg_status;\n \ndiff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh\nindex badf3ed320..dae76ded0c 100755\n--- a/t/t7528-signed-commit-ssh.sh\n+++ b/t/t7528-signed-commit-ssh.sh\n@@ -76,6 +76,23 @@ test_expect_success GPGSSH 'create signed commits' '\n \tgit tag twelfth-signed-alt $(cat oid)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.mintrustlevel UNDEFINED &&\n@@ -122,6 +139,31 @@ test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature'\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit succeeds with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-commit timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.minTrustLevel fully &&\n-- \n2.31.1\n\n"},{"id":"442657","messageId":"20211130141112.78193-5-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 4/7] ssh signing: make git log verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:09Z","receivedAt":"2021-11-30T14:12:01Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling git log.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n log-tree.c     |  2 ++\n t/t4202-log.sh | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex a46cf60e1e..d3e7a40b64 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,6 +513,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n@@ -583,6 +584,7 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n+\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 7884e3d46b..ba855ec893 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -1677,6 +1677,24 @@ test_expect_success GPGSSH 'setup sshkey signed branch' '\n \tgit commit -S -m signed_commit\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_config gpg.format ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSM 'log x509 fingerprint' '\n \techo \"F8BF62E0693D0694816377099909C779FA23FD65 | \" >expect &&\n \tgit log -n1 --format=\"%GF | %GP\" signed-x509 >actual &&\n@@ -1714,6 +1732,31 @@ test_expect_success GPGSSH 'log --graph --show-signature ssh' '\n \tgrep \"${GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 expired-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 notyetvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log show success with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedvalid-signed >actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedinvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPG 'log --graph --show-signature for merged tag' '\n \ttest_when_finished \"git reset --hard && git checkout main\" &&\n \tgit checkout -b plain main &&\n-- \n2.31.1\n\n"},{"id":"442659","messageId":"20211130141112.78193-6-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 5/7] ssh signing: make verify-tag consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:10Z","receivedAt":"2021-11-30T14:13:49Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling verify-tag.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t7031-verify-tag-signed-ssh.sh | 42 ++++++++++++++++++++++++++++++++\n tag.c                            |  1 +\n 2 files changed, 43 insertions(+)\n\ndiff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh\nindex 06c9dd6c93..1cb36b9ab8 100755\n--- a/t/t7031-verify-tag-signed-ssh.sh\n+++ b/t/t7031-verify-tag-signed-ssh.sh\n@@ -48,6 +48,23 @@ test_expect_success GPGSSH 'create signed tags ssh' '\n \tgit tag -u\"${GPGSSH_KEY_UNTRUSTED}\" -m eighth eighth-signed-alt\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show ssh signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \t(\n@@ -80,6 +97,31 @@ test_expect_success GPGSSH 'verify and show ssh signatures' '\n \t)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag succeeds with tag date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-tag timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'detect fudged ssh signature' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit cat-file tag seventh-signed >raw &&\ndiff --git a/tag.c b/tag.c\nindex 62fb09f5a5..dfbcd7fcc2 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,6 +25,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tret = check_signature(&sigc, signature.buf, signature.len);\n \n-- \n2.31.1\n\n"},{"id":"442660","messageId":"20211130141112.78193-7-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 6/7] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:11Z","receivedAt":"2021-11-30T14:13:53Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when generating merge messages to\nverify merged tags signatures key lifetimes.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n fmt-merge-msg.c          |  1 +\n t/t6200-fmt-merge-msg.sh | 54 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+)\n\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex deca1ea3a3..e4f7810be2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,6 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n+\t\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 06c5fb5615..2dd2423643 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\tgit checkout -b signed-expiry-ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success 'message for merging local branch' '\n \techo \"Merge branch ${apos}left${apos}\" >expected &&\n \n@@ -137,6 +157,40 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . expired-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . notyetvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedinvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success 'message for merging external branch' '\n \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n \n-- \n2.31.1\n\n"},{"id":"442661","messageId":"20211130141112.78193-8-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-11-30T14:11:12Z","receivedAt":"2021-11-30T14:13:58Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Do a full ssh signing, find-principals and verify operation in the test\nprereq's to make sure ssh-keygen works as expected. Only generating the\nkeys and verifying its presence is not sufficient in some situations.\nOne example was ssh-keygen creating unusable ssh keys in cygwin because\nof unsafe default permissions for the key files. The other a broken\nopenssh 8.7 that segfaulted on any find-principals operation. This\nextended prereq check avoids future test breakages in case ssh-keygen or\nany environment behaviour changes.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 53 +++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 40 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex fc03c8f89b..3fadfcb306 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -109,34 +109,61 @@ test_lazy_prereq GPGSSH '\n \techo $ssh_version | grep -q \"find-principals:missing signature file\"\n \ttest $? = 0 || exit 1;\n \n-\t# some broken versions of ssh-keygen segfault on find-principals;\n-\t# avoid testing with them.\n-\tssh-keygen -Y find-principals -f /dev/null -s /dev/null\n-\ttest $? = 139 && exit 1\n-\n+\t# Setup some keys and an allowed signers file\n \tmkdir -p \"${GNUPGHOME}\" &&\n \tchmod 0700 \"${GNUPGHOME}\" &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 1\\\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n+\n+\tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"principal with number 1\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\"\n+\t\"principal with number 2\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\"\n+\t\"principal with number 3\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\"\n+\tEOF\n+\n+\t# Verify if at least one key and ssh-keygen works as expected\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" >gpgssh_prereq.sig &&\n+\tssh-keygen -Y find-principals -f \"${GPGSSH_ALLOWED_SIGNERS}\" -s gpgssh_prereq.sig &&\n+\techo \"testpayload\" |\n+\tssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with number 1\" -s gpgssh_prereq.sig\n '\n \n test_lazy_prereq GPGSSH_VERIFYTIME '\n \t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n \tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\n+\t# Set up keys with key lifetimes\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_valid=$(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_invalid=$(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n-\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_expired=$(cat \"${GPGSSH_KEY_EXPIRED}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n-\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n+\tkey_notyetvalid=$(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\") &&\n+\n+\t# Timestamps outside of test_tick span\n+\tts2005a=20050401000000 ts2005b=200504020000 &&\n+\t# Timestamps within test_tick span\n+\tts2005c=20050407000000 ts2005d=200504100000 &&\n+\t# Definitely not yet valid / expired timestamps\n+\tts2000=20000101000000 ts2999=29990101000000 &&\n+\n+\tcat >>\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"timeboxed valid key\" valid-after=\"$ts2005c\",valid-before=\"$ts2005d\" $key_valid\"\n+\t\"timeboxed invalid key\" valid-after=\"$ts2005a\",valid-before=\"$ts2005b\" $key_invalid\"\n+\t\"principal with expired key\" valid-before=\"$ts2000\" $key_expired\"\n+\t\"principal with not yet valid key\" valid-after=\"$ts2999\" $key_notyetvalid\"\n+\tEOF\n+\n+\t# and verify ssh-keygen verifies the key lifetime\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" >gpgssh_verifytime_prereq.sig &&\n+\t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"442835","messageId":"xmqqczmfyi44.fsf@gitster.g","threadId":"56793","inReplyTo":"20211130141112.78193-8-fs@gigacodes.de","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-02T00:18:19Z","receivedAt":"2021-12-02T00:18:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fabian Stelzer <fs@gigacodes.de> writes:\n\n> Do a full ssh signing, find-principals and verify operation in the test\n> prereq's to make sure ssh-keygen works as expected. Only generating the\n> keys and verifying its presence is not sufficient in some situations.\n> One example was ssh-keygen creating unusable ssh keys in cygwin because\n> of unsafe default permissions for the key files. The other a broken\n> openssh 8.7 that segfaulted on any find-principals operation. This\n> extended prereq check avoids future test breakages in case ssh-keygen or\n> any environment behaviour changes.\n>\n> Signed-off-by: Fabian Stelzer <fs@gigacodes.de>\n> ---\n\nThe way keys are set-up has become much easier to follow.\n\nThis unfortunately interacts with the old way of adding a test key\ndone in <20211119150707.3924636-2-fs@gigacodes.de> 350a2518 (ssh\nsigning: support non ssh-* keytypes, 2021-11-19)\n\nHere is my attempt (which is in 'seen') to resolve the inevitable\nmerge conflicts between the topics. \n\nThanks.\n\ncommit fa6c2973744b419c95b5eaf6a697c795ab7823fa\nMerge: 2a8505f6a0 3b4b5a793a\nAuthor: Junio C Hamano <gitster@pobox.com>\nDate:   Wed Dec 1 16:01:54 2021 -0800\n\n    Merge branch 'fs/ssh-signing-other-keytypes' into jch\n    \n    * fs/ssh-signing-other-keytypes:\n      ssh signing: make sign/amend test more resilient\n      ssh signing: support non ssh-* keytypes\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex ff944f0548..3e7ee1386a 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -117,13 +117,14 @@ test_lazy_prereq GPGSSH '\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n-<<<<<<< 2a8505f6a0 (Merge branch 'fs/ssh-signing-key-lifetime' into jch)\n+\tssh-keygen -t ecdsa -N \"\" -f \"${GPGSSH_KEY_ECDSA}\" >/dev/null &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n \n \tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n \t\"principal with number 1\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\"\n \t\"principal with number 2\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\"\n \t\"principal with number 3\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\"\n+\t\"principal with number 4\" $(cat \"${GPGSSH_KEY_ECDSA}.pub\")\"\n \tEOF\n \n \t# Verify if at least one key and ssh-keygen works as expected\n@@ -166,15 +167,6 @@ test_lazy_prereq GPGSSH_VERIFYTIME '\n \techo \"testpayload\" |\n \tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" >gpgssh_verifytime_prereq.sig &&\n \t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n-||||||| cd3e606211\n-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n-=======\n-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ecdsa -N \"\" -f \"${GPGSSH_KEY_ECDSA}\" >/dev/null\n-\techo \"\\\"principal with number 4\\\" $(cat \"${GPGSSH_KEY_ECDSA}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n->>>>>>> 3b4b5a793a (ssh signing: make sign/amend test more resilient)\n '\n \n sanitize_pgp() {\n"},{"id":"442861","messageId":"20211202093126.nuuvxjnhbkdu5pwn@fs","threadId":"56793","inReplyTo":"xmqqczmfyi44.fsf@gitster.g","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-02T09:31:26Z","receivedAt":"2021-12-02T09:31:31Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 01.12.2021 16:18, Junio C Hamano wrote:\n>Fabian Stelzer <fs@gigacodes.de> writes:\n>\n>> Do a full ssh signing, find-principals and verify operation in the test\n>> prereq's to make sure ssh-keygen works as expected. Only generating the\n>> keys and verifying its presence is not sufficient in some situations.\n>> One example was ssh-keygen creating unusable ssh keys in cygwin because\n>> of unsafe default permissions for the key files. The other a broken\n>> openssh 8.7 that segfaulted on any find-principals operation. This\n>> extended prereq check avoids future test breakages in case ssh-keygen or\n>> any environment behaviour changes.\n>>\n>> Signed-off-by: Fabian Stelzer <fs@gigacodes.de>\n>> ---\n>\n>The way keys are set-up has become much easier to follow.\n>\n>This unfortunately interacts with the old way of adding a test key\n>done in <20211119150707.3924636-2-fs@gigacodes.de> 350a2518 (ssh\n>signing: support non ssh-* keytypes, 2021-11-19)\n>\n>Here is my attempt (which is in 'seen') to resolve the inevitable\n>merge conflicts between the topics.\n\nYes, that looks good. In this case the conflict is rather trivial, but how \ncould i prevent this / make it easier for you to merge these?\nEspecially since in this case the conflict only arose after a reroll when \nboth topics were already in seen. For a new topic i can of course make them \nas \"on top of XXX\". Should I in the future rebase the \"support non ssh-* \nkeytypes\" topic on top of this series and mark it as such? Or whats a good \nway to deal with things like this? (besides avoiding merge conflicts \naltogether :D)\n\nThanks\n\n>\n>Thanks.\n>\n>commit fa6c2973744b419c95b5eaf6a697c795ab7823fa\n>Merge: 2a8505f6a0 3b4b5a793a\n>Author: Junio C Hamano <gitster@pobox.com>\n>Date:   Wed Dec 1 16:01:54 2021 -0800\n>\n>    Merge branch 'fs/ssh-signing-other-keytypes' into jch\n>\n>    * fs/ssh-signing-other-keytypes:\n>      ssh signing: make sign/amend test more resilient\n>      ssh signing: support non ssh-* keytypes\n>\n>diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\n>index ff944f0548..3e7ee1386a 100644\n>--- a/t/lib-gpg.sh\n>+++ b/t/lib-gpg.sh\n>@@ -117,13 +117,14 @@ test_lazy_prereq GPGSSH '\n> \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n> \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n> \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n>-<<<<<<< 2a8505f6a0 (Merge branch 'fs/ssh-signing-key-lifetime' into jch)\n>+\tssh-keygen -t ecdsa -N \"\" -f \"${GPGSSH_KEY_ECDSA}\" >/dev/null &&\n> \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n>\n> \tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n> \t\"principal with number 1\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\"\n> \t\"principal with number 2\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\"\n> \t\"principal with number 3\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\"\n>+\t\"principal with number 4\" $(cat \"${GPGSSH_KEY_ECDSA}.pub\")\"\n> \tEOF\n>\n> \t# Verify if at least one key and ssh-keygen works as expected\n>@@ -166,15 +167,6 @@ test_lazy_prereq GPGSSH_VERIFYTIME '\n> \techo \"testpayload\" |\n> \tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" >gpgssh_verifytime_prereq.sig &&\n> \t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n>-||||||| cd3e606211\n>-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n>-=======\n>-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>-\tssh-keygen -t ecdsa -N \"\" -f \"${GPGSSH_KEY_ECDSA}\" >/dev/null\n>-\techo \"\\\"principal with number 4\\\" $(cat \"${GPGSSH_KEY_ECDSA}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n>->>>>>>> 3b4b5a793a (ssh signing: make sign/amend test more resilient)\n> '\n>\n> sanitize_pgp() {\n"},{"id":"442883","messageId":"xmqqwnknue3n.fsf@gitster.g","threadId":"56793","inReplyTo":"20211202093126.nuuvxjnhbkdu5pwn@fs","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-02T17:10:52Z","receivedAt":"2021-12-02T17:10:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fabian Stelzer <fs@gigacodes.de> writes:\n\n> Yes, that looks good. In this case the conflict is rather trivial, but\n> how could i prevent this / make it easier for you to merge these?\n> Especially since in this case the conflict only arose after a reroll\n> when both topics were already in seen. For a new topic i can of course\n> make them as \"on top of XXX\". Should I in the future rebase the\n> \"support non ssh-* keytypes\" topic on top of this series and mark it\n> as such? Or whats a good way to deal with things like this? (besides\n> avoiding merge conflicts altogether :D)\n\nFor this particular one, my rerere database already knows how the\nconflict and its resolution should look like, so there is nothing\nthat is urgently needed.\n\nIf the other topic were to be rerolled, since either has hit 'next',\nbasing it on top of the other, essentially making the result into a\nsingle series, may be the easiest (and that is basically avoiding\nconflicts altogether ;-).\n\nThanks.\n"},{"id":"442968","messageId":"211203.864k7qszy7.gmgdl@evledraar.gmail.com","threadId":"56793","inReplyTo":"xmqqwnknue3n.fsf@gitster.g","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-03T11:07:45Z","receivedAt":"2021-12-03T11:14:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Dec 02 2021, Junio C Hamano wrote:\n\n> Fabian Stelzer <fs@gigacodes.de> writes:\n>\n>> Yes, that looks good. In this case the conflict is rather trivial, but\n>> how could i prevent this / make it easier for you to merge these?\n>> Especially since in this case the conflict only arose after a reroll\n>> when both topics were already in seen. For a new topic i can of course\n>> make them as \"on top of XXX\". Should I in the future rebase the\n>> \"support non ssh-* keytypes\" topic on top of this series and mark it\n>> as such? Or whats a good way to deal with things like this? (besides\n>> avoiding merge conflicts altogether :D)\n>\n> For this particular one, my rerere database already knows how the\n> conflict and its resolution should look like, so there is nothing\n> that is urgently needed.\n>\n> If the other topic were to be rerolled, since either has hit 'next',\n> basing it on top of the other, essentially making the result into a\n> single series, may be the easiest (and that is basically avoiding\n> conflicts altogether ;-).\n\n...but to answer a bit of Fabian's question: Just as someone giving\nthese two topics a brief look it's not clear to me why the existing\nGPGSSH prerequisite needs an adjustment at the same time as adding a\ntest that uses it (in addition to existing tests).\n\nI.e. was it that it was always wrong, in that case I'd expect a patch\nthat fixes the prereq and doesn't make any other test changes in the\nsame commit as [1] does.\n\nOr does it need to be more strict to cater to one new test being added\nin the same commit, but that strictness doesn't apply to existing tests?\n\nThen maybe it should be a new GPGSSH_THAT_NEW_REQUIRED_FEATURE, which\ncan in turn depend on the GPGSSH prerequisite.\n\nWhich, incidentally would help with any textual conflict, but more\nimportantly makes for clearer end-state, and maps prerequisites to those\nexisting tests that need those specific things, and not a more stricter\n& recent requirement.\n\nI don't know/think that any of this needs re-rolling, just my 0.02.\n\n1. https://lore.kernel.org/git/20211119150707.3924636-2-fs@gigacodes.de/\n"},{"id":"442970","messageId":"20211203122009.lvuz2w7xrp7yapcw@fs","threadId":"56793","inReplyTo":"211203.864k7qszy7.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-03T12:20:09Z","receivedAt":"2021-12-03T12:20:14Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 03.12.2021 12:07, Ævar Arnfjörð Bjarmason wrote:\n>\n>On Thu, Dec 02 2021, Junio C Hamano wrote:\n>\n>> Fabian Stelzer <fs@gigacodes.de> writes:\n>>\n>>> Yes, that looks good. In this case the conflict is rather trivial, but\n>>> how could i prevent this / make it easier for you to merge these?\n>>> Especially since in this case the conflict only arose after a reroll\n>>> when both topics were already in seen. For a new topic i can of course\n>>> make them as \"on top of XXX\". Should I in the future rebase the\n>>> \"support non ssh-* keytypes\" topic on top of this series and mark it\n>>> as such? Or whats a good way to deal with things like this? (besides\n>>> avoiding merge conflicts altogether :D)\n>>\n>> For this particular one, my rerere database already knows how the\n>> conflict and its resolution should look like, so there is nothing\n>> that is urgently needed.\n>>\n>> If the other topic were to be rerolled, since either has hit 'next',\n>> basing it on top of the other, essentially making the result into a\n>> single series, may be the easiest (and that is basically avoiding\n>> conflicts altogether ;-).\n>\n>...but to answer a bit of Fabian's question: Just as someone giving\n>these two topics a brief look it's not clear to me why the existing\n>GPGSSH prerequisite needs an adjustment at the same time as adding a\n>test that uses it (in addition to existing tests).\n>\n>I.e. was it that it was always wrong, in that case I'd expect a patch\n>that fixes the prereq and doesn't make any other test changes in the\n>same commit as [1] does.\n>\n>Or does it need to be more strict to cater to one new test being added\n>in the same commit, but that strictness doesn't apply to existing tests?\n>\n>Then maybe it should be a new GPGSSH_THAT_NEW_REQUIRED_FEATURE, which\n>can in turn depend on the GPGSSH prerequisite.\n>\nWhat [1] needed was another keytype set up for some tests to run. I could \nhave done this in the test itself or in a separate setup function. But since \nall the other keys are set up in the prereq and we might want to reuse this \nnew ecdsa key in other tests as well this seemed like the better place for \nit.\nA new prereq depending on GPGSSH feels kinda wrong to me when it would just \ncreate the key and always succeed otherwise. I guess this is a side effect \nof using the prereq to do setup as well (something that we discussed in \nhttps://lore.kernel.org/git/YYXAwxmhrLLMBqa+@coredump.intra.peff.net/ ).\n\n>Which, incidentally would help with any textual conflict, but more\n>importantly makes for clearer end-state, and maps prerequisites to those\n>existing tests that need those specific things, and not a more stricter\n>& recent requirement.\n>\n\nThe problem here was just that I also refactored the existing GPGSSH prereq \nto be more readable while doing the same for the new one. Only refactoring \nthe new one while leaving the older one just above it untouched would have \navoided the conflict but would have needed a follow up series. In the future \nI would probably do that or leave the code as is and refactor both in a \nfollow up.\n\n>I don't know/think that any of this needs re-rolling, just my 0.02.\n>\n>1. https://lore.kernel.org/git/20211119150707.3924636-2-fs@gigacodes.de/\n\nAlways glad for your input.\nThanks\n"},{"id":"443015","messageId":"xmqqo85xsf03.fsf@gitster.g","threadId":"56793","inReplyTo":"xmqqwnknue3n.fsf@gitster.g","subject":"Re: [PATCH v4 7/7] ssh signing: verify ssh-keygen in test prereq","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-03T18:46:36Z","receivedAt":"2021-12-03T18:46:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Fabian Stelzer <fs@gigacodes.de> writes:\n>\n>> Yes, that looks good. In this case the conflict is rather trivial, but\n>> how could i prevent this / make it easier for you to merge these?\n>> Especially since in this case the conflict only arose after a reroll\n>> when both topics were already in seen. For a new topic i can of course\n>> make them as \"on top of XXX\". Should I in the future rebase the\n>> \"support non ssh-* keytypes\" topic on top of this series and mark it\n>> as such? Or whats a good way to deal with things like this? (besides\n>> avoiding merge conflicts altogether :D)\n>\n> For this particular one, my rerere database already knows how the\n> conflict and its resolution should look like, so there is nothing\n> that is urgently needed.\n>\n> If the other topic were to be rerolled, since either has hit 'next',\n\nSorry, \"since neither has hit 'next' yet,\" was what I meant to type.\nNot being in 'next' allows us to replace the series without having\nto do \"oops, that earlier one was wrong, and here is a band-aid\".\n\nThanks.\n\n> basing it on top of the other, essentially making the result into a\n> single series, may be the easiest (and that is basically avoiding\n> conflicts altogether ;-).\n>\n> Thanks.\n"},{"id":"443111","messageId":"20211205192344.GB624717@szeder.dev","threadId":"56793","inReplyTo":"20211130141112.78193-7-fs@gigacodes.de","subject":"Re: [PATCH v4 6/7] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-12-05T19:23:44Z","receivedAt":"2021-12-05T19:23:49Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Nov 30, 2021 at 03:11:11PM +0100, Fabian Stelzer wrote:\n> diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\n> index 06c5fb5615..2dd2423643 100755\n> --- a/t/t6200-fmt-merge-msg.sh\n> +++ b/t/t6200-fmt-merge-msg.sh\n> @@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n>  \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n>  '\n>  \n> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n> +\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n> +\ttest_config gpg.format ssh &&\n> +\tgit checkout -b signed-expiry-ssh &&\n> +\ttouch file &&\n> +\tgit add file &&\n> +\n> +\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n> +\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n> +\n> +\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n> +\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n> +\n> +\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n> +\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n> +\n> +\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n> +\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n> +'\n> +\n>  test_expect_success 'message for merging local branch' '\n>  \techo \"Merge branch ${apos}left${apos}\" >expected &&\n>  \n> @@ -137,6 +157,40 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n>  \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n>  \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n>  '\n> +\n> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n> +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n> +\tgit checkout main &&\n> +\tgit fetch . expired-signed &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n\nAre these messages supposed to go to standard output or error?  If\nit's the former, then please drop the unnecessary (and confusing)\n'2>&1' redirection, but if it's the latter, then save and 'grep' only\nstderr.\n\n> +'\n> +\n> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n> +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n> +\tgit checkout main &&\n> +\tgit fetch . notyetvalid-signed &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n> +'\n> +\n> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n> +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n> +\tgit checkout main &&\n> +\tgit fetch . timeboxedvalid-signed &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n> +\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n> +'\n> +\n> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n> +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n> +\tgit checkout main &&\n> +\tgit fetch . timeboxedinvalid-signed &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n> +'\n> +\n>  test_expect_success 'message for merging external branch' '\n>  \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n>  \n> -- \n> 2.31.1\n> \n"},{"id":"443469","messageId":"20211208155954.4ksd7cmowhhjeig5@fs","threadId":"56793","inReplyTo":"20211205192344.GB624717@szeder.dev","subject":"Re: [PATCH v4 6/7] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T15:59:54Z","receivedAt":"2021-12-08T16:00:00Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 05.12.2021 20:23, SZEDER Gábor wrote:\n>On Tue, Nov 30, 2021 at 03:11:11PM +0100, Fabian Stelzer wrote:\n>> +\n>> +test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n>> +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>> +\tgit checkout main &&\n>> +\tgit fetch . expired-signed &&\n>> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n>> +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n>\n>Are these messages supposed to go to standard output or error?  If\n>it's the former, then please drop the unnecessary (and confusing)\n>'2>&1' redirection, but if it's the latter, then save and 'grep' only\n>stderr.\n>\n\nYou are right, these are not correct. I copied the fmt-merge-msg calls from \nthe GPG tests a bit above which grep for the merged tag name and for gpg \nsignature output (i assume those two things go to stdout/err respectively).  \nI will check and create a new patch series for fixing up all of these.\n\nThanks\n"},{"id":"443470","messageId":"20211208163335.1231795-1-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211130141112.78193-1-fs@gigacodes.de","subject":"[PATCH v5 0/8] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:27Z","receivedAt":"2021-12-08T16:33:52Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"changes since v4:\n - removed unneccessary io redir in merge-msg tests\n - added grep for merged tag to gpgssh merge-msg tests\n\nchanges since v3:\n - improve readability of prereq setup code by using heredoc and some\n   variables\n\nchanges since v2:\n - fix swich/case indentation\n - BUG() on unknown payload types\n - improve test prereq by actually validating ssh-keygen functionality\n\nchanges since v1:\n - struct signature_check is now used to input payload data into\n   check_function\n - payload metadata parsing is completely internal to check_signature.\n   the caller only need to set the payload type in the sigc struct\n - small nits and readability fixes\n - removed payload_signer parameter. since we now use the struct we can\n   extend\n   this later.\n\nFabian Stelzer (8):\n  ssh signing: use sigc struct to pass payload\n  ssh signing: add key lifetime test prereqs\n  ssh signing: make verify-commit consider key lifetime\n  ssh signing: make git log verify key lifetime\n  ssh signing: make verify-tag consider key lifetime\n  ssh signing: make fmt-merge-msg consider key lifetime\n  ssh signing: verify ssh-keygen in test prereq\n  t/fmt-merge-msg: make gpg/ssh tests more specific\n\n Documentation/config/gpg.txt     |  5 ++\n builtin/receive-pack.c           |  6 ++-\n commit.c                         |  6 ++-\n fmt-merge-msg.c                  |  5 +-\n gpg-interface.c                  | 90 +++++++++++++++++++++++++-------\n gpg-interface.h                  | 15 ++++--\n log-tree.c                       | 10 ++--\n t/lib-gpg.sh                     | 62 ++++++++++++++++++----\n t/t4202-log.sh                   | 43 +++++++++++++++\n t/t6200-fmt-merge-msg.sh         | 67 ++++++++++++++++++++++--\n t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n tag.c                            |  5 +-\n 13 files changed, 350 insertions(+), 48 deletions(-)\n\nRange-diff against v4:\n1:  c4447d30f2 = 1:  c4447d30f2 ssh signing: use sigc struct to pass payload\n2:  0bb1617529 = 2:  0bb1617529 ssh signing: add key lifetime test prereqs\n3:  f60bd1efd0 = 3:  f60bd1efd0 ssh signing: make verify-commit consider key lifetime\n4:  5fc0ad5c37 = 4:  5fc0ad5c37 ssh signing: make git log verify key lifetime\n5:  f1c225871f = 5:  f1c225871f ssh signing: make verify-tag consider key lifetime\n6:  1cbd4dbb6b = 6:  1cbd4dbb6b ssh signing: make fmt-merge-msg consider key lifetime\n7:  d60f4ec82c = 7:  d60f4ec82c ssh signing: verify ssh-keygen in test prereq\n-:  ---------- > 8:  679a02d1fe t/fmt-merge-msg: make gpg/ssh tests more specific\n-- \n2.31.1\n\n"},{"id":"443471","messageId":"20211208163335.1231795-2-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 1/8] ssh signing: use sigc struct to pass payload","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:28Z","receivedAt":"2021-12-08T16:33:53Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"To be able to extend the payload metadata with things like its creation\ntimestamp or the creators ident we remove the payload parameters to\ncheck_signature() and use the already existing sigc->payload field\ninstead, only adding the length field to the struct. This also allows\nus to get rid of the xmemdupz() calls in the verify functions. Since\nsigc is now used to input data as well as output the result move it to\nthe front of the function list.\n\n - Add payload_length to struct signature_check\n - Populate sigc.payload/payload_len on all call sites\n - Remove payload parameters to check_signature()\n - Remove payload parameters to internal verify_* functions and use sigc\n   instead\n - Remove xmemdupz() used for verbose output since payload is now already\n   populated.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n builtin/receive-pack.c |  6 ++++--\n commit.c               |  5 +++--\n fmt-merge-msg.c        |  4 ++--\n gpg-interface.c        | 37 +++++++++++++++++--------------------\n gpg-interface.h        |  6 +++---\n log-tree.c             |  8 ++++----\n tag.c                  |  4 ++--\n 7 files changed, 35 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d960..61ab63c2ea 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -769,8 +769,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n \n \t\tbogs = parse_signed_buffer(push_cert.buf, push_cert.len);\n-\t\tcheck_signature(push_cert.buf, bogs, push_cert.buf + bogs,\n-\t\t\t\tpush_cert.len - bogs, &sigcheck);\n+\t\tsigcheck.payload = xmemdupz(push_cert.buf, bogs);\n+\t\tsigcheck.payload_len = bogs;\n+\t\tcheck_signature(&sigcheck, push_cert.buf + bogs,\n+\t\t\t\tpush_cert.len - bogs);\n \n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\ndiff --git a/commit.c b/commit.c\nindex 551de4903c..64e040a99b 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1212,8 +1212,9 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\tsignature.len, sigc);\n+\n+\tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n+\tret = check_signature(sigc, signature.buf, signature.len);\n \n  out:\n \tstrbuf_release(&payload);\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5216191488..deca1ea3a3 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,8 +533,8 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n-\t\t\tif (check_signature(payload.buf, payload.len, sig.buf,\n-\t\t\t\t\t    sig.len, &sigc) &&\n+\t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\n \t\t\t\tstrbuf_addstr(&sig, \"gpg verification failed.\\n\");\n \t\t\telse\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3e7255a2a9..75ab6faacb 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -19,8 +19,8 @@ struct gpg_format {\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n \tint (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {\n };\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -314,8 +314,8 @@ static void parse_gpg_output(struct signature_check *sigc)\n }\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process gpg = CHILD_PROCESS_INIT;\n@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,\n \t\t     NULL);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n-\tret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,\n+\tret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,\n \t\t\t   &gpg_stderr, 0);\n \tsigchain_pop(SIGPIPE);\n \n \tdelete_tempfile(&temp);\n \n \tret |= !strstr(gpg_stdout.buf, \"\\n[GNUPG:] GOODSIG \");\n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tsigc->output = strbuf_detach(&gpg_stderr, NULL);\n \tsigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);\n \n@@ -426,8 +425,8 @@ static void parse_ssh_output(struct signature_check *sigc)\n }\n \n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process ssh_keygen = CHILD_PROCESS_INIT;\n@@ -480,7 +479,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n \t\t\t     NULL);\n-\t\tpipe_command(&ssh_keygen, payload, payload_size,\n+\t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \n \t\t/*\n@@ -526,7 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t}\n \n \t\t\tsigchain_push(SIGPIPE, SIG_IGN);\n-\t\t\tret = pipe_command(&ssh_keygen, payload, payload_size,\n+\t\t\tret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \t\t\tsigchain_pop(SIGPIPE);\n \n@@ -540,7 +539,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t}\n \t}\n \n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tstrbuf_stripspace(&ssh_keygen_out, 0);\n \tstrbuf_stripspace(&ssh_keygen_err, 0);\n \t/* Add stderr outputs to show the user actual ssh-keygen errors */\n@@ -562,8 +560,8 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \treturn ret;\n }\n \n-int check_signature(const char *payload, size_t plen, const char *signature,\n-\tsize_t slen, struct signature_check *sigc)\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen)\n {\n \tstruct gpg_format *fmt;\n \tint status;\n@@ -575,8 +573,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n-\tstatus = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,\n-\t\t\t\t\t   slen);\n+\tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\n \t\treturn !!status;\n@@ -593,7 +590,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)\n \t\t\t\t\t\t\t    sigc->output;\n \n \tif (flags & GPG_VERIFY_VERBOSE && sigc->payload)\n-\t\tfputs(sigc->payload, stdout);\n+\t\tfwrite(sigc->payload, 1, sigc->payload_len, stdout);\n \n \tif (output)\n \t\tfputs(output, stderr);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex beefacbb1e..5ee7d8b6b9 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -17,6 +17,7 @@ enum signature_trust_level {\n \n struct signature_check {\n \tchar *payload;\n+\tsize_t payload_len;\n \tchar *output;\n \tchar *gpg_status;\n \n@@ -70,9 +71,8 @@ const char *get_signing_key(void);\n  * Either a GPG KeyID or a SSH Key Fingerprint\n  */\n const char *get_signing_key_id(void);\n-int check_signature(const char *payload, size_t plen,\n-\t\t    const char *signature, size_t slen,\n-\t\t    struct signature_check *sigc);\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen);\n void print_signature_buffer(const struct signature_check *sigc,\n \t\t\t    unsigned flags);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 644893fd8c..a46cf60e1e 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,8 +513,8 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n-\tstatus = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\t signature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n \t\tshow_sig_lines(opt, status, \"No signature\\n\");\n \telse\n@@ -583,8 +583,8 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n-\t\tstatus = check_signature(payload.buf, payload.len,\n-\t\t\t\t\t signature.buf, signature.len, &sigc);\n+\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\n \t\t\tstrbuf_addstr(&verify_message, sigc.output);\n \t\telse\ndiff --git a/tag.c b/tag.c\nindex 3e18a41841..62fb09f5a5 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,8 +25,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\tsignature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tret = check_signature(&sigc, signature.buf, signature.len);\n \n \tif (!(flags & GPG_VERIFY_OMIT_STATUS))\n \t\tprint_signature_buffer(&sigc, flags);\n-- \n2.31.1\n\n"},{"id":"443472","messageId":"20211208163335.1231795-3-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 2/8] ssh signing: add key lifetime test prereqs","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:29Z","receivedAt":"2021-12-08T16:33:58Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"if ssh-keygen supports -Overify-time, add test keys marked as expired,\nnot yet valid and valid both within the test_tick timeframe and outside of it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex a3f285f515..fc03c8f89b 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -90,6 +90,10 @@ test_lazy_prereq RFC1991 '\n GPGSSH_KEY_PRIMARY=\"${GNUPGHOME}/ed25519_ssh_signing_key\"\n GPGSSH_KEY_SECONDARY=\"${GNUPGHOME}/rsa_2048_ssh_signing_key\"\n GPGSSH_KEY_UNTRUSTED=\"${GNUPGHOME}/untrusted_ssh_signing_key\"\n+GPGSSH_KEY_EXPIRED=\"${GNUPGHOME}/expired_ssh_signing_key\"\n+GPGSSH_KEY_NOTYETVALID=\"${GNUPGHOME}/notyetvalid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDVALID=\"${GNUPGHOME}/timeboxed_valid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDINVALID=\"${GNUPGHOME}/timeboxed_invalid_ssh_signing_key\"\n GPGSSH_KEY_WITH_PASSPHRASE=\"${GNUPGHOME}/protected_ssh_signing_key\"\n GPGSSH_KEY_PASSPHRASE=\"super_secret\"\n GPGSSH_ALLOWED_SIGNERS=\"${GNUPGHOME}/ssh.all_valid.allowedSignersFile\"\n@@ -119,7 +123,20 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+'\n+\n+test_lazy_prereq GPGSSH_VERIFYTIME '\n+\t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n+\tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n+\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n+\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"443473","messageId":"20211208163335.1231795-4-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 3/8] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:30Z","receivedAt":"2021-12-08T16:33:58Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"If valid-before/after dates are configured for this signatures key in the\nallowedSigners file then the verification should check if the key was valid at\nthe time the commit was made. This allows for graceful key rollover and\nrevoking keys without invalidating all previous commits.\nThis feature needs openssh > 8.8. Older ssh-keygen versions will simply\nignore this flag and use the current time.\nStrictly speaking this feature is available in 8.7, but since 8.7 has a\nbug that makes it unusable in another needed call we require 8.8.\n\nTimestamp information is present on most invocations of check_signature.\nHowever signer ident is not. We will need the signer email / name to be able\nto implement \"Trust on first use\" functionality later.\nSince the payload contains all necessary information we can parse it\nfrom there. The caller only needs to provide us some info about the\npayload by setting payload_type in the signature_check struct.\n\n - Add payload_type field & enum and payload_timestamp to struct\n   signature_check\n - Populate the timestamp when not already set if we know about the\n   payload type\n - Pass -Overify-time={payload_timestamp} in the users timezone to all\n   ssh-keygen verification calls\n - Set the payload type when verifying commits\n - Add tests for expired, not yet valid and keys having a commit date\n   outside of key validity as well as within\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n Documentation/config/gpg.txt |  5 ++++\n commit.c                     |  1 +\n gpg-interface.c              | 53 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h              |  9 ++++++\n t/t7528-signed-commit-ssh.sh | 42 ++++++++++++++++++++++++++++\n 5 files changed, 110 insertions(+)\n\ndiff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt\nindex 4f30c7dbdd..c9be554c73 100644\n--- a/Documentation/config/gpg.txt\n+++ b/Documentation/config/gpg.txt\n@@ -64,6 +64,11 @@ A repository that only allows signed commits can store the file\n in the repository itself using a path relative to the top-level of the working tree.\n This way only committers with an already valid key can add or change keys in the keyring.\n +\n+Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &\n+valid-before options. Git will mark signatures as valid if the signing key was\n+valid at the time of the signatures creation. This allows users to change a\n+signing key without invalidating all previously made signatures.\n++\n Using a SSH CA key with the cert-authority option\n (see ssh-keygen(1) \"CERTIFICATES\") is also valid.\n \ndiff --git a/commit.c b/commit.c\nindex 64e040a99b..a348f085b2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1213,6 +1213,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n \tret = check_signature(sigc, signature.buf, signature.len);\n \ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 75ab6faacb..330cfc5845 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -439,6 +439,13 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstruct strbuf ssh_principals_err = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_out = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_err = STRBUF_INIT;\n+\tstruct strbuf verify_time = STRBUF_INIT;\n+\tconst struct date_mode verify_date_mode = {\n+\t\t.type = DATE_STRFTIME,\n+\t\t.strftime_fmt = \"%Y%m%d%H%M%S\",\n+\t\t/* SSH signing key validity has no timezone information - Use the local timezone */\n+\t\t.local = 1,\n+\t};\n \n \tif (!ssh_allowed_signers) {\n \t\terror(_(\"gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification\"));\n@@ -456,11 +463,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\treturn -1;\n \t}\n \n+\tif (sigc->payload_timestamp)\n+\t\tstrbuf_addf(&verify_time, \"-Overify-time=%s\",\n+\t\t\tshow_date(sigc->payload_timestamp, 0, &verify_date_mode));\n+\n \t/* Find the principal from the signers */\n \tstrvec_pushl(&ssh_keygen.args, fmt->program,\n \t\t     \"-Y\", \"find-principals\",\n \t\t     \"-f\", ssh_allowed_signers,\n \t\t     \"-s\", buffer_file->filename.buf,\n+\t\t     verify_time.buf,\n \t\t     NULL);\n \tret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,\n \t\t\t   &ssh_principals_err, 0);\n@@ -478,6 +490,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-Y\", \"check-novalidate\",\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t     verify_time.buf,\n \t\t\t     NULL);\n \t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n@@ -512,6 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t\t     \"-f\", ssh_allowed_signers,\n \t\t\t\t     \"-I\", principal,\n \t\t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t\t     verify_time.buf,\n \t\t\t\t     NULL);\n \n \t\t\tif (ssh_revocation_file) {\n@@ -556,10 +570,46 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstrbuf_release(&ssh_principals_err);\n \tstrbuf_release(&ssh_keygen_out);\n \tstrbuf_release(&ssh_keygen_err);\n+\tstrbuf_release(&verify_time);\n \n \treturn ret;\n }\n \n+static int parse_payload_metadata(struct signature_check *sigc)\n+{\n+\tconst char *ident_line = NULL;\n+\tsize_t ident_len;\n+\tstruct ident_split ident;\n+\tconst char *signer_header;\n+\n+\tswitch (sigc->payload_type) {\n+\tcase SIGNATURE_PAYLOAD_COMMIT:\n+\t\tsigner_header = \"committer\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_TAG:\n+\t\tsigner_header = \"tagger\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_UNDEFINED:\n+\tcase SIGNATURE_PAYLOAD_PUSH_CERT:\n+\t\t/* Ignore payloads we don't want to parse */\n+\t\treturn 0;\n+\tdefault:\n+\t\tBUG(\"invalid value for sigc->payload_type\");\n+\t}\n+\n+\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n+\tif (!ident_line || !ident_len)\n+\t\treturn 1;\n+\n+\tif (split_ident_line(&ident, ident_line, ident_len))\n+\t\treturn 1;\n+\n+\tif (!sigc->payload_timestamp && ident.date_begin && ident.date_end)\n+\t\tsigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n int check_signature(struct signature_check *sigc,\n \t\t    const char *signature, size_t slen)\n {\n@@ -573,6 +623,9 @@ int check_signature(struct signature_check *sigc,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n+\tif (parse_payload_metadata(sigc))\n+\t\treturn 1;\n+\n \tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 5ee7d8b6b9..b30cbdcd3d 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -15,9 +15,18 @@ enum signature_trust_level {\n \tTRUST_ULTIMATE,\n };\n \n+enum payload_type {\n+\tSIGNATURE_PAYLOAD_UNDEFINED,\n+\tSIGNATURE_PAYLOAD_COMMIT,\n+\tSIGNATURE_PAYLOAD_TAG,\n+\tSIGNATURE_PAYLOAD_PUSH_CERT,\n+};\n+\n struct signature_check {\n \tchar *payload;\n \tsize_t payload_len;\n+\tenum payload_type payload_type;\n+\ttimestamp_t payload_timestamp;\n \tchar *output;\n \tchar *gpg_status;\n \ndiff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh\nindex badf3ed320..dae76ded0c 100755\n--- a/t/t7528-signed-commit-ssh.sh\n+++ b/t/t7528-signed-commit-ssh.sh\n@@ -76,6 +76,23 @@ test_expect_success GPGSSH 'create signed commits' '\n \tgit tag twelfth-signed-alt $(cat oid)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.mintrustlevel UNDEFINED &&\n@@ -122,6 +139,31 @@ test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature'\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit succeeds with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-commit timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.minTrustLevel fully &&\n-- \n2.31.1\n\n"},{"id":"443474","messageId":"20211208163335.1231795-5-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 4/8] ssh signing: make git log verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:31Z","receivedAt":"2021-12-08T16:33:59Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling git log.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n log-tree.c     |  2 ++\n t/t4202-log.sh | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex a46cf60e1e..d3e7a40b64 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,6 +513,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n@@ -583,6 +584,7 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n+\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 7884e3d46b..ba855ec893 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -1677,6 +1677,24 @@ test_expect_success GPGSSH 'setup sshkey signed branch' '\n \tgit commit -S -m signed_commit\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_config gpg.format ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSM 'log x509 fingerprint' '\n \techo \"F8BF62E0693D0694816377099909C779FA23FD65 | \" >expect &&\n \tgit log -n1 --format=\"%GF | %GP\" signed-x509 >actual &&\n@@ -1714,6 +1732,31 @@ test_expect_success GPGSSH 'log --graph --show-signature ssh' '\n \tgrep \"${GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 expired-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 notyetvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log show success with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedvalid-signed >actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedinvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPG 'log --graph --show-signature for merged tag' '\n \ttest_when_finished \"git reset --hard && git checkout main\" &&\n \tgit checkout -b plain main &&\n-- \n2.31.1\n\n"},{"id":"443475","messageId":"20211208163335.1231795-6-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 5/8] ssh signing: make verify-tag consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:32Z","receivedAt":"2021-12-08T16:34:01Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling verify-tag.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t7031-verify-tag-signed-ssh.sh | 42 ++++++++++++++++++++++++++++++++\n tag.c                            |  1 +\n 2 files changed, 43 insertions(+)\n\ndiff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh\nindex 06c9dd6c93..1cb36b9ab8 100755\n--- a/t/t7031-verify-tag-signed-ssh.sh\n+++ b/t/t7031-verify-tag-signed-ssh.sh\n@@ -48,6 +48,23 @@ test_expect_success GPGSSH 'create signed tags ssh' '\n \tgit tag -u\"${GPGSSH_KEY_UNTRUSTED}\" -m eighth eighth-signed-alt\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show ssh signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \t(\n@@ -80,6 +97,31 @@ test_expect_success GPGSSH 'verify and show ssh signatures' '\n \t)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag succeeds with tag date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-tag timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'detect fudged ssh signature' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit cat-file tag seventh-signed >raw &&\ndiff --git a/tag.c b/tag.c\nindex 62fb09f5a5..dfbcd7fcc2 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,6 +25,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tret = check_signature(&sigc, signature.buf, signature.len);\n \n-- \n2.31.1\n\n"},{"id":"443476","messageId":"20211208163335.1231795-7-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 6/8] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:33Z","receivedAt":"2021-12-08T16:34:05Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when generating merge messages to\nverify merged tags signatures key lifetimes.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n fmt-merge-msg.c          |  1 +\n t/t6200-fmt-merge-msg.sh | 54 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+)\n\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex deca1ea3a3..e4f7810be2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,6 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n+\t\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 06c5fb5615..2dd2423643 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\tgit checkout -b signed-expiry-ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success 'message for merging local branch' '\n \techo \"Merge branch ${apos}left${apos}\" >expected &&\n \n@@ -137,6 +157,40 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . expired-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . notyetvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedinvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success 'message for merging external branch' '\n \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n \n-- \n2.31.1\n\n"},{"id":"443477","messageId":"20211208163335.1231795-8-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 7/8] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:34Z","receivedAt":"2021-12-08T16:34:07Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Do a full ssh signing, find-principals and verify operation in the test\nprereq's to make sure ssh-keygen works as expected. Only generating the\nkeys and verifying its presence is not sufficient in some situations.\nOne example was ssh-keygen creating unusable ssh keys in cygwin because\nof unsafe default permissions for the key files. The other a broken\nopenssh 8.7 that segfaulted on any find-principals operation. This\nextended prereq check avoids future test breakages in case ssh-keygen or\nany environment behaviour changes.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 53 +++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 40 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex fc03c8f89b..3fadfcb306 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -109,34 +109,61 @@ test_lazy_prereq GPGSSH '\n \techo $ssh_version | grep -q \"find-principals:missing signature file\"\n \ttest $? = 0 || exit 1;\n \n-\t# some broken versions of ssh-keygen segfault on find-principals;\n-\t# avoid testing with them.\n-\tssh-keygen -Y find-principals -f /dev/null -s /dev/null\n-\ttest $? = 139 && exit 1\n-\n+\t# Setup some keys and an allowed signers file\n \tmkdir -p \"${GNUPGHOME}\" &&\n \tchmod 0700 \"${GNUPGHOME}\" &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 1\\\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n+\n+\tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"principal with number 1\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\"\n+\t\"principal with number 2\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\"\n+\t\"principal with number 3\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\"\n+\tEOF\n+\n+\t# Verify if at least one key and ssh-keygen works as expected\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" >gpgssh_prereq.sig &&\n+\tssh-keygen -Y find-principals -f \"${GPGSSH_ALLOWED_SIGNERS}\" -s gpgssh_prereq.sig &&\n+\techo \"testpayload\" |\n+\tssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with number 1\" -s gpgssh_prereq.sig\n '\n \n test_lazy_prereq GPGSSH_VERIFYTIME '\n \t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n \tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\n+\t# Set up keys with key lifetimes\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_valid=$(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_invalid=$(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n-\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_expired=$(cat \"${GPGSSH_KEY_EXPIRED}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n-\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n+\tkey_notyetvalid=$(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\") &&\n+\n+\t# Timestamps outside of test_tick span\n+\tts2005a=20050401000000 ts2005b=200504020000 &&\n+\t# Timestamps within test_tick span\n+\tts2005c=20050407000000 ts2005d=200504100000 &&\n+\t# Definitely not yet valid / expired timestamps\n+\tts2000=20000101000000 ts2999=29990101000000 &&\n+\n+\tcat >>\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"timeboxed valid key\" valid-after=\"$ts2005c\",valid-before=\"$ts2005d\" $key_valid\"\n+\t\"timeboxed invalid key\" valid-after=\"$ts2005a\",valid-before=\"$ts2005b\" $key_invalid\"\n+\t\"principal with expired key\" valid-before=\"$ts2000\" $key_expired\"\n+\t\"principal with not yet valid key\" valid-after=\"$ts2999\" $key_notyetvalid\"\n+\tEOF\n+\n+\t# and verify ssh-keygen verifies the key lifetime\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" >gpgssh_verifytime_prereq.sig &&\n+\t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"443478","messageId":"20211208163335.1231795-9-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v5 8/8] t/fmt-merge-msg: make gpg/ssh tests more specific","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-08T16:33:35Z","receivedAt":"2021-12-08T16:34:10Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"All the GPG, GPGSSH and the new GPGSSH_VERIFYTIME tests are redirecing\nstdout as well as stderr to `actual` and grep for success/failure over\nthe resulting flie. However, no output is printed on stderr and we do\nnot need to include it in the grep. The newer SSH signing based tests\nare also missing a grep for the merged tag to make sure the merge\nmessage is correct.\n\n- remove unneccessary 2>&1 redirects\n- add grep for merged tag to gpgssh* tests\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t6200-fmt-merge-msg.sh | 21 +++++++++++++--------\n 1 file changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 2dd2423643..12a1e62bf0 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -124,7 +124,7 @@ test_expect_success 'message for merging local branch' '\n test_expect_success GPG 'message for merging local tag signed by good key' '\n \tgit checkout main &&\n \tgit fetch . signed-good-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n \tgrep \"^# gpg: Signature made\" actual &&\n \tgrep \"^# gpg: Good signature from\" actual\n@@ -133,7 +133,7 @@ test_expect_success GPG 'message for merging local tag signed by good key' '\n test_expect_success GPG 'message for merging local tag signed by unknown key' '\n \tgit checkout main &&\n \tgit fetch . signed-good-tag &&\n-\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n \tgrep \"^# gpg: Signature made\" actual &&\n \tgrep -E \"^# gpg: Can${apos}t check signature: (public key not found|No public key)\" actual\n@@ -143,7 +143,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by good ssh key\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . signed-good-ssh-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n '\n@@ -152,7 +152,8 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . signed-untrusted-ssh-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}signed-untrusted-ssh-tag${apos}\" actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n@@ -162,7 +163,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . expired-signed &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}expired-signed${apos}\" actual &&\n \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n@@ -170,7 +172,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . notyetvalid-signed &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}notyetvalid-signed${apos}\" actual &&\n \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n@@ -178,7 +181,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . timeboxedvalid-signed &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}timeboxedvalid-signed${apos}\" actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n '\n@@ -187,7 +191,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . timeboxedinvalid-signed &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}timeboxedinvalid-signed${apos}\" actual &&\n \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n-- \n2.31.1\n\n"},{"id":"443537","messageId":"xmqqsfv2wuo1.fsf@gitster.g","threadId":"56793","inReplyTo":"20211208163335.1231795-9-fs@gigacodes.de","subject":"Re: [PATCH v5 8/8] t/fmt-merge-msg: make gpg/ssh tests more specific","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-08T23:20:30Z","receivedAt":"2021-12-08T23:20:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fabian Stelzer <fs@gigacodes.de> writes:\n\n> All the GPG, GPGSSH and the new GPGSSH_VERIFYTIME tests are redirecing\n> stdout as well as stderr to `actual` and grep for success/failure over\n> the resulting flie. However, no output is printed on stderr and we do\n> not need to include it in the grep. The newer SSH signing based tests\n> are also missing a grep for the merged tag to make sure the merge\n> message is correct.\n>\n> - remove unneccessary 2>&1 redirects\n> - add grep for merged tag to gpgssh* tests\n>\n> Signed-off-by: Fabian Stelzer <fs@gigacodes.de>\n> ---\n>  t/t6200-fmt-merge-msg.sh | 21 +++++++++++++--------\n>  1 file changed, 13 insertions(+), 8 deletions(-)\n\nThe 4 hunks at the beginning seems to be a clean-up of the existing\nissue, but aren't hunks -162,7, -170,7, -178,7, 187,7 \"oops, we\nscrewed up in [6/8], and we patch up after the fact\"?  \n\nFor a new topic not yet in 'next', we'd prefer to pretend to be more\nperfect humans by not deliberatly keeping mistakes made in an\nearlier step, only to be corrected in a later step.\n\nWhile there may not be any difference in the end-result, it would be\ncleaner and less sloppy to have the 4 hunks to fix the ones before\nthe series as a preliminary clean-up step, and the other hunks\nfolded into the step that introduced the problem, no?\n\nThanks.\n\n> diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\n> index 2dd2423643..12a1e62bf0 100755\n> --- a/t/t6200-fmt-merge-msg.sh\n> +++ b/t/t6200-fmt-merge-msg.sh\n> @@ -124,7 +124,7 @@ test_expect_success 'message for merging local branch' '\n>  test_expect_success GPG 'message for merging local tag signed by good key' '\n>  \tgit checkout main &&\n>  \tgit fetch . signed-good-tag &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n>  \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n>  \tgrep \"^# gpg: Signature made\" actual &&\n>  \tgrep \"^# gpg: Good signature from\" actual\n> @@ -133,7 +133,7 @@ test_expect_success GPG 'message for merging local tag signed by good key' '\n>  test_expect_success GPG 'message for merging local tag signed by unknown key' '\n>  \tgit checkout main &&\n>  \tgit fetch . signed-good-tag &&\n> -\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual &&\n>  \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n>  \tgrep \"^# gpg: Signature made\" actual &&\n>  \tgrep -E \"^# gpg: Can${apos}t check signature: (public key not found|No public key)\" actual\n> @@ -143,7 +143,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by good ssh key\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . signed-good-ssh-tag &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n>  \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n>  \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n>  '\n> @@ -152,7 +152,8 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . signed-untrusted-ssh-tag &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n> +\tgrep \"^Merge tag ${apos}signed-untrusted-ssh-tag${apos}\" actual &&\n>  \tgrep \"${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}\" actual &&\n>  \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n>  \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n> @@ -162,7 +163,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . expired-signed &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n> +\tgrep \"^Merge tag ${apos}expired-signed${apos}\" actual &&\n>  \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n>  '\n>  \n> @@ -170,7 +172,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . notyetvalid-signed &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n> +\tgrep \"^Merge tag ${apos}notyetvalid-signed${apos}\" actual &&\n>  \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n>  '\n>  \n> @@ -178,7 +181,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . timeboxedvalid-signed &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n> +\tgrep \"^Merge tag ${apos}timeboxedvalid-signed${apos}\" actual &&\n>  \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n>  \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n>  '\n> @@ -187,7 +191,8 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag sign\n>  \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n>  \tgit checkout main &&\n>  \tgit fetch . timeboxedinvalid-signed &&\n> -\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n> +\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n> +\tgrep \"^Merge tag ${apos}timeboxedinvalid-signed${apos}\" actual &&\n>  \t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n>  '\n"},{"id":"443613","messageId":"20211209083615.nozxvqt5rvblyz34@fs","threadId":"56793","inReplyTo":"xmqqsfv2wuo1.fsf@gitster.g","subject":"Re: [PATCH v5 8/8] t/fmt-merge-msg: make gpg/ssh tests more specific","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:36:15Z","receivedAt":"2021-12-09T08:36:20Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"On 08.12.2021 15:20, Junio C Hamano wrote:\n>Fabian Stelzer <fs@gigacodes.de> writes:\n>\n>> All the GPG, GPGSSH and the new GPGSSH_VERIFYTIME tests are redirecing\n>> stdout as well as stderr to `actual` and grep for success/failure over\n>> the resulting flie. However, no output is printed on stderr and we do\n>> not need to include it in the grep. The newer SSH signing based tests\n>> are also missing a grep for the merged tag to make sure the merge\n>> message is correct.\n>>\n>> - remove unneccessary 2>&1 redirects\n>> - add grep for merged tag to gpgssh* tests\n>>\n>> Signed-off-by: Fabian Stelzer <fs@gigacodes.de>\n>> ---\n>>  t/t6200-fmt-merge-msg.sh | 21 +++++++++++++--------\n>>  1 file changed, 13 insertions(+), 8 deletions(-)\n>\n>The 4 hunks at the beginning seems to be a clean-up of the existing\n>issue, but aren't hunks -162,7, -170,7, -178,7, 187,7 \"oops, we\n>screwed up in [6/8], and we patch up after the fact\"?\n>\n>For a new topic not yet in 'next', we'd prefer to pretend to be more\n>perfect humans by not deliberatly keeping mistakes made in an\n>earlier step, only to be corrected in a later step.\n>\n>While there may not be any difference in the end-result, it would be\n>cleaner and less sloppy to have the 4 hunks to fix the ones before\n>the series as a preliminary clean-up step, and the other hunks\n>folded into the step that introduced the problem, no?\n>\n\nOk, i will send a new version with the fixes for the earlier tests in extra \ncommits.  Your 5.1 includes a +\"grep \"^Merge tag \n${apos}signed-untrusted-ssh-tag${apos}\" actual &&\" fix for a preexisting \ntest in the first patch which now claims to only remove the stderr redirects \nthough.  Also another grep for the merged tag for an earlier test got lost \nsomewhere. I'll send a 5.2\n\nThanks\n\n"},{"id":"443614","messageId":"20211209085249.13587-1-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211208163335.1231795-1-fs@gigacodes.de","subject":"[PATCH v6 0/9] ssh signing: verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:40Z","receivedAt":"2021-12-09T08:52:56Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"changes since v5:\n - moved the fixes to existing test to the first two commits and merged\n   those fixing new tests into the corresponding commit\n\nchanges since v4:\n - removed unneccessary io redir in merge-msg tests\n - added grep for merged tag to gpgssh merge-msg tests\n\nchanges since v3:\n - improve readability of prereq setup code by using heredoc and some\n   variables\n\nchanges since v2:\n - fix swich/case indentation\n - BUG() on unknown payload types\n - improve test prereq by actually validating ssh-keygen functionality\n\nchanges since v1:\n - struct signature_check is now used to input payload data into\n   check_function\n - payload metadata parsing is completely internal to check_signature.\n   the caller only need to set the payload type in the sigc struct\n - small nits and readability fixes\n - removed payload_signer parameter. since we now use the struct we can\n   extend\n   this later.\n\nFabian Stelzer (9):\n  t/fmt-merge-msg: do not redirect stderr\n  t/fmt-merge-msg: make gpgssh tests more specific\n  ssh signing: use sigc struct to pass payload\n  ssh signing: add key lifetime test prereqs\n  ssh signing: make verify-commit consider key lifetime\n  ssh signing: make git log verify key lifetime\n  ssh signing: make verify-tag consider key lifetime\n  ssh signing: make fmt-merge-msg consider key lifetime\n  ssh signing: verify ssh-keygen in test prereq\n\n Documentation/config/gpg.txt     |  5 ++\n builtin/receive-pack.c           |  6 ++-\n commit.c                         |  6 ++-\n fmt-merge-msg.c                  |  5 +-\n gpg-interface.c                  | 90 +++++++++++++++++++++++++-------\n gpg-interface.h                  | 15 ++++--\n log-tree.c                       | 10 ++--\n t/lib-gpg.sh                     | 62 ++++++++++++++++++----\n t/t4202-log.sh                   | 43 +++++++++++++++\n t/t6200-fmt-merge-msg.sh         | 68 ++++++++++++++++++++++--\n t/t7031-verify-tag-signed-ssh.sh | 42 +++++++++++++++\n t/t7528-signed-commit-ssh.sh     | 42 +++++++++++++++\n tag.c                            |  5 +-\n 13 files changed, 351 insertions(+), 48 deletions(-)\n\nRange-diff against v5:\n -:  ---------- >  1:  0b3848d23b t/fmt-merge-msg: do not redirect stderr\n -:  ---------- >  2:  f29d838574 t/fmt-merge-msg: make gpgssh tests more specific\n 1:  c4447d30f2 =  3:  b065dcb7fb ssh signing: use sigc struct to pass payload\n 2:  0bb1617529 =  4:  c37d33db31 ssh signing: add key lifetime test prereqs\n 3:  f60bd1efd0 =  5:  640e9a4a99 ssh signing: make verify-commit consider key lifetime\n 4:  5fc0ad5c37 =  6:  2e98307c18 ssh signing: make git log verify key lifetime\n 5:  f1c225871f =  7:  75d213ab15 ssh signing: make verify-tag consider key lifetime\n 6:  1cbd4dbb6b !  8:  498821af14 ssh signing: make fmt-merge-msg consider key lifetime\n    @@ t/t6200-fmt-merge-msg.sh: test_expect_success GPGSSH 'message for merging local\n     +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n     +\tgit checkout main &&\n     +\tgit fetch . expired-signed &&\n    -+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n    ++\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n    ++\tgrep \"^Merge tag ${apos}expired-signed${apos}\" actual &&\n     +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n     +'\n     +\n    @@ t/t6200-fmt-merge-msg.sh: test_expect_success GPGSSH 'message for merging local\n     +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n     +\tgit checkout main &&\n     +\tgit fetch . notyetvalid-signed &&\n    -+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n    ++\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n    ++\tgrep \"^Merge tag ${apos}notyetvalid-signed${apos}\" actual &&\n     +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n     +'\n     +\n    @@ t/t6200-fmt-merge-msg.sh: test_expect_success GPGSSH 'message for merging local\n     +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n     +\tgit checkout main &&\n     +\tgit fetch . timeboxedvalid-signed &&\n    -+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n    ++\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n    ++\tgrep \"^Merge tag ${apos}timeboxedvalid-signed${apos}\" actual &&\n     +\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n     +\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n     +'\n    @@ t/t6200-fmt-merge-msg.sh: test_expect_success GPGSSH 'message for merging local\n     +\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n     +\tgit checkout main &&\n     +\tgit fetch . timeboxedinvalid-signed &&\n    -+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n    ++\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n    ++\tgrep \"^Merge tag ${apos}timeboxedinvalid-signed${apos}\" actual &&\n     +\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n     +'\n     +\n 7:  d60f4ec82c =  9:  0816dd2ec8 ssh signing: verify ssh-keygen in test prereq\n-- \n2.31.1\n\n"},{"id":"443615","messageId":"20211209085249.13587-3-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 2/9] t/fmt-merge-msg: make gpgssh tests more specific","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:42Z","receivedAt":"2021-12-09T08:52:57Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Some GPGSSH fmt-merge-msg tests were only grepping for failed/successful\nsignature validation and not checking for the tag in the resulting merge\nmessage. Add the missing grep for it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t6200-fmt-merge-msg.sh | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex cc68bba064..eb2a9a4a78 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -124,6 +124,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by good ssh key\n \tgit checkout main &&\n \tgit fetch . signed-good-ssh-tag &&\n \tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}signed-good-ssh-tag${apos}\" actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n '\n@@ -133,6 +134,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \tgit checkout main &&\n \tgit fetch . signed-untrusted-ssh-tag &&\n \tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}signed-untrusted-ssh-tag${apos}\" actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n-- \n2.31.1\n\n"},{"id":"443616","messageId":"20211209085249.13587-2-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 1/9] t/fmt-merge-msg: do not redirect stderr","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:41Z","receivedAt":"2021-12-09T08:52:59Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"All the GPG and GPGSSH tests are redirecing stdout as well as stderr\nto `actual` and grep for success/failure over the resulting file.\nHowever, no output is printed on stderr and we do not need to\ninclude it in the grep.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t6200-fmt-merge-msg.sh | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex 06c5fb5615..cc68bba064 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -104,7 +104,7 @@ test_expect_success 'message for merging local branch' '\n test_expect_success GPG 'message for merging local tag signed by good key' '\n \tgit checkout main &&\n \tgit fetch . signed-good-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n \tgrep \"^# gpg: Signature made\" actual &&\n \tgrep \"^# gpg: Good signature from\" actual\n@@ -113,7 +113,7 @@ test_expect_success GPG 'message for merging local tag signed by good key' '\n test_expect_success GPG 'message for merging local tag signed by unknown key' '\n \tgit checkout main &&\n \tgit fetch . signed-good-tag &&\n-\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tGNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"^Merge tag ${apos}signed-good-tag${apos}\" actual &&\n \tgrep \"^# gpg: Signature made\" actual &&\n \tgrep -E \"^# gpg: Can${apos}t check signature: (public key not found|No public key)\" actual\n@@ -123,7 +123,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by good ssh key\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . signed-good-ssh-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n '\n@@ -132,7 +132,7 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit checkout main &&\n \tgit fetch . signed-untrusted-ssh-tag &&\n-\tgit fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n \tgrep \"${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}\" actual &&\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n-- \n2.31.1\n\n"},{"id":"443617","messageId":"20211209085249.13587-4-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 3/9] ssh signing: use sigc struct to pass payload","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:43Z","receivedAt":"2021-12-09T08:53:00Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"To be able to extend the payload metadata with things like its creation\ntimestamp or the creators ident we remove the payload parameters to\ncheck_signature() and use the already existing sigc->payload field\ninstead, only adding the length field to the struct. This also allows\nus to get rid of the xmemdupz() calls in the verify functions. Since\nsigc is now used to input data as well as output the result move it to\nthe front of the function list.\n\n - Add payload_length to struct signature_check\n - Populate sigc.payload/payload_len on all call sites\n - Remove payload parameters to check_signature()\n - Remove payload parameters to internal verify_* functions and use sigc\n   instead\n - Remove xmemdupz() used for verbose output since payload is now already\n   populated.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n builtin/receive-pack.c |  6 ++++--\n commit.c               |  5 +++--\n fmt-merge-msg.c        |  4 ++--\n gpg-interface.c        | 37 +++++++++++++++++--------------------\n gpg-interface.h        |  6 +++---\n log-tree.c             |  8 ++++----\n tag.c                  |  4 ++--\n 7 files changed, 35 insertions(+), 35 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d960..61ab63c2ea 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -769,8 +769,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n \n \t\tbogs = parse_signed_buffer(push_cert.buf, push_cert.len);\n-\t\tcheck_signature(push_cert.buf, bogs, push_cert.buf + bogs,\n-\t\t\t\tpush_cert.len - bogs, &sigcheck);\n+\t\tsigcheck.payload = xmemdupz(push_cert.buf, bogs);\n+\t\tsigcheck.payload_len = bogs;\n+\t\tcheck_signature(&sigcheck, push_cert.buf + bogs,\n+\t\t\t\tpush_cert.len - bogs);\n \n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\ndiff --git a/commit.c b/commit.c\nindex 551de4903c..64e040a99b 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1212,8 +1212,9 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\tsignature.len, sigc);\n+\n+\tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n+\tret = check_signature(sigc, signature.buf, signature.len);\n \n  out:\n \tstrbuf_release(&payload);\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5216191488..deca1ea3a3 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,8 +533,8 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n-\t\t\tif (check_signature(payload.buf, payload.len, sig.buf,\n-\t\t\t\t\t    sig.len, &sigc) &&\n+\t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\n \t\t\t\tstrbuf_addstr(&sig, \"gpg verification failed.\\n\");\n \t\t\telse\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3e7255a2a9..75ab6faacb 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -19,8 +19,8 @@ struct gpg_format {\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n \tint (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {\n };\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size);\n static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,\n \t\t\t   const char *signing_key);\n@@ -314,8 +314,8 @@ static void parse_gpg_output(struct signature_check *sigc)\n }\n \n static int verify_gpg_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process gpg = CHILD_PROCESS_INIT;\n@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,\n \t\t     NULL);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n-\tret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,\n+\tret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,\n \t\t\t   &gpg_stderr, 0);\n \tsigchain_pop(SIGPIPE);\n \n \tdelete_tempfile(&temp);\n \n \tret |= !strstr(gpg_stdout.buf, \"\\n[GNUPG:] GOODSIG \");\n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tsigc->output = strbuf_detach(&gpg_stderr, NULL);\n \tsigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);\n \n@@ -426,8 +425,8 @@ static void parse_ssh_output(struct signature_check *sigc)\n }\n \n static int verify_ssh_signed_buffer(struct signature_check *sigc,\n-\t\t\t\t    struct gpg_format *fmt, const char *payload,\n-\t\t\t\t    size_t payload_size, const char *signature,\n+\t\t\t\t    struct gpg_format *fmt,\n+\t\t\t\t    const char *signature,\n \t\t\t\t    size_t signature_size)\n {\n \tstruct child_process ssh_keygen = CHILD_PROCESS_INIT;\n@@ -480,7 +479,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n \t\t\t     NULL);\n-\t\tpipe_command(&ssh_keygen, payload, payload_size,\n+\t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \n \t\t/*\n@@ -526,7 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t}\n \n \t\t\tsigchain_push(SIGPIPE, SIG_IGN);\n-\t\t\tret = pipe_command(&ssh_keygen, payload, payload_size,\n+\t\t\tret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n \t\t\tsigchain_pop(SIGPIPE);\n \n@@ -540,7 +539,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t}\n \t}\n \n-\tsigc->payload = xmemdupz(payload, payload_size);\n \tstrbuf_stripspace(&ssh_keygen_out, 0);\n \tstrbuf_stripspace(&ssh_keygen_err, 0);\n \t/* Add stderr outputs to show the user actual ssh-keygen errors */\n@@ -562,8 +560,8 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \treturn ret;\n }\n \n-int check_signature(const char *payload, size_t plen, const char *signature,\n-\tsize_t slen, struct signature_check *sigc)\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen)\n {\n \tstruct gpg_format *fmt;\n \tint status;\n@@ -575,8 +573,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n-\tstatus = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,\n-\t\t\t\t\t   slen);\n+\tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\n \t\treturn !!status;\n@@ -593,7 +590,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)\n \t\t\t\t\t\t\t    sigc->output;\n \n \tif (flags & GPG_VERIFY_VERBOSE && sigc->payload)\n-\t\tfputs(sigc->payload, stdout);\n+\t\tfwrite(sigc->payload, 1, sigc->payload_len, stdout);\n \n \tif (output)\n \t\tfputs(output, stderr);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex beefacbb1e..5ee7d8b6b9 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -17,6 +17,7 @@ enum signature_trust_level {\n \n struct signature_check {\n \tchar *payload;\n+\tsize_t payload_len;\n \tchar *output;\n \tchar *gpg_status;\n \n@@ -70,9 +71,8 @@ const char *get_signing_key(void);\n  * Either a GPG KeyID or a SSH Key Fingerprint\n  */\n const char *get_signing_key_id(void);\n-int check_signature(const char *payload, size_t plen,\n-\t\t    const char *signature, size_t slen,\n-\t\t    struct signature_check *sigc);\n+int check_signature(struct signature_check *sigc,\n+\t\t    const char *signature, size_t slen);\n void print_signature_buffer(const struct signature_check *sigc,\n \t\t\t    unsigned flags);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 644893fd8c..a46cf60e1e 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,8 +513,8 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n-\tstatus = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\t signature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n \t\tshow_sig_lines(opt, status, \"No signature\\n\");\n \telse\n@@ -583,8 +583,8 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n-\t\tstatus = check_signature(payload.buf, payload.len,\n-\t\t\t\t\t signature.buf, signature.len, &sigc);\n+\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\n \t\t\tstrbuf_addstr(&verify_message, sigc.output);\n \t\telse\ndiff --git a/tag.c b/tag.c\nindex 3e18a41841..62fb09f5a5 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,8 +25,8 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n-\tret = check_signature(payload.buf, payload.len, signature.buf,\n-\t\t\t\tsignature.len, &sigc);\n+\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n+\tret = check_signature(&sigc, signature.buf, signature.len);\n \n \tif (!(flags & GPG_VERIFY_OMIT_STATUS))\n \t\tprint_signature_buffer(&sigc, flags);\n-- \n2.31.1\n\n"},{"id":"443618","messageId":"20211209085249.13587-5-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 4/9] ssh signing: add key lifetime test prereqs","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:44Z","receivedAt":"2021-12-09T08:53:08Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"if ssh-keygen supports -Overify-time, add test keys marked as expired,\nnot yet valid and valid both within the test_tick timeframe and outside of it.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex a3f285f515..fc03c8f89b 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -90,6 +90,10 @@ test_lazy_prereq RFC1991 '\n GPGSSH_KEY_PRIMARY=\"${GNUPGHOME}/ed25519_ssh_signing_key\"\n GPGSSH_KEY_SECONDARY=\"${GNUPGHOME}/rsa_2048_ssh_signing_key\"\n GPGSSH_KEY_UNTRUSTED=\"${GNUPGHOME}/untrusted_ssh_signing_key\"\n+GPGSSH_KEY_EXPIRED=\"${GNUPGHOME}/expired_ssh_signing_key\"\n+GPGSSH_KEY_NOTYETVALID=\"${GNUPGHOME}/notyetvalid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDVALID=\"${GNUPGHOME}/timeboxed_valid_ssh_signing_key\"\n+GPGSSH_KEY_TIMEBOXEDINVALID=\"${GNUPGHOME}/timeboxed_invalid_ssh_signing_key\"\n GPGSSH_KEY_WITH_PASSPHRASE=\"${GNUPGHOME}/protected_ssh_signing_key\"\n GPGSSH_KEY_PASSPHRASE=\"super_secret\"\n GPGSSH_ALLOWED_SIGNERS=\"${GNUPGHOME}/ssh.all_valid.allowedSignersFile\"\n@@ -119,7 +123,20 @@ test_lazy_prereq GPGSSH '\n \techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n \techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+'\n+\n+test_lazy_prereq GPGSSH_VERIFYTIME '\n+\t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n+\tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n+\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n+\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n+\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"},{"id":"443619","messageId":"20211209085249.13587-6-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 5/9] ssh signing: make verify-commit consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:45Z","receivedAt":"2021-12-09T08:53:09Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"If valid-before/after dates are configured for this signatures key in the\nallowedSigners file then the verification should check if the key was valid at\nthe time the commit was made. This allows for graceful key rollover and\nrevoking keys without invalidating all previous commits.\nThis feature needs openssh > 8.8. Older ssh-keygen versions will simply\nignore this flag and use the current time.\nStrictly speaking this feature is available in 8.7, but since 8.7 has a\nbug that makes it unusable in another needed call we require 8.8.\n\nTimestamp information is present on most invocations of check_signature.\nHowever signer ident is not. We will need the signer email / name to be able\nto implement \"Trust on first use\" functionality later.\nSince the payload contains all necessary information we can parse it\nfrom there. The caller only needs to provide us some info about the\npayload by setting payload_type in the signature_check struct.\n\n - Add payload_type field & enum and payload_timestamp to struct\n   signature_check\n - Populate the timestamp when not already set if we know about the\n   payload type\n - Pass -Overify-time={payload_timestamp} in the users timezone to all\n   ssh-keygen verification calls\n - Set the payload type when verifying commits\n - Add tests for expired, not yet valid and keys having a commit date\n   outside of key validity as well as within\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n Documentation/config/gpg.txt |  5 ++++\n commit.c                     |  1 +\n gpg-interface.c              | 53 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h              |  9 ++++++\n t/t7528-signed-commit-ssh.sh | 42 ++++++++++++++++++++++++++++\n 5 files changed, 110 insertions(+)\n\ndiff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt\nindex 4f30c7dbdd..c9be554c73 100644\n--- a/Documentation/config/gpg.txt\n+++ b/Documentation/config/gpg.txt\n@@ -64,6 +64,11 @@ A repository that only allows signed commits can store the file\n in the repository itself using a path relative to the top-level of the working tree.\n This way only committers with an already valid key can add or change keys in the keyring.\n +\n+Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &\n+valid-before options. Git will mark signatures as valid if the signing key was\n+valid at the time of the signatures creation. This allows users to change a\n+signing key without invalidating all previously made signatures.\n++\n Using a SSH CA key with the cert-authority option\n (see ssh-keygen(1) \"CERTIFICATES\") is also valid.\n \ndiff --git a/commit.c b/commit.c\nindex 64e040a99b..a348f085b2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1213,6 +1213,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check *\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc->payload = strbuf_detach(&payload, &sigc->payload_len);\n \tret = check_signature(sigc, signature.buf, signature.len);\n \ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 75ab6faacb..330cfc5845 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -439,6 +439,13 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstruct strbuf ssh_principals_err = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_out = STRBUF_INIT;\n \tstruct strbuf ssh_keygen_err = STRBUF_INIT;\n+\tstruct strbuf verify_time = STRBUF_INIT;\n+\tconst struct date_mode verify_date_mode = {\n+\t\t.type = DATE_STRFTIME,\n+\t\t.strftime_fmt = \"%Y%m%d%H%M%S\",\n+\t\t/* SSH signing key validity has no timezone information - Use the local timezone */\n+\t\t.local = 1,\n+\t};\n \n \tif (!ssh_allowed_signers) {\n \t\terror(_(\"gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification\"));\n@@ -456,11 +463,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\treturn -1;\n \t}\n \n+\tif (sigc->payload_timestamp)\n+\t\tstrbuf_addf(&verify_time, \"-Overify-time=%s\",\n+\t\t\tshow_date(sigc->payload_timestamp, 0, &verify_date_mode));\n+\n \t/* Find the principal from the signers */\n \tstrvec_pushl(&ssh_keygen.args, fmt->program,\n \t\t     \"-Y\", \"find-principals\",\n \t\t     \"-f\", ssh_allowed_signers,\n \t\t     \"-s\", buffer_file->filename.buf,\n+\t\t     verify_time.buf,\n \t\t     NULL);\n \tret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,\n \t\t\t   &ssh_principals_err, 0);\n@@ -478,6 +490,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t     \"-Y\", \"check-novalidate\",\n \t\t\t     \"-n\", \"git\",\n \t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t     verify_time.buf,\n \t\t\t     NULL);\n \t\tpipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,\n \t\t\t\t   &ssh_keygen_out, 0, &ssh_keygen_err, 0);\n@@ -512,6 +525,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \t\t\t\t     \"-f\", ssh_allowed_signers,\n \t\t\t\t     \"-I\", principal,\n \t\t\t\t     \"-s\", buffer_file->filename.buf,\n+\t\t\t\t     verify_time.buf,\n \t\t\t\t     NULL);\n \n \t\t\tif (ssh_revocation_file) {\n@@ -556,10 +570,46 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,\n \tstrbuf_release(&ssh_principals_err);\n \tstrbuf_release(&ssh_keygen_out);\n \tstrbuf_release(&ssh_keygen_err);\n+\tstrbuf_release(&verify_time);\n \n \treturn ret;\n }\n \n+static int parse_payload_metadata(struct signature_check *sigc)\n+{\n+\tconst char *ident_line = NULL;\n+\tsize_t ident_len;\n+\tstruct ident_split ident;\n+\tconst char *signer_header;\n+\n+\tswitch (sigc->payload_type) {\n+\tcase SIGNATURE_PAYLOAD_COMMIT:\n+\t\tsigner_header = \"committer\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_TAG:\n+\t\tsigner_header = \"tagger\";\n+\t\tbreak;\n+\tcase SIGNATURE_PAYLOAD_UNDEFINED:\n+\tcase SIGNATURE_PAYLOAD_PUSH_CERT:\n+\t\t/* Ignore payloads we don't want to parse */\n+\t\treturn 0;\n+\tdefault:\n+\t\tBUG(\"invalid value for sigc->payload_type\");\n+\t}\n+\n+\tident_line = find_commit_header(sigc->payload, signer_header, &ident_len);\n+\tif (!ident_line || !ident_len)\n+\t\treturn 1;\n+\n+\tif (split_ident_line(&ident, ident_line, ident_len))\n+\t\treturn 1;\n+\n+\tif (!sigc->payload_timestamp && ident.date_begin && ident.date_end)\n+\t\tsigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);\n+\n+\treturn 0;\n+}\n+\n int check_signature(struct signature_check *sigc,\n \t\t    const char *signature, size_t slen)\n {\n@@ -573,6 +623,9 @@ int check_signature(struct signature_check *sigc,\n \tif (!fmt)\n \t\tdie(_(\"bad/incompatible signature '%s'\"), signature);\n \n+\tif (parse_payload_metadata(sigc))\n+\t\treturn 1;\n+\n \tstatus = fmt->verify_signed_buffer(sigc, fmt, signature, slen);\n \n \tif (status && !sigc->output)\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 5ee7d8b6b9..b30cbdcd3d 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -15,9 +15,18 @@ enum signature_trust_level {\n \tTRUST_ULTIMATE,\n };\n \n+enum payload_type {\n+\tSIGNATURE_PAYLOAD_UNDEFINED,\n+\tSIGNATURE_PAYLOAD_COMMIT,\n+\tSIGNATURE_PAYLOAD_TAG,\n+\tSIGNATURE_PAYLOAD_PUSH_CERT,\n+};\n+\n struct signature_check {\n \tchar *payload;\n \tsize_t payload_len;\n+\tenum payload_type payload_type;\n+\ttimestamp_t payload_timestamp;\n \tchar *output;\n \tchar *gpg_status;\n \ndiff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh\nindex badf3ed320..dae76ded0c 100755\n--- a/t/t7528-signed-commit-ssh.sh\n+++ b/t/t7528-signed-commit-ssh.sh\n@@ -76,6 +76,23 @@ test_expect_success GPGSSH 'create signed commits' '\n \tgit tag twelfth-signed-alt $(cat oid)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.mintrustlevel UNDEFINED &&\n@@ -122,6 +139,31 @@ test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature'\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit succeeds with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-commit timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-commit exits failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-commit timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \ttest_config gpg.minTrustLevel fully &&\n-- \n2.31.1\n\n"},{"id":"443620","messageId":"20211209085249.13587-7-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 6/9] ssh signing: make git log verify key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:46Z","receivedAt":"2021-12-09T08:53:11Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling git log.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n log-tree.c     |  2 ++\n t/t4202-log.sh | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex a46cf60e1e..d3e7a40b64 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -513,6 +513,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit)\n \tif (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)\n \t\tgoto out;\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_COMMIT;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tstatus = check_signature(&sigc, signature.buf, signature.len);\n \tif (status && !sigc.output)\n@@ -583,6 +584,7 @@ static int show_one_mergetag(struct commit *commit,\n \tstatus = -1;\n \tif (parse_signature(extra->value, extra->len, &payload, &signature)) {\n \t\t/* could have a good signature */\n+\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\tstatus = check_signature(&sigc, signature.buf, signature.len);\n \t\tif (sigc.output)\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 7884e3d46b..ba855ec893 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -1677,6 +1677,24 @@ test_expect_success GPGSSH 'setup sshkey signed branch' '\n \tgit commit -S -m signed_commit\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed commits with keys having defined lifetimes' '\n+\ttest_config gpg.format ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSM 'log x509 fingerprint' '\n \techo \"F8BF62E0693D0694816377099909C779FA23FD65 | \" >expect &&\n \tgit log -n1 --format=\"%GF | %GP\" signed-x509 >actual &&\n@@ -1714,6 +1732,31 @@ test_expect_success GPGSSH 'log --graph --show-signature ssh' '\n \tgrep \"${GOOD_SIGNATURE_TRUSTED}\" actual\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 expired-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 notyetvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log show success with commit date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedvalid-signed >actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure with commit date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit log --graph --show-signature -n1 timeboxedinvalid-signed >actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPG 'log --graph --show-signature for merged tag' '\n \ttest_when_finished \"git reset --hard && git checkout main\" &&\n \tgit checkout -b plain main &&\n-- \n2.31.1\n\n"},{"id":"443621","messageId":"20211209085249.13587-8-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 7/9] ssh signing: make verify-tag consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:47Z","receivedAt":"2021-12-09T08:53:12Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when calling verify-tag.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/t7031-verify-tag-signed-ssh.sh | 42 ++++++++++++++++++++++++++++++++\n tag.c                            |  1 +\n 2 files changed, 43 insertions(+)\n\ndiff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh\nindex 06c9dd6c93..1cb36b9ab8 100755\n--- a/t/t7031-verify-tag-signed-ssh.sh\n+++ b/t/t7031-verify-tag-signed-ssh.sh\n@@ -48,6 +48,23 @@ test_expect_success GPGSSH 'create signed tags ssh' '\n \tgit tag -u\"${GPGSSH_KEY_UNTRUSTED}\" -m eighth eighth-signed-alt\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success GPGSSH 'verify and show ssh signatures' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \t(\n@@ -80,6 +97,31 @@ test_expect_success GPGSSH 'verify and show ssh signatures' '\n \t)\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on expired signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag expired-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag exits failure on not yet valid signature key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag notyetvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag succeeds with tag date and key validity matching' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit verify-tag timeboxedvalid-signed 2>actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date outside of key validity' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\ttest_must_fail git verify-tag timeboxedinvalid-signed 2>actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success GPGSSH 'detect fudged ssh signature' '\n \ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tgit cat-file tag seventh-signed >raw &&\ndiff --git a/tag.c b/tag.c\nindex 62fb09f5a5..dfbcd7fcc2 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -25,6 +25,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)\n \t\treturn error(\"no signature found\");\n \t}\n \n+\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \tret = check_signature(&sigc, signature.buf, signature.len);\n \n-- \n2.31.1\n\n"},{"id":"443622","messageId":"20211209085249.13587-9-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 8/9] ssh signing: make fmt-merge-msg consider key lifetime","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:48Z","receivedAt":"2021-12-09T08:53:13Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Set the payload_type for check_signature() when generating merge messages to\nverify merged tags signatures key lifetimes.\nImplements the same tests as for verify-commit.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n fmt-merge-msg.c          |  1 +\n t/t6200-fmt-merge-msg.sh | 58 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 59 insertions(+)\n\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex deca1ea3a3..e4f7810be2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -533,6 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)\n \t\telse {\n \t\t\tbuf = payload.buf;\n \t\t\tlen = payload.len;\n+\t\t\tsigc.payload_type = SIGNATURE_PAYLOAD_TAG;\n \t\t\tsigc.payload = strbuf_detach(&payload, &sigc.payload_len);\n \t\t\tif (check_signature(&sigc, sig.buf, sig.len) &&\n \t\t\t    !sigc.output)\ndiff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh\nindex eb2a9a4a78..6e10a539ce 100755\n--- a/t/t6200-fmt-merge-msg.sh\n+++ b/t/t6200-fmt-merge-msg.sh\n@@ -91,6 +91,26 @@ test_expect_success GPGSSH 'created ssh signed commit and tag' '\n \tgit tag -s -u\"${GPGSSH_KEY_UNTRUSTED}\" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left\n '\n \n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'create signed tags with keys having defined lifetimes' '\n+\ttest_when_finished \"test_unconfig commit.gpgsign\" &&\n+\ttest_config gpg.format ssh &&\n+\tgit checkout -b signed-expiry-ssh &&\n+\ttouch file &&\n+\tgit add file &&\n+\n+\techo expired >file && test_tick && git commit -a -m expired -S\"${GPGSSH_KEY_EXPIRED}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_EXPIRED}\" -m expired-signed expired-signed &&\n+\n+\techo notyetvalid >file && test_tick && git commit -a -m notyetvalid -S\"${GPGSSH_KEY_NOTYETVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_NOTYETVALID}\" -m notyetvalid-signed notyetvalid-signed &&\n+\n+\techo timeboxedvalid >file && test_tick && git commit -a -m timeboxedvalid -S\"${GPGSSH_KEY_TIMEBOXEDVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDVALID}\" -m timeboxedvalid-signed timeboxedvalid-signed &&\n+\n+\techo timeboxedinvalid >file && test_tick && git commit -a -m timeboxedinvalid -S\"${GPGSSH_KEY_TIMEBOXEDINVALID}\" &&\n+\tgit tag -s -u \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" -m timeboxedinvalid-signed timeboxedinvalid-signed\n+'\n+\n test_expect_success 'message for merging local branch' '\n \techo \"Merge branch ${apos}left${apos}\" >expected &&\n \n@@ -139,6 +159,44 @@ test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh\n \t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual &&\n \tgrep \"${GPGSSH_KEY_NOT_TRUSTED}\" actual\n '\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by expired ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . expired-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}expired-signed${apos}\" actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by not yet valid ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . notyetvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}notyetvalid-signed${apos}\" actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by valid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}timeboxedvalid-signed${apos}\" actual &&\n+\tgrep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual &&\n+\t! grep \"${GPGSSH_BAD_SIGNATURE}\" actual\n+'\n+\n+test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'message for merging local tag signed by invalid timeboxed ssh key' '\n+\ttest_config gpg.ssh.allowedSignersFile \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tgit checkout main &&\n+\tgit fetch . timeboxedinvalid-signed &&\n+\tgit fmt-merge-msg <.git/FETCH_HEAD >actual &&\n+\tgrep \"^Merge tag ${apos}timeboxedinvalid-signed${apos}\" actual &&\n+\t! grep \"${GPGSSH_GOOD_SIGNATURE_TRUSTED}\" actual\n+'\n+\n test_expect_success 'message for merging external branch' '\n \techo \"Merge branch ${apos}left${apos} of $(pwd)\" >expected &&\n \n-- \n2.31.1\n\n"},{"id":"443623","messageId":"20211209085249.13587-10-fs@gigacodes.de","threadId":"56793","inReplyTo":"20211209085249.13587-1-fs@gigacodes.de","subject":"[PATCH v6 9/9] ssh signing: verify ssh-keygen in test prereq","fromName":"Fabian Stelzer","fromEmail":"fs@gigacodes.de","sentAt":"2021-12-09T08:52:49Z","receivedAt":"2021-12-09T08:53:15Z","isPatch":true,"sender":{"key":"fs@gigacodes.de","avatar":"https://avatars.githubusercontent.com/u/564858?v=4"},"body":"Do a full ssh signing, find-principals and verify operation in the test\nprereq's to make sure ssh-keygen works as expected. Only generating the\nkeys and verifying its presence is not sufficient in some situations.\nOne example was ssh-keygen creating unusable ssh keys in cygwin because\nof unsafe default permissions for the key files. The other a broken\nopenssh 8.7 that segfaulted on any find-principals operation. This\nextended prereq check avoids future test breakages in case ssh-keygen or\nany environment behaviour changes.\n\nSigned-off-by: Fabian Stelzer <fs@gigacodes.de>\n---\n t/lib-gpg.sh | 53 +++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 40 insertions(+), 13 deletions(-)\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nindex fc03c8f89b..3fadfcb306 100644\n--- a/t/lib-gpg.sh\n+++ b/t/lib-gpg.sh\n@@ -109,34 +109,61 @@ test_lazy_prereq GPGSSH '\n \techo $ssh_version | grep -q \"find-principals:missing signature file\"\n \ttest $? = 0 || exit 1;\n \n-\t# some broken versions of ssh-keygen segfault on find-principals;\n-\t# avoid testing with them.\n-\tssh-keygen -Y find-principals -f /dev/null -s /dev/null\n-\ttest $? = 139 && exit 1\n-\n+\t# Setup some keys and an allowed signers file\n \tmkdir -p \"${GNUPGHOME}\" &&\n \tchmod 0700 \"${GNUPGHOME}\" &&\n \t(setfacl -k \"${GNUPGHOME}\" 2>/dev/null || true) &&\n \tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_PRIMARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 1\\\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t rsa -b 2048 -N \"\" -C \"git rsa2048 key\" -f \"${GPGSSH_KEY_SECONDARY}\" >/dev/null &&\n-\techo \"\\\"principal with number 2\\\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n \tssh-keygen -t ed25519 -N \"${GPGSSH_KEY_PASSPHRASE}\" -C \"git ed25519 encrypted key\" -f \"${GPGSSH_KEY_WITH_PASSPHRASE}\" >/dev/null &&\n-\techo \"\\\"principal with number 3\\\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n-\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null\n+\tssh-keygen -t ed25519 -N \"\" -C \"git ed25519 key\" -f \"${GPGSSH_KEY_UNTRUSTED}\" >/dev/null &&\n+\n+\tcat >\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"principal with number 1\" $(cat \"${GPGSSH_KEY_PRIMARY}.pub\")\"\n+\t\"principal with number 2\" $(cat \"${GPGSSH_KEY_SECONDARY}.pub\")\"\n+\t\"principal with number 3\" $(cat \"${GPGSSH_KEY_WITH_PASSPHRASE}.pub\")\"\n+\tEOF\n+\n+\t# Verify if at least one key and ssh-keygen works as expected\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_PRIMARY}\" >gpgssh_prereq.sig &&\n+\tssh-keygen -Y find-principals -f \"${GPGSSH_ALLOWED_SIGNERS}\" -s gpgssh_prereq.sig &&\n+\techo \"testpayload\" |\n+\tssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with number 1\" -s gpgssh_prereq.sig\n '\n \n test_lazy_prereq GPGSSH_VERIFYTIME '\n \t# Check if ssh-keygen has a verify-time option by passing an invalid date to it\n \tssh-keygen -Overify-time=INVALID -Y check-novalidate -s doesnotmatter 2>&1 | grep -q -F \"Invalid \\\"verify-time\\\"\" &&\n+\n+\t# Set up keys with key lifetimes\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed valid key\" -f \"${GPGSSH_KEY_TIMEBOXEDVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed valid key\\\" valid-after=\\\"20050407000000\\\",valid-before=\\\"200504100000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_valid=$(cat \"${GPGSSH_KEY_TIMEBOXEDVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"timeboxed invalid key\" -f \"${GPGSSH_KEY_TIMEBOXEDINVALID}\" >/dev/null &&\n-\techo \"\\\"timeboxed invalid key\\\" valid-after=\\\"20050401000000\\\",valid-before=\\\"20050402000000\\\" $(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_invalid=$(cat \"${GPGSSH_KEY_TIMEBOXEDINVALID}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"expired key\" -f \"${GPGSSH_KEY_EXPIRED}\" >/dev/null &&\n-\techo \"\\\"principal with expired key\\\" valid-before=\\\"20000101000000\\\" $(cat \"${GPGSSH_KEY_EXPIRED}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\" &&\n+\tkey_expired=$(cat \"${GPGSSH_KEY_EXPIRED}.pub\") &&\n \tssh-keygen -t ed25519 -N \"\" -C \"not yet valid key\" -f \"${GPGSSH_KEY_NOTYETVALID}\" >/dev/null &&\n-\techo \"\\\"principal with not yet valid key\\\" valid-after=\\\"29990101000000\\\" $(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\")\" >> \"${GPGSSH_ALLOWED_SIGNERS}\"\n+\tkey_notyetvalid=$(cat \"${GPGSSH_KEY_NOTYETVALID}.pub\") &&\n+\n+\t# Timestamps outside of test_tick span\n+\tts2005a=20050401000000 ts2005b=200504020000 &&\n+\t# Timestamps within test_tick span\n+\tts2005c=20050407000000 ts2005d=200504100000 &&\n+\t# Definitely not yet valid / expired timestamps\n+\tts2000=20000101000000 ts2999=29990101000000 &&\n+\n+\tcat >>\"${GPGSSH_ALLOWED_SIGNERS}\" <<-EOF &&\n+\t\"timeboxed valid key\" valid-after=\"$ts2005c\",valid-before=\"$ts2005d\" $key_valid\"\n+\t\"timeboxed invalid key\" valid-after=\"$ts2005a\",valid-before=\"$ts2005b\" $key_invalid\"\n+\t\"principal with expired key\" valid-before=\"$ts2000\" $key_expired\"\n+\t\"principal with not yet valid key\" valid-after=\"$ts2999\" $key_notyetvalid\"\n+\tEOF\n+\n+\t# and verify ssh-keygen verifies the key lifetime\n+\techo \"testpayload\" |\n+\tssh-keygen -Y sign -n \"git\" -f \"${GPGSSH_KEY_EXPIRED}\" >gpgssh_verifytime_prereq.sig &&\n+\t! (ssh-keygen -Y verify -n \"git\" -f \"${GPGSSH_ALLOWED_SIGNERS}\" -I \"principal with expired key\" -s gpgssh_verifytime_prereq.sig)\n '\n \n sanitize_pgp() {\n-- \n2.31.1\n\n"}]}