Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions pgp/yubikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,15 @@ func buildSignaturePacket(signedContent []byte, signer crypto.Signer, pubKey *pa
// Wrap in an OpenPGP packet (new-format header)
var pkt bytes.Buffer
bodyBytes := body.Bytes()
pkt.WriteByte(0xC2) // new-format packet tag for signature (type 2)
writeNewFormatLength(&pkt, len(bodyBytes))
pkt.Write(bodyBytes)
if err := pkt.WriteByte(0xC2); err != nil { // new-format packet tag for signature (type 2)
return nil, fmt.Errorf("failed to write OpenPGP signature packet tag: %w", err)
}
if err := writeNewFormatLength(&pkt, len(bodyBytes)); err != nil {
return nil, err
}
if _, err := pkt.Write(bodyBytes); err != nil {
return nil, fmt.Errorf("failed to write OpenPGP signature packet body: %w", err)
}

return pkt.Bytes(), nil
}
Expand Down Expand Up @@ -409,20 +415,26 @@ func bitLength(b byte) int {
return n
}

func writeOpenPGPPacketLengthBytes(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return fmt.Errorf("failed to write OpenPGP packet length: %w", err)
}
return nil
}

// writeNewFormatLength writes an OpenPGP new-format packet body length.
func writeNewFormatLength(w *bytes.Buffer, length int) {
func writeNewFormatLength(w io.Writer, length int) error {
if length < 192 {
w.WriteByte(byte(length))
return writeOpenPGPPacketLengthBytes(w, []byte{byte(length)})
} else if length < 8384 {
length -= 192
w.WriteByte(byte(length>>8) + 192)
w.WriteByte(byte(length))
} else {
w.WriteByte(255)
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(length))
w.Write(buf)
return writeOpenPGPPacketLengthBytes(w, []byte{byte(length>>8) + 192, byte(length)})
}

buf := make([]byte, 5)
buf[0] = 255
binary.BigEndian.PutUint32(buf[1:], uint32(length))
return writeOpenPGPPacketLengthBytes(w, buf)
}

// parseASN1Signature extracts r and s from an ASN.1 DER encoded ECDSA signature.
Expand Down
44 changes: 44 additions & 0 deletions pgp/yubikey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ package pgp

import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"testing"
)

type failingLengthWriter struct{}

func (failingLengthWriter) Write(_ []byte) (int, error) {
return 0, errors.New("simulated length write failure")
}

// TestParseASN1Signature_TruncatedDoesNotPanic covers the bounds-check path
// added for #613. Each input would have panicked in the original parser
// with "index out of range"; here we expect a typed error instead.
Expand Down Expand Up @@ -110,3 +118,39 @@ func TestGenerateMIMEBoundaryFallsBackToUnixNano(t *testing.T) {
t.Fatalf("fallback boundary suffix is not a UnixNano timestamp: %v", err)
}
}

func TestWriteNewFormatLengthEncodings(t *testing.T) {
cases := []struct {
length int
want []byte
}{
{length: 191, want: []byte{0xbf}},
{length: 192, want: []byte{0xc0, 0x00}},
{length: 8383, want: []byte{0xdf, 0xff}},
{length: 8384, want: []byte{0xff, 0x00, 0x00, 0x20, 0xc0}},
}

for _, tc := range cases {
t.Run(fmt.Sprint(tc.length), func(t *testing.T) {
var got strings.Builder
if err := writeNewFormatLength(&got, tc.length); err != nil {
t.Fatalf("writeNewFormatLength() returned error: %v", err)
}
if string(tc.want) != got.String() {
t.Fatalf("writeNewFormatLength(%d) = %x, want %x", tc.length, got.String(), tc.want)
}
})
}
}

func TestWriteNewFormatLengthPropagatesWriteError(t *testing.T) {
err := writeNewFormatLength(failingLengthWriter{}, 8384)
if err == nil {
t.Fatal("expected write error, got nil")
}
if !strings.Contains(err.Error(), "OpenPGP packet length") {
t.Fatalf("expected length write context, got %v", err)
}
}

var _ io.Writer = failingLengthWriter{}
Loading