Git fork

Merge branch 'cc/fast-import-export-signature-names'

Clean up the way how signature on commit objects are exported to
and imported from fast-import stream.

* cc/fast-import-export-signature-names:
fast-(import|export): improve on commit signature output format

+312 -44
+17
Documentation/git-fast-export.adoc
··· 50 50 is the same as how earlier versions of this command without 51 51 this option behaved. 52 52 + 53 + When exported, a signature starts with: 54 + + 55 + gpgsig <git-hash-algo> <signature-format> 56 + + 57 + where <git-hash-algo> is the Git object hash so either "sha1" or 58 + "sha256", and <signature-format> is the signature type, so "openpgp", 59 + "x509", "ssh" or "unknown". 60 + + 61 + For example, an OpenPGP signature on a SHA-1 commit starts with 62 + `gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit 63 + starts with `gpgsig sha256 ssh`. 64 + + 65 + While all the signatures of a commit are exported, an importer may 66 + choose to accept only some of them. For example 67 + linkgit:git-fast-import[1] currently stores at most one signature per 68 + Git hash algorithm in each commit. 69 + + 53 70 NOTE: This is highly experimental and the format of the data stream may 54 71 change in the future without compatibility guarantees. 55 72
+32 -6
Documentation/git-fast-import.adoc
··· 445 445 original-oid? 446 446 ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 447 447 'committer' (SP <name>)? SP LT <email> GT SP <when> LF 448 - ('gpgsig' SP <alg> LF data)? 448 + ('gpgsig' SP <algo> SP <format> LF data)? 449 449 ('encoding' SP <encoding> LF)? 450 450 data 451 451 ('from' SP <commit-ish> LF)? ··· 518 518 ^^^^^^^^ 519 519 520 520 The optional `gpgsig` command is used to include a PGP/GPG signature 521 - that signs the commit data. 521 + or other cryptographic signature that signs the commit data. 522 522 523 - Here <alg> specifies which hashing algorithm is used for this 524 - signature, either `sha1` or `sha256`. 523 + .... 524 + 'gpgsig' SP <git-hash-algo> SP <signature-format> LF data 525 + .... 525 526 526 - NOTE: This is highly experimental and the format of the data stream may 527 - change in the future without compatibility guarantees. 527 + The `gpgsig` command takes two arguments: 528 + 529 + * `<git-hash-algo>` specifies which Git object format this signature 530 + applies to, either `sha1` or `sha256`. This allows to know which 531 + representation of the commit was signed (the SHA-1 or the SHA-256 532 + version) which helps with both signature verification and 533 + interoperability between repos with different hash functions. 534 + 535 + * `<signature-format>` specifies the type of signature, such as 536 + `openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for 537 + tools that process the stream, so they don't have to parse the ASCII 538 + armor to identify the signature type. 539 + 540 + A commit may have at most one signature for the SHA-1 object format 541 + (stored in the "gpgsig" header) and one for the SHA-256 object format 542 + (stored in the "gpgsig-sha256" header). 543 + 544 + See below for a detailed description of the `data` command which 545 + contains the raw signature data. 546 + 547 + Signatures are not yet checked in the current implementation 548 + though. (Already setting the `extensions.compatObjectFormat` 549 + configuration option might help with verifying both SHA-1 and SHA-256 550 + object format signatures when it will be implemented.) 551 + 552 + NOTE: This is highly experimental and the format of the `gpgsig` 553 + command may change in the future without compatibility guarantees. 528 554 529 555 `encoding` 530 556 ^^^^^^^^^^
+48 -14
builtin/fast-export.c
··· 29 29 #include "quote.h" 30 30 #include "remote.h" 31 31 #include "blob.h" 32 + #include "gpg-interface.h" 32 33 33 34 static const char *const fast_export_usage[] = { 34 35 N_("git fast-export [<rev-list-opts>]"), ··· 652 653 return strbuf_detach(&val, NULL); 653 654 } 654 655 656 + static void print_signature(const char *signature, const char *object_hash) 657 + { 658 + if (!signature) 659 + return; 660 + 661 + printf("gpgsig %s %s\ndata %u\n%s\n", 662 + object_hash, 663 + get_signature_format(signature), 664 + (unsigned)strlen(signature), 665 + signature); 666 + } 667 + 668 + static const char *append_signatures_for_header(struct string_list *signatures, 669 + const char *pos, 670 + const char *header, 671 + const char *object_hash) 672 + { 673 + const char *signature; 674 + const char *start = pos; 675 + const char *end = pos; 676 + 677 + while ((signature = find_commit_multiline_header(start + 1, 678 + header, 679 + &end))) { 680 + string_list_append(signatures, signature)->util = (void *)object_hash; 681 + free((char *)signature); 682 + start = end; 683 + } 684 + 685 + return end; 686 + } 687 + 655 688 static void handle_commit(struct commit *commit, struct rev_info *rev, 656 689 struct string_list *paths_of_changed_objects) 657 690 { ··· 660 693 const char *author, *author_end, *committer, *committer_end; 661 694 const char *encoding = NULL; 662 695 size_t encoding_len; 663 - const char *signature_alg = NULL, *signature = NULL; 696 + struct string_list signatures = STRING_LIST_INIT_DUP; 664 697 const char *message; 665 698 char *reencoded = NULL; 666 699 struct commit_list *p; ··· 700 733 } 701 734 702 735 if (*commit_buffer_cursor == '\n') { 703 - if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) 704 - signature_alg = "sha1"; 705 - else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) 706 - signature_alg = "sha256"; 736 + const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor, 737 + "gpgsig", "sha1"); 738 + const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor, 739 + "gpgsig-sha256", "sha256"); 740 + commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256; 707 741 } 708 742 709 743 message = strstr(commit_buffer_cursor, "\n\n"); ··· 769 803 printf("%.*s\n%.*s\n", 770 804 (int)(author_end - author), author, 771 805 (int)(committer_end - committer), committer); 772 - if (signature) { 806 + if (signatures.nr) { 773 807 switch (signed_commit_mode) { 774 808 case SIGN_ABORT: 775 809 die("encountered signed commit %s; use " 776 810 "--signed-commits=<mode> to handle it", 777 811 oid_to_hex(&commit->object.oid)); 778 812 case SIGN_WARN_VERBATIM: 779 - warning("exporting signed commit %s", 780 - oid_to_hex(&commit->object.oid)); 813 + warning("exporting %"PRIuMAX" signature(s) for commit %s", 814 + (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); 781 815 /* fallthru */ 782 816 case SIGN_VERBATIM: 783 - printf("gpgsig %s\ndata %u\n%s", 784 - signature_alg, 785 - (unsigned)strlen(signature), 786 - signature); 817 + for (size_t i = 0; i < signatures.nr; i++) { 818 + struct string_list_item *item = &signatures.items[i]; 819 + print_signature(item->string, item->util); 820 + } 787 821 break; 788 822 case SIGN_WARN_STRIP: 789 - warning("stripping signature from commit %s", 823 + warning("stripping signature(s) from commit %s", 790 824 oid_to_hex(&commit->object.oid)); 791 825 /* fallthru */ 792 826 case SIGN_STRIP: 793 827 break; 794 828 } 795 - free((char *)signature); 829 + string_list_clear(&signatures, 0); 796 830 } 797 831 if (!reencoded && encoding) 798 832 printf("encoding %.*s\n", (int)encoding_len, encoding);
+91 -22
builtin/fast-import.c
··· 29 29 #include "commit-reach.h" 30 30 #include "khash.h" 31 31 #include "date.h" 32 + #include "gpg-interface.h" 32 33 33 34 #define PACK_ID_BITS 16 34 35 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) ··· 2716 2717 return list; 2717 2718 } 2718 2719 2720 + struct signature_data { 2721 + char *hash_algo; /* "sha1" or "sha256" */ 2722 + char *sig_format; /* "openpgp", "x509", "ssh", or "unknown" */ 2723 + struct strbuf data; /* The actual signature data */ 2724 + }; 2725 + 2726 + static void parse_one_signature(struct signature_data *sig, const char *v) 2727 + { 2728 + char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */ 2729 + char *space = strchr(args, ' '); 2730 + 2731 + if (!space) 2732 + die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', " 2733 + "got 'gpgsig %s'", args); 2734 + *space = '\0'; 2735 + 2736 + sig->hash_algo = args; 2737 + sig->sig_format = space + 1; 2738 + 2739 + /* Validate hash algorithm */ 2740 + if (strcmp(sig->hash_algo, "sha1") && 2741 + strcmp(sig->hash_algo, "sha256")) 2742 + die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo); 2743 + 2744 + /* Validate signature format */ 2745 + if (!valid_signature_format(sig->sig_format)) 2746 + die("Invalid signature format in gpgsig: '%s'", sig->sig_format); 2747 + if (!strcmp(sig->sig_format, "unknown")) 2748 + warning("'unknown' signature format in gpgsig"); 2749 + 2750 + /* Read signature data */ 2751 + read_next_command(); 2752 + parse_data(&sig->data, 0, NULL); 2753 + } 2754 + 2755 + static void add_gpgsig_to_commit(struct strbuf *commit_data, 2756 + const char *header, 2757 + struct signature_data *sig) 2758 + { 2759 + struct string_list siglines = STRING_LIST_INIT_NODUP; 2760 + 2761 + if (!sig->hash_algo) 2762 + return; 2763 + 2764 + strbuf_addstr(commit_data, header); 2765 + string_list_split_in_place(&siglines, sig->data.buf, "\n", -1); 2766 + strbuf_add_separated_string_list(commit_data, "\n ", &siglines); 2767 + strbuf_addch(commit_data, '\n'); 2768 + string_list_clear(&siglines, 1); 2769 + strbuf_release(&sig->data); 2770 + free(sig->hash_algo); 2771 + } 2772 + 2773 + static void store_signature(struct signature_data *stored_sig, 2774 + struct signature_data *new_sig, 2775 + const char *hash_type) 2776 + { 2777 + if (stored_sig->hash_algo) { 2778 + warning("multiple %s signatures found, " 2779 + "ignoring additional signature", 2780 + hash_type); 2781 + strbuf_release(&new_sig->data); 2782 + free(new_sig->hash_algo); 2783 + } else { 2784 + *stored_sig = *new_sig; 2785 + } 2786 + } 2787 + 2719 2788 static void parse_new_commit(const char *arg) 2720 2789 { 2721 - static struct strbuf sig = STRBUF_INIT; 2722 2790 static struct strbuf msg = STRBUF_INIT; 2723 - struct string_list siglines = STRING_LIST_INIT_NODUP; 2791 + struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT }; 2792 + struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT }; 2724 2793 struct branch *b; 2725 2794 char *author = NULL; 2726 2795 char *committer = NULL; 2727 - char *sig_alg = NULL; 2728 2796 char *encoding = NULL; 2729 2797 struct hash_list *merge_list = NULL; 2730 2798 unsigned int merge_count; ··· 2748 2816 } 2749 2817 if (!committer) 2750 2818 die("Expected committer but didn't get one"); 2751 - if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { 2752 - sig_alg = xstrdup(v); 2819 + 2820 + /* Process signatures (up to 2: one "sha1" and one "sha256") */ 2821 + while (skip_prefix(command_buf.buf, "gpgsig ", &v)) { 2822 + struct signature_data sig = { NULL, NULL, STRBUF_INIT }; 2823 + 2824 + parse_one_signature(&sig, v); 2825 + 2826 + if (!strcmp(sig.hash_algo, "sha1")) 2827 + store_signature(&sig_sha1, &sig, "SHA-1"); 2828 + else if (!strcmp(sig.hash_algo, "sha256")) 2829 + store_signature(&sig_sha256, &sig, "SHA-256"); 2830 + else 2831 + BUG("parse_one_signature() returned unknown hash algo"); 2832 + 2753 2833 read_next_command(); 2754 - parse_data(&sig, 0, NULL); 2755 - read_next_command(); 2756 - } else 2757 - strbuf_setlen(&sig, 0); 2834 + } 2835 + 2758 2836 if (skip_prefix(command_buf.buf, "encoding ", &v)) { 2759 2837 encoding = xstrdup(v); 2760 2838 read_next_command(); ··· 2828 2906 strbuf_addf(&new_data, 2829 2907 "encoding %s\n", 2830 2908 encoding); 2831 - if (sig_alg) { 2832 - if (!strcmp(sig_alg, "sha1")) 2833 - strbuf_addstr(&new_data, "gpgsig "); 2834 - else if (!strcmp(sig_alg, "sha256")) 2835 - strbuf_addstr(&new_data, "gpgsig-sha256 "); 2836 - else 2837 - die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); 2838 - string_list_split_in_place(&siglines, sig.buf, "\n", -1); 2839 - strbuf_add_separated_string_list(&new_data, "\n ", &siglines); 2840 - strbuf_addch(&new_data, '\n'); 2841 - } 2909 + 2910 + add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); 2911 + add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); 2912 + 2842 2913 strbuf_addch(&new_data, '\n'); 2843 2914 strbuf_addbuf(&new_data, &msg); 2844 - string_list_clear(&siglines, 1); 2845 2915 free(author); 2846 2916 free(committer); 2847 - free(sig_alg); 2848 2917 free(encoding); 2849 2918 2850 2919 if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))
+12
gpg-interface.c
··· 144 144 return NULL; 145 145 } 146 146 147 + const char *get_signature_format(const char *buf) 148 + { 149 + struct gpg_format *format = get_format_by_sig(buf); 150 + return format ? format->name : "unknown"; 151 + } 152 + 153 + int valid_signature_format(const char *format) 154 + { 155 + return (!!get_format_by_name(format) || 156 + !strcmp(format, "unknown")); 157 + } 158 + 147 159 void signature_check_clear(struct signature_check *sigc) 148 160 { 149 161 FREE_AND_NULL(sigc->payload);
+12
gpg-interface.h
··· 48 48 void signature_check_clear(struct signature_check *sigc); 49 49 50 50 /* 51 + * Return the format of the signature (like "openpgp", "x509", "ssh" 52 + * or "unknown"). 53 + */ 54 + const char *get_signature_format(const char *buf); 55 + 56 + /* 57 + * Is the signature format valid (like "openpgp", "x509", "ssh" or 58 + * "unknown") 59 + */ 60 + int valid_signature_format(const char *format); 61 + 62 + /* 51 63 * Look at a GPG signed tag object. If such a signature exists, store it in 52 64 * signature and the signed content in payload. Return 1 if a signature was 53 65 * found, and 0 otherwise.
+100 -2
t/t9350-fast-export.sh
··· 314 314 test_expect_success GPG 'signed-commits=verbatim' ' 315 315 316 316 git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && 317 - grep "^gpgsig sha" output && 317 + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && 318 318 grep "encoding ISO-8859-1" output && 319 319 ( 320 320 cd new && ··· 328 328 test_expect_success GPG 'signed-commits=warn-verbatim' ' 329 329 330 330 git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && 331 - grep "^gpgsig sha" output && 331 + test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output && 332 332 grep "encoding ISO-8859-1" output && 333 333 test -s err && 334 334 ( ··· 366 366 STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && 367 367 test $COMMIT_SIGNING != $STRIPPED 368 368 ) 369 + 370 + ' 371 + 372 + test_expect_success GPGSM 'setup X.509 signed commit' ' 373 + 374 + git checkout -b x509-signing main && 375 + test_config gpg.format x509 && 376 + test_config user.signingkey $GIT_COMMITTER_EMAIL && 377 + echo "X.509 content" >file && 378 + git add file && 379 + git commit -S -m "X.509 signed commit" && 380 + X509_COMMIT=$(git rev-parse HEAD) && 381 + git checkout main 382 + 383 + ' 384 + 385 + test_expect_success GPGSM 'round-trip X.509 signed commit' ' 386 + 387 + git fast-export --signed-commits=verbatim x509-signing >output && 388 + test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output && 389 + ( 390 + cd new && 391 + git fast-import && 392 + git cat-file commit refs/heads/x509-signing >actual && 393 + grep "^gpgsig" actual && 394 + IMPORTED=$(git rev-parse refs/heads/x509-signing) && 395 + test $X509_COMMIT = $IMPORTED 396 + ) <output 397 + 398 + ' 399 + 400 + test_expect_success GPGSSH 'setup SSH signed commit' ' 401 + 402 + git checkout -b ssh-signing main && 403 + test_config gpg.format ssh && 404 + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && 405 + echo "SSH content" >file && 406 + git add file && 407 + git commit -S -m "SSH signed commit" && 408 + SSH_COMMIT=$(git rev-parse HEAD) && 409 + git checkout main 410 + 411 + ' 412 + 413 + test_expect_success GPGSSH 'round-trip SSH signed commit' ' 414 + 415 + git fast-export --signed-commits=verbatim ssh-signing >output && 416 + test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output && 417 + ( 418 + cd new && 419 + git fast-import && 420 + git cat-file commit refs/heads/ssh-signing >actual && 421 + grep "^gpgsig" actual && 422 + IMPORTED=$(git rev-parse refs/heads/ssh-signing) && 423 + test $SSH_COMMIT = $IMPORTED 424 + ) <output 369 425 370 426 ' 371 427 ··· 903 959 # fix up lines which mention the ref for comparison 904 960 sed s/--dashes/nodash/ <actual.raw >actual && 905 961 test_cmp expect actual 962 + ' 963 + 964 + test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' 965 + # Create a signed SHA-256 commit 966 + git init --object-format=sha256 explicit-sha256 && 967 + git -C explicit-sha256 config extensions.compatObjectFormat sha1 && 968 + git -C explicit-sha256 checkout -b dual-signed && 969 + test_commit -C explicit-sha256 A && 970 + echo B >explicit-sha256/B && 971 + git -C explicit-sha256 add B && 972 + test_tick && 973 + git -C explicit-sha256 commit -S -m "signed" B && 974 + SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && 975 + 976 + # Create the corresponding SHA-1 commit 977 + SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) && 978 + 979 + # Check that the resulting SHA-1 commit has both signatures 980 + echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out && 981 + test_grep -E "^gpgsig " out && 982 + test_grep -E "^gpgsig-sha256 " out 983 + ' 984 + 985 + test_expect_success GPG 'export and import of doubly signed commit' ' 986 + git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && 987 + test_grep -E "^gpgsig sha1 openpgp" output && 988 + test_grep -E "^gpgsig sha256 openpgp" output && 989 + 990 + ( 991 + cd new && 992 + git fast-import && 993 + git cat-file commit refs/heads/dual-signed >actual && 994 + test_grep -E "^gpgsig " actual && 995 + test_grep -E "^gpgsig-sha256 " actual && 996 + IMPORTED=$(git rev-parse refs/heads/dual-signed) && 997 + if test "$GIT_DEFAULT_HASH" = "sha1" 998 + then 999 + test $SHA1_B = $IMPORTED 1000 + else 1001 + test $SHA256_B = $IMPORTED 1002 + fi 1003 + ) <output 906 1004 ' 907 1005 908 1006 test_done