mirror of
https://github.com/soypat/lneto.git
synced 2026-08-19 06:04:01 +00:00
284 lines
7.7 KiB
Go
284 lines
7.7 KiB
Go
package tls_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/soypat/lneto"
|
|
"github.com/soypat/lneto/x/tls"
|
|
)
|
|
|
|
func TestExtensionListWalksAndToleratesGREASE(t *testing.T) {
|
|
// Two extensions: a GREASE type with empty data, then supported_versions.
|
|
exts := []byte{
|
|
0x0a, 0x0a, 0x00, 0x00, // GREASE, len 0
|
|
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, // supported_versions
|
|
}
|
|
list, err := tls.ParseClientExtensions(exts, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var types []tls.ExtensionType
|
|
for off, ext := range list.All {
|
|
if want := 4*len(types) + 4; off != want {
|
|
t.Errorf("%v at offset %d want %d", ext.Type(), off, want)
|
|
}
|
|
types = append(types, ext.Type())
|
|
}
|
|
if len(types) != 2 || types[1] != tls.ExtSupportedVersions {
|
|
t.Fatalf("got %v", types)
|
|
}
|
|
if !tls.IsGREASE(uint16(types[0])) {
|
|
t.Errorf("first extension %#x not recognized as GREASE", types[0])
|
|
}
|
|
}
|
|
|
|
func TestParseExtensionsTruncated(t *testing.T) {
|
|
for _, tc := range [][]byte{
|
|
{0x00}, // partial type
|
|
{0x00, 0x2b, 0x00}, // partial length
|
|
{0x00, 0x2b, 0x00, 0x05, 0x02, 0x03}, // length overruns
|
|
} {
|
|
_, err := tls.ParseClientExtensions(tc, 0)
|
|
if !errors.Is(err, lneto.ErrTruncatedFrame) {
|
|
t.Errorf("% x: got %v want ErrTruncatedFrame", tc, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseExtensionsRejectsMalformedInnerList(t *testing.T) {
|
|
// Framing inside a recognized extension is checked at parse, which is what
|
|
// lets the iterators be error-free.
|
|
for _, tc := range [][]byte{
|
|
{0x00, 0x0a, 0x00, 0x03, 0x00, 0x01, 0x1d}, // supported_groups, odd list
|
|
{0x00, 0x2b, 0x00, 0x03, 0x04, 0x03, 0x04}, // supported_versions, prefix overruns
|
|
{0x00, 0x10, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00}, // alpn, zero-length name
|
|
{0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x09}, // server_name, name overruns
|
|
} {
|
|
if _, err := tls.ParseClientExtensions(tc, 0); err == nil {
|
|
t.Errorf("% x accepted", tc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtensionIterationStopsOnBreak(t *testing.T) {
|
|
// Two extensions with no inner structure of their own.
|
|
exts := []byte{0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00}
|
|
list, err := tls.ParseClientExtensions(exts, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n := 0
|
|
for range list.All {
|
|
n++
|
|
break
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("walk continued after break: %d iterations", n)
|
|
}
|
|
}
|
|
|
|
func TestKeySharesAcceptGREASEEntry(t *testing.T) {
|
|
// Chrome sends a GREASE key share whose key_exchange is a single byte.
|
|
// Rejecting it as malformed breaks Chrome outright.
|
|
exts := []byte{
|
|
0x00, 0x33, 0x00, 0x0d, // key_share, 13 bytes
|
|
0x00, 0x0b, // client_shares length 11
|
|
0x1a, 0x1a, 0x00, 0x01, 0x00, // GREASE group, 1 byte body
|
|
0x00, 0x1d, 0x00, 0x02, 0xab, 0xcd, // x25519, 2 byte body
|
|
}
|
|
list, err := tls.ParseClientExtensions(exts, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
type share struct {
|
|
g tls.NamedGroup
|
|
n int
|
|
}
|
|
var got []share
|
|
for _, ext := range list.All {
|
|
for _, ks := range ext.KeyShares {
|
|
got = append(got, share{ks.Group, len(ks.Key)})
|
|
}
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("got %d shares want 2", len(got))
|
|
}
|
|
if !tls.IsGREASE(uint16(got[0].g)) || got[0].n != 1 {
|
|
t.Errorf("GREASE share mishandled: %+v", got[0])
|
|
}
|
|
if got[1].g != tls.GroupX25519 || got[1].n != 2 {
|
|
t.Errorf("x25519 share mishandled: %+v", got[1])
|
|
}
|
|
}
|
|
|
|
func TestServerKeyShareHelloRetryRequestForm(t *testing.T) {
|
|
// A HelloRetryRequest names a group with no key.
|
|
exts := []byte{0x00, 0x33, 0x00, 0x02, 0x00, 0x1d}
|
|
list, err := tls.ParseServerExtensions(exts, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n := 0
|
|
for _, ext := range list.All {
|
|
for _, ks := range ext.KeyShares {
|
|
n++
|
|
if ks.Group != tls.GroupX25519 || len(ks.Key) != 0 {
|
|
t.Errorf("got %v with %d key bytes", ks.Group, len(ks.Key))
|
|
}
|
|
}
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("walked %d shares want 1", n)
|
|
}
|
|
}
|
|
|
|
func TestALPNProtos(t *testing.T) {
|
|
exts := []byte{
|
|
0x00, 0x10, 0x00, 0x0e,
|
|
0x00, 0x0c,
|
|
0x02, 'h', '2',
|
|
0x08, 'h', 't', 't', 'p', '/', '1', '.', '1',
|
|
}
|
|
list, err := tls.ParseClientExtensions(exts, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var names []string
|
|
for _, ext := range list.All {
|
|
for _, p := range ext.ALPNProtos {
|
|
names = append(names, string(p))
|
|
}
|
|
}
|
|
if len(names) != 2 || names[0] != "h2" || names[1] != "http/1.1" {
|
|
t.Errorf("got %q", names)
|
|
}
|
|
}
|
|
|
|
func TestSupportedVersionsClientAndServerForms(t *testing.T) {
|
|
// The ClientHello form is a list behind a one-byte prefix; the ServerHello
|
|
// form is a bare uint16.
|
|
client := []byte{0x00, 0x2b, 0x00, 0x05, 0x04, 0x1a, 0x1a, 0x03, 0x04}
|
|
list, err := tls.ParseClientExtensions(client, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var vers []uint16
|
|
for _, ext := range list.All {
|
|
for _, v := range ext.SupportedVersions {
|
|
vers = append(vers, v)
|
|
}
|
|
}
|
|
if len(vers) != 2 || vers[1] != tls.VersionTLS13 {
|
|
t.Errorf("client form got %#x", vers)
|
|
}
|
|
|
|
server := []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
|
|
list, err = tls.ParseServerExtensions(server, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vers = vers[:0]
|
|
for _, ext := range list.All {
|
|
for _, v := range ext.SupportedVersions {
|
|
vers = append(vers, v)
|
|
}
|
|
}
|
|
if len(vers) != 1 || vers[0] != tls.VersionTLS13 {
|
|
t.Errorf("server form got %#x", vers)
|
|
}
|
|
}
|
|
|
|
func TestVectorRejectsTrailingBytes(t *testing.T) {
|
|
// A prefix that under-describes its buffer leaves bytes whose meaning this
|
|
// parser and a middlebox could disagree about.
|
|
exts := []byte{0x00, 0x0a, 0x00, 0x05, 0x00, 0x02, 0x00, 0x1d, 0xff}
|
|
if _, err := tls.ParseClientExtensions(exts, 0); err == nil {
|
|
t.Error("trailing bytes after vector accepted")
|
|
}
|
|
}
|
|
|
|
func TestSubIteratorOffsetsAreBodyRelative(t *testing.T) {
|
|
// base threads through every nesting level so a decoder adds no arithmetic.
|
|
const base = 100
|
|
exts := []byte{
|
|
0x00, 0x00, 0x00, 0x0b, // server_name, 11 bytes
|
|
0x00, 0x09, // list length
|
|
0x00, 0x00, 0x06, // host_name, 6 bytes
|
|
'a', '.', 'c', 'o', 'm', '!',
|
|
}
|
|
list, err := tls.ParseClientExtensions(exts, base)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for off, ext := range list.All {
|
|
if off != base+4 {
|
|
t.Errorf("extension data at %d want %d", off, base+4)
|
|
}
|
|
for noff, name := range ext.ServerNames {
|
|
// 4 extension header + 2 list length + 3 entry header
|
|
if want := base + 9; noff != want {
|
|
t.Errorf("name at %d want %d", noff, want)
|
|
}
|
|
if string(name.Name) != "a.com!" {
|
|
t.Errorf("name %q", name.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzParseClientExtensions(f *testing.F) {
|
|
f.Add([]byte{0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04})
|
|
f.Add([]byte{0x0a, 0x0a, 0x00, 0x00})
|
|
f.Fuzz(func(t *testing.T, b []byte) {
|
|
list, err := tls.ParseClientExtensions(b, 0)
|
|
if err != nil {
|
|
return
|
|
}
|
|
total := 0
|
|
for off, ext := range list.All {
|
|
total += 4 + len(ext.Data())
|
|
if total > len(b) {
|
|
t.Fatalf("walked %d bytes past input length %d", total, len(b))
|
|
}
|
|
if off+len(ext.Data()) > len(b) {
|
|
t.Fatalf("extension data at %d overruns %d byte input", off, len(b))
|
|
}
|
|
// No sub-iterator may escape its slice, whatever the type says.
|
|
for o, ks := range ext.KeyShares {
|
|
if o+len(ks.Key) > len(b) {
|
|
t.Fatalf("key share at %d overruns input", o)
|
|
}
|
|
}
|
|
for o, name := range ext.ServerNames {
|
|
if o+len(name.Name) > len(b) {
|
|
t.Fatalf("server name at %d overruns input", o)
|
|
}
|
|
}
|
|
for o, p := range ext.ALPNProtos {
|
|
if o+len(p) > len(b) {
|
|
t.Fatalf("alpn name at %d overruns input", o)
|
|
}
|
|
}
|
|
for o := range ext.SupportedVersions {
|
|
if o+2 > len(b) {
|
|
t.Fatalf("version at %d overruns input", o)
|
|
}
|
|
}
|
|
for o := range ext.SupportedGroups {
|
|
if o+2 > len(b) {
|
|
t.Fatalf("group at %d overruns input", o)
|
|
}
|
|
}
|
|
for o := range ext.SignatureSchemes {
|
|
if o+2 > len(b) {
|
|
t.Fatalf("scheme at %d overruns input", o)
|
|
}
|
|
}
|
|
}
|
|
if total != len(b) {
|
|
t.Fatalf("clean walk consumed %d of %d bytes", total, len(b))
|
|
}
|
|
})
|
|
}
|