1package pgp
2
3import (
4 "errors"
5 "strconv"
6 "strings"
7 "testing"
8)
9
10// TestParseASN1Signature_TruncatedDoesNotPanic covers the bounds-check path
11// added for #613. Each input would have panicked in the original parser
12// with "index out of range"; here we expect a typed error instead.
13func TestParseASN1Signature_TruncatedDoesNotPanic(t *testing.T) {
14 cases := []struct {
15 name string
16 der []byte
17 wantErr string
18 }{
19 {
20 // Length byte declares 0x10 bytes of R but only 1 byte follows.
21 name: "R length overruns buffer",
22 der: []byte{0x30, 0x06, 0x02, 0x10, 0xAA, 0x00},
23 wantErr: "R length overflow",
24 },
25 {
26 // Length byte declares 0x10 bytes of S but only 1 byte follows.
27 name: "S length overruns buffer",
28 der: []byte{0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x10, 0xAA},
29 wantErr: "S length overflow",
30 },
31 {
32 // Valid R, then no S block at all.
33 name: "missing S after R",
34 der: []byte{0x30, 0x06, 0x02, 0x01, 0x01, 0x00},
35 wantErr: "expected INTEGER tag for S",
36 },
37 }
38
39 for _, tc := range cases {
40 tc := tc
41 t.Run(tc.name, func(t *testing.T) {
42 // The test must not panic: the fix replaces panics with errors.
43 defer func() {
44 if r := recover(); r != nil {
45 t.Fatalf("parseASN1Signature panicked: %v", r)
46 }
47 }()
48 _, _, err := parseASN1Signature(tc.der)
49 if err == nil {
50 t.Fatalf("want error, got nil")
51 }
52 if !strings.Contains(err.Error(), tc.wantErr) {
53 t.Fatalf("error = %q, want it to mention %q", err.Error(), tc.wantErr)
54 }
55 })
56 }
57}
58
59// TestParseASN1Signature_WellFormed guards against regressions in the
60// happy path: a minimal SEQUENCE { INTEGER, INTEGER } must still decode
61// to the original r and s bytes.
62func TestParseASN1Signature_WellFormed(t *testing.T) {
63 // SEQUENCE (6 bytes) { INTEGER 0x01, INTEGER 0x02 }
64 der := []byte{0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02}
65
66 r, s, err := parseASN1Signature(der)
67 if err != nil {
68 t.Fatalf("unexpected error: %v", err)
69 }
70 if len(r) != 1 || r[0] != 0x01 {
71 t.Errorf("r = %x, want 01", r)
72 }
73 if len(s) != 1 || s[0] != 0x02 {
74 t.Errorf("s = %x, want 02", s)
75 }
76}
77
78func TestGenerateMIMEBoundaryUsesCryptoRandomBytes(t *testing.T) {
79 oldRandRead := randRead
80 defer func() { randRead = oldRandRead }()
81
82 randRead = func(p []byte) (int, error) {
83 for i := range p {
84 p[i] = byte(i)
85 }
86 return len(p), nil
87 }
88
89 got := generateMIMEBoundary()
90 want := "----=_Part_000102030405060708090a0b0c0d0e0f"
91 if got != want {
92 t.Fatalf("boundary = %q, want %q", got, want)
93 }
94}
95
96func TestGenerateMIMEBoundaryFallsBackToUnixNano(t *testing.T) {
97 oldRandRead := randRead
98 defer func() { randRead = oldRandRead }()
99
100 randRead = func(_ []byte) (int, error) {
101 return 0, errors.New("random source unavailable")
102 }
103
104 const prefix = "----=_Part_"
105 got := generateMIMEBoundary()
106 if !strings.HasPrefix(got, prefix) {
107 t.Fatalf("boundary = %q, want prefix %q", got, prefix)
108 }
109 if _, err := strconv.ParseInt(strings.TrimPrefix(got, prefix), 10, 64); err != nil {
110 t.Fatalf("fallback boundary suffix is not a UnixNano timestamp: %v", err)
111 }
112}