Fossil SCM

Add ability to sign check-ins with ssh.

danield 2025-01-14 17:13 trunk merge
Commit 0f0184e0cc8174ae51b8398494f006509d570ec4b620ae3fced1e8247263f796
+26 -3
--- src/clearsign.c
+++ src/clearsign.c
@@ -29,28 +29,51 @@
2929
int clearsign(Blob *pIn, Blob *pOut){
3030
char *zRand;
3131
char *zIn;
3232
char *zOut;
3333
char *zBase = db_get("pgp-command", "gpg --clearsign -o ");
34
+ int useSsh = 0;
3435
char *zCmd;
3536
int rc;
3637
if( is_false(zBase) ){
3738
return 0;
3839
}
3940
zRand = db_text(0, "SELECT hex(randomblob(10))");
4041
zOut = mprintf("out-%s", zRand);
41
- zIn = mprintf("in-%z", zRand);
4242
blob_write_to_file(pIn, zOut);
43
- zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
43
+ useSsh = (fossil_strncmp(command_basename(zBase), "ssh", 3)==0);
44
+ if( useSsh ){
45
+ zIn = mprintf("out-%s.sig", zRand);
46
+ zCmd = mprintf("%s %s", zBase, zOut);
47
+ }else{
48
+ zIn = mprintf("in-%z", zRand);
49
+ zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
50
+ }
4451
rc = fossil_system(zCmd);
4552
free(zCmd);
4653
if( rc==0 ){
4754
if( pOut==pIn ){
4855
blob_reset(pIn);
4956
}
5057
blob_zero(pOut);
51
- blob_read_from_file(pOut, zIn, ExtFILE);
58
+ if( useSsh ){
59
+ /* As of 2025, SSH cannot create non-detached SSH signatures */
60
+ /* We put one together */
61
+ Blob tmpBlob;
62
+ blob_zero(&tmpBlob);
63
+ blob_read_from_file(&tmpBlob, zOut, ExtFILE);
64
+ /* Add armor header line and manifest */
65
+ blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n");
66
+ blob_appendf(pOut, "%s", blob_str(&tmpBlob));
67
+ blob_zero(&tmpBlob);
68
+ blob_read_from_file(&tmpBlob, zIn, ExtFILE);
69
+ /* Add signature - already armored by SSH */
70
+ blob_appendf(pOut, "%s", blob_str(&tmpBlob));
71
+ }else{
72
+ /* Assume that the external command creates non-detached signatures */
73
+ blob_read_from_file(pOut, zIn, ExtFILE);
74
+ }
5275
}else{
5376
if( pOut!=pIn ){
5477
blob_copy(pOut, pIn);
5578
}
5679
}
5780
--- src/clearsign.c
+++ src/clearsign.c
@@ -29,28 +29,51 @@
29 int clearsign(Blob *pIn, Blob *pOut){
30 char *zRand;
31 char *zIn;
32 char *zOut;
33 char *zBase = db_get("pgp-command", "gpg --clearsign -o ");
 
34 char *zCmd;
35 int rc;
36 if( is_false(zBase) ){
37 return 0;
38 }
39 zRand = db_text(0, "SELECT hex(randomblob(10))");
40 zOut = mprintf("out-%s", zRand);
41 zIn = mprintf("in-%z", zRand);
42 blob_write_to_file(pIn, zOut);
43 zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
 
 
 
 
 
 
 
44 rc = fossil_system(zCmd);
45 free(zCmd);
46 if( rc==0 ){
47 if( pOut==pIn ){
48 blob_reset(pIn);
49 }
50 blob_zero(pOut);
51 blob_read_from_file(pOut, zIn, ExtFILE);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52 }else{
53 if( pOut!=pIn ){
54 blob_copy(pOut, pIn);
55 }
56 }
57
--- src/clearsign.c
+++ src/clearsign.c
@@ -29,28 +29,51 @@
29 int clearsign(Blob *pIn, Blob *pOut){
30 char *zRand;
31 char *zIn;
32 char *zOut;
33 char *zBase = db_get("pgp-command", "gpg --clearsign -o ");
34 int useSsh = 0;
35 char *zCmd;
36 int rc;
37 if( is_false(zBase) ){
38 return 0;
39 }
40 zRand = db_text(0, "SELECT hex(randomblob(10))");
41 zOut = mprintf("out-%s", zRand);
 
42 blob_write_to_file(pIn, zOut);
43 useSsh = (fossil_strncmp(command_basename(zBase), "ssh", 3)==0);
44 if( useSsh ){
45 zIn = mprintf("out-%s.sig", zRand);
46 zCmd = mprintf("%s %s", zBase, zOut);
47 }else{
48 zIn = mprintf("in-%z", zRand);
49 zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
50 }
51 rc = fossil_system(zCmd);
52 free(zCmd);
53 if( rc==0 ){
54 if( pOut==pIn ){
55 blob_reset(pIn);
56 }
57 blob_zero(pOut);
58 if( useSsh ){
59 /* As of 2025, SSH cannot create non-detached SSH signatures */
60 /* We put one together */
61 Blob tmpBlob;
62 blob_zero(&tmpBlob);
63 blob_read_from_file(&tmpBlob, zOut, ExtFILE);
64 /* Add armor header line and manifest */
65 blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n");
66 blob_appendf(pOut, "%s", blob_str(&tmpBlob));
67 blob_zero(&tmpBlob);
68 blob_read_from_file(&tmpBlob, zIn, ExtFILE);
69 /* Add signature - already armored by SSH */
70 blob_appendf(pOut, "%s", blob_str(&tmpBlob));
71 }else{
72 /* Assume that the external command creates non-detached signatures */
73 blob_read_from_file(pOut, zIn, ExtFILE);
74 }
75 }else{
76 if( pOut!=pIn ){
77 blob_copy(pOut, pIn);
78 }
79 }
80
--- src/content.c
+++ src/content.c
@@ -946,10 +946,11 @@
946946
static int looks_like_control_artifact(Blob *p){
947947
const char *z = blob_buffer(p);
948948
int n = blob_size(p);
949949
if( n<10 ) return 0;
950950
if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) return 1;
951
+ if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) return 1;
951952
if( z[0]<'A' || z[0]>'Z' || z[1]!=' ' || z[0]=='I' ) return 0;
952953
if( z[n-1]!='\n' ) return 0;
953954
return 1;
954955
}
955956
956957
--- src/content.c
+++ src/content.c
@@ -946,10 +946,11 @@
946 static int looks_like_control_artifact(Blob *p){
947 const char *z = blob_buffer(p);
948 int n = blob_size(p);
949 if( n<10 ) return 0;
950 if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) return 1;
 
951 if( z[0]<'A' || z[0]>'Z' || z[1]!=' ' || z[0]=='I' ) return 0;
952 if( z[n-1]!='\n' ) return 0;
953 return 1;
954 }
955
956
--- src/content.c
+++ src/content.c
@@ -946,10 +946,11 @@
946 static int looks_like_control_artifact(Blob *p){
947 const char *z = blob_buffer(p);
948 int n = blob_size(p);
949 if( n<10 ) return 0;
950 if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) return 1;
951 if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) return 1;
952 if( z[0]<'A' || z[0]>'Z' || z[1]!=' ' || z[0]=='I' ) return 0;
953 if( z[n-1]!='\n' ) return 0;
954 return 1;
955 }
956
957
+35
--- src/file.c
+++ src/file.c
@@ -555,10 +555,28 @@
555555
if( fossil_isdirsep(z[0]) ) zTail = &z[1];
556556
z++;
557557
}
558558
return zTail;
559559
}
560
+
561
+/*
562
+** Return the tail of a command: the basename of the putative executable (which
563
+** could be quoted when containing spaces) and the following arguments.
564
+*/
565
+const char *command_tail(const char *z){
566
+ const char *zTail = z;
567
+ char chQuote = 0;
568
+ if( !zTail ) return 0;
569
+ while( z[0] && (!fossil_isspace(z[0]) || chQuote) ){
570
+ if( z[0]=='"' || z[0]=='\'' ){
571
+ chQuote = (chQuote==z[0]) ? 0 : z[0];
572
+ }
573
+ if( fossil_isdirsep(z[0]) ) zTail = &z[1];
574
+ z++;
575
+ }
576
+ return zTail;
577
+}
560578
561579
/*
562580
** Return the directory of a file path name. The directory is all components
563581
** except the last one. For example, the directory of "/a/b/c.d" is "/a/b".
564582
** If there is no directory, NULL is returned; otherwise, the returned memory
@@ -570,10 +588,27 @@
570588
return mprintf("%.*s", (int)(zTail-z-1), z);
571589
}else{
572590
return 0;
573591
}
574592
}
593
+
594
+/*
595
+** Return the basename of the putative executable in a command (w/o arguments).
596
+** The returned memory should be freed via fossil_free().
597
+*/
598
+char *command_basename(const char *z){
599
+ const char *zTail = command_tail(z);
600
+ const char *zEnd = zTail;
601
+ while( zEnd[0] && !fossil_isspace(zEnd[0]) && zEnd[0]!='"' && zEnd[0]!='\'' ){
602
+ zEnd++;
603
+ }
604
+ if( zEnd ){
605
+ return mprintf("%.*s", (int)(zEnd-zTail), zTail);
606
+ }else{
607
+ return 0;
608
+ }
609
+}
575610
576611
/* SQL Function: file_dirname(NAME)
577612
**
578613
** Return the directory for NAME
579614
*/
580615
--- src/file.c
+++ src/file.c
@@ -555,10 +555,28 @@
555 if( fossil_isdirsep(z[0]) ) zTail = &z[1];
556 z++;
557 }
558 return zTail;
559 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
561 /*
562 ** Return the directory of a file path name. The directory is all components
563 ** except the last one. For example, the directory of "/a/b/c.d" is "/a/b".
564 ** If there is no directory, NULL is returned; otherwise, the returned memory
@@ -570,10 +588,27 @@
570 return mprintf("%.*s", (int)(zTail-z-1), z);
571 }else{
572 return 0;
573 }
574 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
576 /* SQL Function: file_dirname(NAME)
577 **
578 ** Return the directory for NAME
579 */
580
--- src/file.c
+++ src/file.c
@@ -555,10 +555,28 @@
555 if( fossil_isdirsep(z[0]) ) zTail = &z[1];
556 z++;
557 }
558 return zTail;
559 }
560
561 /*
562 ** Return the tail of a command: the basename of the putative executable (which
563 ** could be quoted when containing spaces) and the following arguments.
564 */
565 const char *command_tail(const char *z){
566 const char *zTail = z;
567 char chQuote = 0;
568 if( !zTail ) return 0;
569 while( z[0] && (!fossil_isspace(z[0]) || chQuote) ){
570 if( z[0]=='"' || z[0]=='\'' ){
571 chQuote = (chQuote==z[0]) ? 0 : z[0];
572 }
573 if( fossil_isdirsep(z[0]) ) zTail = &z[1];
574 z++;
575 }
576 return zTail;
577 }
578
579 /*
580 ** Return the directory of a file path name. The directory is all components
581 ** except the last one. For example, the directory of "/a/b/c.d" is "/a/b".
582 ** If there is no directory, NULL is returned; otherwise, the returned memory
@@ -570,10 +588,27 @@
588 return mprintf("%.*s", (int)(zTail-z-1), z);
589 }else{
590 return 0;
591 }
592 }
593
594 /*
595 ** Return the basename of the putative executable in a command (w/o arguments).
596 ** The returned memory should be freed via fossil_free().
597 */
598 char *command_basename(const char *z){
599 const char *zTail = command_tail(z);
600 const char *zEnd = zTail;
601 while( zEnd[0] && !fossil_isspace(zEnd[0]) && zEnd[0]!='"' && zEnd[0]!='\'' ){
602 zEnd++;
603 }
604 if( zEnd ){
605 return mprintf("%.*s", (int)(zEnd-zTail), zTail);
606 }else{
607 return 0;
608 }
609 }
610
611 /* SQL Function: file_dirname(NAME)
612 **
613 ** Return the directory for NAME
614 */
615
+7 -3
--- src/manifest.c
+++ src/manifest.c
@@ -318,18 +318,22 @@
318318
*/
319319
static void remove_pgp_signature(const char **pz, int *pn){
320320
const char *z = *pz;
321321
int n = *pn;
322322
int i;
323
- if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
324
- for(i=34; i<n && !after_blank_line(z+i); i++){}
323
+ if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) i = 34;
324
+ else if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) i = 34;
325
+ else return;
326
+ for(; i<n && !after_blank_line(z+i); i++){}
325327
if( i>=n ) return;
326328
z += i;
327329
n -= i;
328330
*pz = z;
329331
for(i=n-1; i>=0; i--){
330
- if( z[i]=='\n' && strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
332
+ if( z[i]=='\n' &&
333
+ (strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-----", 29)==0
334
+ || strncmp(&z[i],"\n-----BEGIN SSH SIGNATURE-----", 29)==0 )){
331335
n = i+1;
332336
break;
333337
}
334338
}
335339
*pn = n;
336340
337341
ADDED www/signing.md
--- src/manifest.c
+++ src/manifest.c
@@ -318,18 +318,22 @@
318 */
319 static void remove_pgp_signature(const char **pz, int *pn){
320 const char *z = *pz;
321 int n = *pn;
322 int i;
323 if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
324 for(i=34; i<n && !after_blank_line(z+i); i++){}
 
 
325 if( i>=n ) return;
326 z += i;
327 n -= i;
328 *pz = z;
329 for(i=n-1; i>=0; i--){
330 if( z[i]=='\n' && strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
 
 
331 n = i+1;
332 break;
333 }
334 }
335 *pn = n;
336
337 DDED www/signing.md
--- src/manifest.c
+++ src/manifest.c
@@ -318,18 +318,22 @@
318 */
319 static void remove_pgp_signature(const char **pz, int *pn){
320 const char *z = *pz;
321 int n = *pn;
322 int i;
323 if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) i = 34;
324 else if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) i = 34;
325 else return;
326 for(; i<n && !after_blank_line(z+i); i++){}
327 if( i>=n ) return;
328 z += i;
329 n -= i;
330 *pz = z;
331 for(i=n-1; i>=0; i--){
332 if( z[i]=='\n' &&
333 (strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-----", 29)==0
334 || strncmp(&z[i],"\n-----BEGIN SSH SIGNATURE-----", 29)==0 )){
335 n = i+1;
336 break;
337 }
338 }
339 *pn = n;
340
341 DDED www/signing.md
--- a/www/signing.md
+++ b/www/signing.md
@@ -0,0 +1,117 @@
1
+# Signing Check-ins
2
+
3
+Fossil can sign check-in manifests. A basic concept in public-key
4
+cryptography, signing can bring some advantages such as authentication and
5
+non-repudiation. In practice, a serious obstacle is the public key
6
+infrastructure – that is, the problem of reliably verifying that a given
7
+public key belongs to its supposed owner (also known as _"signing is easy,
8
+verifying is hard"_).
9
+
10
+Fossil neither creates nor verifies signatures by itself, instead relying on
11
+external tools that have to be installed side-by-side. Historically, the tool
12
+most employed for this task was [GnuPG](https://gnupg.org); recently, there has
13
+been an increase in the usage of [OpenSSH](https://openssh.com) (the minimum
14
+required version is 8.1, released on 2019-10-09).
15
+
16
+## Signing a check-in
17
+
18
+The `clearsign` setting must be on; this will cause every check-in to be signed
19
+(unless you provide the `--nosign` flag to `fossil commit`). To this end,
20
+Fossil calls the command given by the `pgp-command` setting.
21
+
22
+Fossil needs a non-detached signature that includes the rest of the usual
23
+manifest. For GnuPG, this is no problem, but as of 2025 (version 9.9p1) OpenSSH
24
+can create **and verify** only detached signatures; Fossil itself must
25
+attach this signature to the manifest prior to committing. This makes the
26
+verification more complex, as additional steps are needed to extract the
27
+signature and feed it into OpenSSH.
28
+
29
+### GnuPG
30
+
31
+The `pgp-command` setting defaults to
32
+`gpg --clearsign -o`.
33
+(A possible interesting option to `gpg --clearsign` is `-u`, to specify the
34
+user to be used for signing.)
35
+
36
+### OpenSSH
37
+
38
+A reasonable value for `pgp-command` is
39
+
40
+```
41
+ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519
42
+```
43
+
44
+for Linux, and
45
+
46
+```
47
+ssh-keygen -q -Y sign -n fossilscm -f %USERPROFILE%/.ssh/id_ed25519
48
+```
49
+
50
+for Windows, changing as appropriate `-f` to the path of the private key to be
51
+used.
52
+
53
+The value for `-n` (the _namespace_) can be changed at will, but care has to be
54
+taken to use the same value whenbs, not Fossil
55
+artifacts.
56
+
57
+
58
+## Verifying a signature
59
+
60
+Fossil does not provide an internal method for verifying signatures and
61
+relies – like it does for sig # Signing Check-ins
62
+
63
+Fossil can sign check-in manifests. A basic concept in public-key
64
+cryptography, signing can bring some advantages such as authentication and
65
+non-repudiation. In practice, a serious obstacle is the public key
66
+infrastructure – that is, the problem of reliably verifying that a given
67
+public key belongs to its supposed owner (also known as _"signing is easy,
68
+verifying is hard"_).
69
+
70
+Fossil neither creates nor verifies signatures by itself, instead relying on
71
+external tools that have to be installed side-by-side. Historically, the tool
72
+most employed for this task was [GnuPG](https://gnupg.org); recently, there has
73
+been an increase in the usage of [OpenSSH](https://openssh.com) (the minimum
74
+required version is 8.1, released on 2019-10-09).
75
+
76
+## Signing a check-in
77
+
78
+The `clearsign` setting must be on; this will cause every check-in to be signed
79
+(unless you provide the `--nosign` flag to `fossil commit`). To this end,
80
+Fossil calls the command given by the `pgp-command` setting.
81
+
82
+Fossil needs a non-detached signature that includes the rest of the usual
83
+manifest. For GnuPG, this is no problem, but as of 2025 (version 9.9p1) OpenSSH
84
+can create **and verify** only detached signatures; Fossil itself must
85
+attach this signature to the manifest prior to committing. This makes the
86
+verification more complex, as additional steps are needed to extract the
87
+signature and feed it into OpenSSH.
88
+
89
+### GnuPG
90
+
91
+The `pgp-command` setting defaults to
92
+`gpg --clearsign -o`.
93
+(A possible interesting option to `gpg --clearsign` is `-u`, to specify the
94
+user to be used for signing.)
95
+
96
+### OpenSSH
97
+
98
+A reasonable value for `pgp-command` is
99
+
100
+```
101
+ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519
102
+```
103
+
104
+for Linux, and
105
+
106
+```
107
+ssh-keygen -q -Y sign -n fossilscm -f %USERPROFILE%/.ssh/id_ed25519
108
+```
109
+
110
+for Windows, changing as appropriate `-f` to the path of the private key to be
111
+used.
112
+
113
+The value for `-n` (the _namespace_) can be changed at will, but care has to be
114
+taken to use the same value when verifying the signature.
115
+
116
+Fossil versions prior to 2.26 do not understand SSH signatures and
117
+will treat artifacts signed this l can sign check-in manifes
--- a/www/signing.md
+++ b/www/signing.md
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/www/signing.md
+++ b/www/signing.md
@@ -0,0 +1,117 @@
1 # Signing Check-ins
2
3 Fossil can sign check-in manifests. A basic concept in public-key
4 cryptography, signing can bring some advantages such as authentication and
5 non-repudiation. In practice, a serious obstacle is the public key
6 infrastructure – that is, the problem of reliably verifying that a given
7 public key belongs to its supposed owner (also known as _"signing is easy,
8 verifying is hard"_).
9
10 Fossil neither creates nor verifies signatures by itself, instead relying on
11 external tools that have to be installed side-by-side. Historically, the tool
12 most employed for this task was [GnuPG](https://gnupg.org); recently, there has
13 been an increase in the usage of [OpenSSH](https://openssh.com) (the minimum
14 required version is 8.1, released on 2019-10-09).
15
16 ## Signing a check-in
17
18 The `clearsign` setting must be on; this will cause every check-in to be signed
19 (unless you provide the `--nosign` flag to `fossil commit`). To this end,
20 Fossil calls the command given by the `pgp-command` setting.
21
22 Fossil needs a non-detached signature that includes the rest of the usual
23 manifest. For GnuPG, this is no problem, but as of 2025 (version 9.9p1) OpenSSH
24 can create **and verify** only detached signatures; Fossil itself must
25 attach this signature to the manifest prior to committing. This makes the
26 verification more complex, as additional steps are needed to extract the
27 signature and feed it into OpenSSH.
28
29 ### GnuPG
30
31 The `pgp-command` setting defaults to
32 `gpg --clearsign -o`.
33 (A possible interesting option to `gpg --clearsign` is `-u`, to specify the
34 user to be used for signing.)
35
36 ### OpenSSH
37
38 A reasonable value for `pgp-command` is
39
40 ```
41 ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519
42 ```
43
44 for Linux, and
45
46 ```
47 ssh-keygen -q -Y sign -n fossilscm -f %USERPROFILE%/.ssh/id_ed25519
48 ```
49
50 for Windows, changing as appropriate `-f` to the path of the private key to be
51 used.
52
53 The value for `-n` (the _namespace_) can be changed at will, but care has to be
54 taken to use the same value whenbs, not Fossil
55 artifacts.
56
57
58 ## Verifying a signature
59
60 Fossil does not provide an internal method for verifying signatures and
61 relies – like it does for sig # Signing Check-ins
62
63 Fossil can sign check-in manifests. A basic concept in public-key
64 cryptography, signing can bring some advantages such as authentication and
65 non-repudiation. In practice, a serious obstacle is the public key
66 infrastructure – that is, the problem of reliably verifying that a given
67 public key belongs to its supposed owner (also known as _"signing is easy,
68 verifying is hard"_).
69
70 Fossil neither creates nor verifies signatures by itself, instead relying on
71 external tools that have to be installed side-by-side. Historically, the tool
72 most employed for this task was [GnuPG](https://gnupg.org); recently, there has
73 been an increase in the usage of [OpenSSH](https://openssh.com) (the minimum
74 required version is 8.1, released on 2019-10-09).
75
76 ## Signing a check-in
77
78 The `clearsign` setting must be on; this will cause every check-in to be signed
79 (unless you provide the `--nosign` flag to `fossil commit`). To this end,
80 Fossil calls the command given by the `pgp-command` setting.
81
82 Fossil needs a non-detached signature that includes the rest of the usual
83 manifest. For GnuPG, this is no problem, but as of 2025 (version 9.9p1) OpenSSH
84 can create **and verify** only detached signatures; Fossil itself must
85 attach this signature to the manifest prior to committing. This makes the
86 verification more complex, as additional steps are needed to extract the
87 signature and feed it into OpenSSH.
88
89 ### GnuPG
90
91 The `pgp-command` setting defaults to
92 `gpg --clearsign -o`.
93 (A possible interesting option to `gpg --clearsign` is `-u`, to specify the
94 user to be used for signing.)
95
96 ### OpenSSH
97
98 A reasonable value for `pgp-command` is
99
100 ```
101 ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519
102 ```
103
104 for Linux, and
105
106 ```
107 ssh-keygen -q -Y sign -n fossilscm -f %USERPROFILE%/.ssh/id_ed25519
108 ```
109
110 for Windows, changing as appropriate `-f` to the path of the private key to be
111 used.
112
113 The value for `-n` (the _namespace_) can be changed at will, but care has to be
114 taken to use the same value when verifying the signature.
115
116 Fossil versions prior to 2.26 do not understand SSH signatures and
117 will treat artifacts signed this l can sign check-in manifes

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button