Skip to content

Commit 9cd4c3a

Browse files
committed
feat(clearsign): Write complete legacy hash header
1 parent 244eb1c commit 9cd4c3a

File tree

2 files changed

+94
-13
lines changed

2 files changed

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

0 commit comments

Comments
 (0)