Git fork

Merge branch 'cc/fast-import-strip-signed-tags'

"git fast-import" is taught to handle signed tags, just like it
recently learned to handle signed commits, in different ways.

* cc/fast-import-strip-signed-tags:
fast-import: add '--signed-tags=<mode>' option
fast-export: handle all kinds of tag signatures
t9350: properly count annotated tags
lib-gpg: allow tests with GPGSM or GPGSSH prereq first
doc: git-tag: stop focusing on GPG signed tags

+229 -27
+5
Documentation/git-fast-import.adoc
··· 66 66 remote-helpers that use the `import` capability, as they are 67 67 already trusted to run their own code. 68 68 69 + --signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort):: 70 + Specify how to handle signed tags. Behaves in the same way 71 + as the same option in linkgit:git-fast-export[1], except that 72 + default is 'verbatim' (instead of 'abort'). 73 + 69 74 --signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: 70 75 Specify how to handle signed commits. Behaves in the same way 71 76 as the same option in linkgit:git-fast-export[1], except that
+33 -15
Documentation/git-tag.adoc
··· 3 3 4 4 NAME 5 5 ---- 6 - git-tag - Create, list, delete or verify a tag object signed with GPG 6 + git-tag - Create, list, delete or verify tags 7 7 8 8 9 9 SYNOPSIS ··· 38 38 Otherwise, a tag reference that points directly at the given object 39 39 (i.e., a lightweight tag) is created. 40 40 41 - A GnuPG signed tag object will be created when `-s` or `-u 42 - <key-id>` is used. When `-u <key-id>` is not used, the 43 - committer identity for the current user is used to find the 44 - GnuPG key for signing. The configuration variable `gpg.program` 45 - is used to specify custom GnuPG binary. 41 + A cryptographically signed tag object will be created when `-s` or 42 + `-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is 43 + controlled by the `gpg.format` configuration variable, defaulting to 44 + OpenPGP. When `-u <key-id>` is not used, the committer identity for 45 + the current user is used to find the key for signing. The 46 + configuration variable `gpg.program` is used to specify a custom 47 + signing binary. 46 48 47 49 Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated" 48 50 tags; they contain a creation date, the tagger name and e-mail, a 49 - tagging message, and an optional GnuPG signature. Whereas a 51 + tagging message, and an optional cryptographic signature. Whereas a 50 52 "lightweight" tag is simply a name for an object (usually a commit 51 53 object). 52 54 ··· 64 66 65 67 `-s`:: 66 68 `--sign`:: 67 - Make a GPG-signed tag, using the default e-mail address's key. 68 - The default behavior of tag GPG-signing is controlled by `tag.gpgSign` 69 - configuration variable if it exists, or disabled otherwise. 70 - See linkgit:git-config[1]. 69 + Make a cryptographically signed tag, using the default signing 70 + key. The signing backend used depends on the `gpg.format` 71 + configuration variable. The default key is determined by the 72 + backend. For GPG, it's based on the committer's email address, 73 + while for SSH it may be a specific key file or agent 74 + identity. See linkgit:git-config[1]. 71 75 72 76 `--no-sign`:: 73 77 Override `tag.gpgSign` configuration variable that is ··· 75 79 76 80 `-u <key-id>`:: 77 81 `--local-user=<key-id>`:: 78 - Make a GPG-signed tag, using the given key. 82 + Make a cryptographically signed tag using the given key. The 83 + format of the <key-id> and the backend used depend on the 84 + `gpg.format` configuration variable. See 85 + linkgit:git-config[1]. 79 86 80 87 `-f`:: 81 88 `--force`:: ··· 87 94 88 95 `-v`:: 89 96 `--verify`:: 90 - Verify the GPG signature of the given tag names. 97 + Verify the cryptographic signature of the given tags. 91 98 92 99 `-n<num>`:: 93 100 _<num>_ specifies how many lines from the annotation, if any, ··· 235 242 236 243 ------------------------------------- 237 244 [user] 238 - signingKey = <gpg-key-id> 245 + signingKey = <key-id> 239 246 ------------------------------------- 240 247 248 + The signing backend can be chosen via the `gpg.format` configuration 249 + variable, which defaults to `openpgp`. See linkgit:git-config[1] 250 + for a list of other supported formats. 251 + 252 + The path to the program used for each signing backend can be specified 253 + with the `gpg.<format>.program` configuration variable. For the 254 + `openpgp` backend, `gpg.program` can be used as a synonym for 255 + `gpg.openpgp.program`. See linkgit:git-config[1] for details. 256 + 241 257 `pager.tag` is only respected when listing tags, i.e., when `-l` is 242 258 used or implied. The default is to use a pager. 243 - See linkgit:git-config[1]. 259 + 260 + See linkgit:git-config[1] for more details and other configuration 261 + variables. 244 262 245 263 DISCUSSION 246 264 ----------
+3 -4
builtin/fast-export.c
··· 931 931 932 932 /* handle signed tags */ 933 933 if (message) { 934 - const char *signature = strstr(message, 935 - "\n-----BEGIN PGP SIGNATURE-----\n"); 936 - if (signature) 934 + size_t sig_offset = parse_signed_buffer(message, message_size); 935 + if (sig_offset < message_size) 937 936 switch (signed_tag_mode) { 938 937 case SIGN_ABORT: 939 938 die("encountered signed tag %s; use " ··· 950 949 oid_to_hex(&tag->object.oid)); 951 950 /* fallthru */ 952 951 case SIGN_STRIP: 953 - message_size = signature + 1 - message; 952 + message_size = sig_offset; 954 953 break; 955 954 } 956 955 }
+43
builtin/fast-import.c
··· 188 188 static const char **global_argv; 189 189 static const char *global_prefix; 190 190 191 + static enum sign_mode signed_tag_mode = SIGN_VERBATIM; 191 192 static enum sign_mode signed_commit_mode = SIGN_VERBATIM; 192 193 193 194 /* Memory pools */ ··· 2963 2964 b->last_commit = object_count_by_type[OBJ_COMMIT]; 2964 2965 } 2965 2966 2967 + static void handle_tag_signature(struct strbuf *msg, const char *name) 2968 + { 2969 + size_t sig_offset = parse_signed_buffer(msg->buf, msg->len); 2970 + 2971 + /* If there is no signature, there is nothing to do. */ 2972 + if (sig_offset >= msg->len) 2973 + return; 2974 + 2975 + switch (signed_tag_mode) { 2976 + 2977 + /* First, modes that don't change anything */ 2978 + case SIGN_ABORT: 2979 + die(_("encountered signed tag; use " 2980 + "--signed-tags=<mode> to handle it")); 2981 + case SIGN_WARN_VERBATIM: 2982 + warning(_("importing a tag signature verbatim for tag '%s'"), name); 2983 + /* fallthru */ 2984 + case SIGN_VERBATIM: 2985 + /* Nothing to do, the signature will be put into the imported tag. */ 2986 + break; 2987 + 2988 + /* Second, modes that remove the signature */ 2989 + case SIGN_WARN_STRIP: 2990 + warning(_("stripping a tag signature for tag '%s'"), name); 2991 + /* fallthru */ 2992 + case SIGN_STRIP: 2993 + /* Truncate the buffer to remove the signature */ 2994 + strbuf_setlen(msg, sig_offset); 2995 + break; 2996 + 2997 + /* Third, BUG */ 2998 + default: 2999 + BUG("invalid signed_tag_mode value %d from tag '%s'", 3000 + signed_tag_mode, name); 3001 + } 3002 + } 3003 + 2966 3004 static void parse_new_tag(const char *arg) 2967 3005 { 2968 3006 static struct strbuf msg = STRBUF_INIT; ··· 3025 3063 3026 3064 /* tag payload/message */ 3027 3065 parse_data(&msg, 0, NULL); 3066 + 3067 + handle_tag_signature(&msg, t->name); 3028 3068 3029 3069 /* build the tag object */ 3030 3070 strbuf_reset(&new_data); ··· 3546 3586 } else if (skip_prefix(option, "signed-commits=", &option)) { 3547 3587 if (parse_sign_mode(option, &signed_commit_mode)) 3548 3588 usagef(_("unknown --signed-commits mode '%s'"), option); 3589 + } else if (skip_prefix(option, "signed-tags=", &option)) { 3590 + if (parse_sign_mode(option, &signed_tag_mode)) 3591 + usagef(_("unknown --signed-tags mode '%s'"), option); 3549 3592 } else if (!strcmp(option, "quiet")) { 3550 3593 show_stats = 0; 3551 3594 quiet = 1;
+20 -4
t/lib-gpg.sh
··· 9 9 GNUPGHOME="$(pwd)/gpghome" 10 10 export GNUPGHOME 11 11 12 + # All the "test_lazy_prereq GPG*" below should use 13 + # `prepare_gnupghome()` either directly or through a call to 14 + # `test_have_prereq GPG*`. That's because `gpg` and `gpgsm` 15 + # only create the directory specified using "$GNUPGHOME" or 16 + # `--homedir` if it's the default (usually "~/.gnupg"). 17 + prepare_gnupghome() { 18 + mkdir -p "$GNUPGHOME" && 19 + chmod 0700 "$GNUPGHOME" 20 + } 21 + 12 22 test_lazy_prereq GPG ' 13 23 gpg_version=$(gpg --version 2>&1) 14 24 test $? != 127 || exit 1 ··· 38 48 # To export ownertrust: 39 49 # gpg --homedir /tmp/gpghome --export-ownertrust \ 40 50 # > lib-gpg/ownertrust 41 - mkdir "$GNUPGHOME" && 42 - chmod 0700 "$GNUPGHOME" && 51 + prepare_gnupghome && 43 52 (gpgconf --kill all || : ) && 44 53 gpg --homedir "${GNUPGHOME}" --import \ 45 54 "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && ··· 63 72 ;; 64 73 *) 65 74 (gpgconf --kill all || : ) && 75 + 76 + # NEEDSWORK: prepare_gnupghome() should definitely be 77 + # called here, but it looks like it exposes a 78 + # pre-existing, hidden bug by allowing some tests in 79 + # t1016-compatObjectFormat.sh to run instead of being 80 + # skipped. See: 81 + # https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/ 82 + 66 83 gpg --homedir "${GNUPGHOME}" --import \ 67 84 "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && 68 85 gpg --homedir "${GNUPGHOME}" --import-ownertrust \ ··· 132 149 test $? = 0 || exit 1; 133 150 134 151 # Setup some keys and an allowed signers file 135 - mkdir -p "${GNUPGHOME}" && 136 - chmod 0700 "${GNUPGHOME}" && 152 + prepare_gnupghome && 137 153 (setfacl -k "${GNUPGHOME}" 2>/dev/null || true) && 138 154 ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null && 139 155 ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
+1
t/meson.build
··· 1037 1037 't9303-fast-import-compression.sh', 1038 1038 't9304-fast-import-marks.sh', 1039 1039 't9305-fast-import-signatures.sh', 1040 + 't9306-fast-import-signed-tags.sh', 1040 1041 't9350-fast-export.sh', 1041 1042 't9351-fast-export-anonymize.sh', 1042 1043 't9400-git-cvsserver-server.sh',
+80
t/t9306-fast-import-signed-tags.sh
··· 1 + #!/bin/sh 2 + 3 + test_description='git fast-import --signed-tags=<mode>' 4 + 5 + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main 6 + 7 + . ./test-lib.sh 8 + . "$TEST_DIRECTORY/lib-gpg.sh" 9 + 10 + test_expect_success 'set up unsigned initial commit and import repo' ' 11 + test_commit first && 12 + git init new 13 + ' 14 + 15 + test_expect_success 'import no signed tag with --signed-tags=abort' ' 16 + git fast-export --signed-tags=verbatim >output && 17 + git -C new fast-import --quiet --signed-tags=abort <output 18 + ' 19 + 20 + test_expect_success GPG 'set up OpenPGP signed tag' ' 21 + git tag -s -m "OpenPGP signed tag" openpgp-signed first && 22 + OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) && 23 + git fast-export --signed-tags=verbatim openpgp-signed >output 24 + ' 25 + 26 + test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' ' 27 + test_must_fail git -C new fast-import --quiet --signed-tags=abort <output 28 + ' 29 + 30 + test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' ' 31 + git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 && 32 + IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) && 33 + test $OPENPGP_SIGNED = $IMPORTED && 34 + test_must_be_empty log 35 + ' 36 + 37 + test_expect_success GPGSM 'setup X.509 signed tag' ' 38 + test_config gpg.format x509 && 39 + test_config user.signingkey $GIT_COMMITTER_EMAIL && 40 + 41 + git tag -s -m "X.509 signed tag" x509-signed first && 42 + X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) && 43 + git fast-export --signed-tags=verbatim x509-signed >output 44 + ' 45 + 46 + test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' ' 47 + git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 && 48 + test_grep "stripping a tag signature for tag '\''x509-signed'\''" log && 49 + IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) && 50 + test $X509_SIGNED != $IMPORTED && 51 + git -C new cat-file -p x509-signed >out && 52 + test_grep ! "SIGNED MESSAGE" out 53 + ' 54 + 55 + test_expect_success GPGSSH 'setup SSH signed tag' ' 56 + test_config gpg.format ssh && 57 + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && 58 + 59 + git tag -s -m "SSH signed tag" ssh-signed first && 60 + SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) && 61 + git fast-export --signed-tags=verbatim ssh-signed >output 62 + ' 63 + 64 + test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' ' 65 + git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 && 66 + test_grep "importing a tag signature verbatim for tag '\''ssh-signed'\''" log && 67 + IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) && 68 + test $SSH_SIGNED = $IMPORTED 69 + ' 70 + 71 + test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' ' 72 + git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 && 73 + test_must_be_empty log && 74 + IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) && 75 + test $SSH_SIGNED != $IMPORTED && 76 + git -C new cat-file -p ssh-signed >out && 77 + test_grep ! "SSH SIGNATURE" out 78 + ' 79 + 80 + test_done
+44 -4
t/t9350-fast-export.sh
··· 35 35 git commit -m sitzt file2 && 36 36 test_tick && 37 37 git tag -a -m valentin muss && 38 + ANNOTATED_TAG_COUNT=1 && 38 39 git merge -s ours main 39 40 40 41 ' ··· 229 230 230 231 test_expect_success 'set up faked signed tag' ' 231 232 232 - git fast-import <signed-tag-import 233 + git fast-import <signed-tag-import && 234 + ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) 233 235 234 236 ' 235 237 ··· 277 279 test -s err 278 280 ' 279 281 282 + test_expect_success GPGSM 'setup X.509 signed tag' ' 283 + test_config gpg.format x509 && 284 + test_config user.signingkey $GIT_COMMITTER_EMAIL && 285 + 286 + git tag -s -m "X.509 signed tag" x509-signed $(git rev-parse HEAD) && 287 + ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) 288 + ' 289 + 290 + test_expect_success GPGSM 'signed-tags=verbatim with X.509' ' 291 + git fast-export --signed-tags=verbatim x509-signed > output && 292 + test_grep "SIGNED MESSAGE" output 293 + ' 294 + 295 + test_expect_success GPGSM 'signed-tags=strip with X.509' ' 296 + git fast-export --signed-tags=strip x509-signed > output && 297 + test_grep ! "SIGNED MESSAGE" output 298 + ' 299 + 300 + test_expect_success GPGSSH 'setup SSH signed tag' ' 301 + test_config gpg.format ssh && 302 + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && 303 + 304 + git tag -s -m "SSH signed tag" ssh-signed $(git rev-parse HEAD) && 305 + ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) 306 + ' 307 + 308 + test_expect_success GPGSSH 'signed-tags=verbatim with SSH' ' 309 + git fast-export --signed-tags=verbatim ssh-signed > output && 310 + test_grep "SSH SIGNATURE" output 311 + ' 312 + 313 + test_expect_success GPGSSH 'signed-tags=strip with SSH' ' 314 + git fast-export --signed-tags=strip ssh-signed > output && 315 + test_grep ! "SSH SIGNATURE" output 316 + ' 317 + 280 318 test_expect_success GPG 'set up signed commit' ' 281 319 282 320 # Generate a commit with both "gpgsig" and "encoding" set, so ··· 491 529 test_expect_success 'fast-export | fast-import when main is tagged' ' 492 530 493 531 git tag -m msg last && 532 + ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) && 494 533 git fast-export -C -C --signed-tags=strip --all > output && 495 - test $(grep -c "^tag " output) = 3 534 + test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT 496 535 497 536 ' 498 537 ··· 506 545 507 546 TAG=$(git hash-object --literally -t tag -w tag-content) && 508 547 git update-ref refs/tags/sonnenschein $TAG && 548 + ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) && 509 549 git fast-export -C -C --signed-tags=strip --all > output && 510 - test $(grep -c "^tag " output) = 4 && 550 + test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT && 511 551 ! grep "Unspecified Tagger" output && 512 552 git fast-export -C -C --signed-tags=strip --all \ 513 553 --fake-missing-tagger > output && 514 - test $(grep -c "^tag " output) = 4 && 554 + test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT && 515 555 grep "Unspecified Tagger" output 516 556 517 557 '