Skip to content

Commit 31919f1

Browse files
committed
feat(clearsign): Write complete legacy hash header
1 parent f72353f commit 31919f1

File tree

2 files changed

+95
-13
lines changed

2 files changed

+95
-13
lines changed

openpgp/clearsign/clearsign.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,6 @@ func EncodeMultiWithHeader(w io.Writer, privateKeys []*packet.PrivateKey, config
390390
}
391391

392392
hashType := config.Hash()
393-
name := nameOfHash(hashType)
394-
if len(name) == 0 {
395-
return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
396-
}
397-
398-
if !hashType.Available() {
399-
return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
400-
}
401393

402394
var hashers []hash.Hash
403395
var hashTypes []crypto.Hash
@@ -444,11 +436,8 @@ func EncodeMultiWithHeader(w io.Writer, privateKeys []*packet.PrivateKey, config
444436
nonV6 := len(salts) < len(hashers)
445437
// Crypto refresh: Headers SHOULD NOT be emitted
446438
if nonV6 { // Emit header if non v6 signatures are present for compatibility
447-
if _, err = buffered.WriteString(fmt.Sprintf("%s: %s", hashHeader, name)); err != nil {
448-
return
449-
}
450-
if err = buffered.WriteByte(lf); err != nil {
451-
return
439+
if err := writeHashHeader(buffered, hashTypes); err != nil {
440+
return nil, err
452441
}
453442
}
454443
if err = buffered.WriteByte(lf); err != nil {
@@ -482,6 +471,40 @@ func (b *Block) VerifySignature(keyring openpgp.KeyRing, config *packet.Config)
482471
return
483472
}
484473

474+
// writeHashHeader writes the legacy cleartext hash header to buffered.
475+
func writeHashHeader(buffered *bufio.Writer, hashTypes []crypto.Hash) error {
476+
seen := make(map[string]bool)
477+
if _, err := buffered.WriteString(fmt.Sprintf("%s: ", hashHeader)); err != nil {
478+
return err
479+
}
480+
481+
for index, sigHashType := range hashTypes {
482+
first := index == 0
483+
name := nameOfHash(sigHashType)
484+
if len(name) == 0 {
485+
return errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(sigHashType)))
486+
}
487+
488+
switch {
489+
case !seen[name] && first:
490+
if _, err := buffered.WriteString(name); err != nil {
491+
return err
492+
}
493+
case !seen[name]:
494+
if _, err := buffered.WriteString(fmt.Sprintf(",%s", name)); err != nil {
495+
return err
496+
}
497+
}
498+
seen[name] = true
499+
}
500+
501+
if err := buffered.WriteByte(lf); err != nil {
502+
return err
503+
}
504+
505+
return nil
506+
}
507+
485508
// nameOfHash returns the OpenPGP name for the given hash, or the empty string
486509
// if the name isn't known. See RFC 4880, section 9.4.
487510
func nameOfHash(h crypto.Hash) string {

openpgp/clearsign/clearsign_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package clearsign
66

77
import (
8+
"bufio"
89
"bytes"
910
"crypto"
1011
"fmt"
@@ -171,6 +172,64 @@ func TestSigningInterop(t *testing.T) {
171172
}
172173
}
173174

175+
func TestHashHeader(t *testing.T) {
176+
tests := []struct {
177+
name string
178+
hashTypes []crypto.Hash
179+
expected string
180+
wantErr bool
181+
}{
182+
{
183+
name: "unique hashes",
184+
hashTypes: []crypto.Hash{
185+
crypto.SHA256,
186+
crypto.SHA512,
187+
crypto.SHA3_512,
188+
},
189+
expected: "Hash: SHA256,SHA512,SHA3-512\n",
190+
},
191+
{
192+
name: "with duplicates",
193+
hashTypes: []crypto.Hash{
194+
crypto.SHA256,
195+
crypto.SHA512,
196+
crypto.SHA512,
197+
crypto.SHA3_512,
198+
},
199+
expected: "Hash: SHA256,SHA512,SHA3-512\n",
200+
},
201+
{
202+
name: "with duplicates",
203+
hashTypes: []crypto.Hash{
204+
crypto.SHA256,
205+
crypto.SHA256,
206+
crypto.SHA256,
207+
crypto.SHA256,
208+
},
209+
expected: "Hash: SHA256\n",
210+
},
211+
}
212+
213+
for _, tc := range tests {
214+
t.Run(tc.name, func(t *testing.T) {
215+
var buf bytes.Buffer
216+
writer := bufio.NewWriter(&buf)
217+
if err := writeHashHeader(writer, tc.hashTypes); err != nil {
218+
t.Fatalf("unexpected error: %v", err)
219+
}
220+
221+
if err := writer.Flush(); err != nil {
222+
t.Fatalf("flush failed: %v", err)
223+
}
224+
225+
actual := buf.String()
226+
if actual != tc.expected {
227+
t.Errorf("output mismatch:\nExpected: %q\nActual: %q", tc.expected, actual)
228+
}
229+
})
230+
}
231+
}
232+
174233
func testMultiSign(t *testing.T, v6 bool) {
175234
if testing.Short() {
176235
t.Skip("skipping long test in -short mode")

0 commit comments

Comments
 (0)