Skip to content
Draft
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
25 changes: 22 additions & 3 deletions loader/goroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
}

// Find the overrides needed for the goroot.
overrides := pathsToOverride(config.GoMinorVersion, needsSyscallPackage(config.BuildTags()))
overrides := pathsToOverride(config.GoMinorVersion, needsSyscallPackage(config.BuildTags()), needsTLSStubPackage(config.GOOS(), config.BuildTags()))

// Resolve the merge links within the goroot.
merge, err := listGorootMergeLinks(goroot, tinygoroot, overrides)
Expand Down Expand Up @@ -225,14 +225,27 @@ func needsSyscallPackage(buildTags []string) bool {
return false
}

// Keep the netdev TLS wrapper except on hosted Linux and Darwin.
// The baremetal tag is needed because those targets also report GOOS=linux.
func needsTLSStubPackage(goos string, buildTags []string) bool {
if goos != "linux" && goos != "darwin" {
return true
}
for _, tag := range buildTags {
if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" || tag == "wasm_unknown" {
return true
}
}
return false
}

// The boolean indicates whether to merge the subdirs. True means merge, false
// means use the TinyGo version.
func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
func pathsToOverride(goMinor int, needsSyscallPackage, needsTLSStubPackage bool) map[string]bool {
paths := map[string]bool{
"": true,
"crypto/": true,
"crypto/rand/": false,
"crypto/tls/": false,
"crypto/x509/": true,
"crypto/x509/internal/": true,
"crypto/x509/internal/macos/": false,
Expand Down Expand Up @@ -263,6 +276,12 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"unique/": false,
}

if needsTLSStubPackage {
// Without this entry crypto/tls falls under the "crypto/" merge above,
// which links in the package of the standard library.
paths["crypto/tls/"] = false
}

if goMinor >= 19 {
paths["crypto/internal/"] = true
paths["crypto/internal/boring/"] = true
Expand Down
28 changes: 28 additions & 0 deletions loader/goroot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package loader

import "testing"

func TestNeedsTLSStubPackage(t *testing.T) {
tests := []struct {
name string
goos string
buildTags []string
want bool
}{
{"hosted linux", "linux", []string{"linux", "amd64"}, false},
{"hosted darwin", "darwin", []string{"darwin", "arm64"}, false},
{"windows", "windows", []string{"windows", "amd64"}, true},
{"wasip1", "wasip1", []string{"wasip1", "tinygo.wasm"}, true},
{"wasip2", "wasip2", []string{"wasip2", "tinygo.wasm"}, true},
// A baremetal target reports GOOS=linux, so the build tags have to
// keep the stub for it.
{"baremetal", "linux", []string{"linux", "arm", "baremetal"}, true},
{"nintendoswitch", "linux", []string{"linux", "nintendoswitch"}, true},
{"wasm_unknown", "linux", []string{"linux", "wasm_unknown"}, true},
}
for _, test := range tests {
if got := needsTLSStubPackage(test.goos, test.buildTags); got != test.want {
t.Errorf("%s: wanted %v, got %v", test.name, test.want, got)
}
}
}
2 changes: 1 addition & 1 deletion loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (p *Program) getOriginalPath(path string) string {
originalPath = realgorootPath
}
maybeInTinyGoRoot := false
for prefix := range pathsToOverride(p.config.GoMinorVersion, needsSyscallPackage(p.config.BuildTags())) {
for prefix := range pathsToOverride(p.config.GoMinorVersion, needsSyscallPackage(p.config.BuildTags()), needsTLSStubPackage(p.config.GOOS(), p.config.BuildTags())) {
if runtime.GOOS == "windows" {
prefix = strings.ReplaceAll(prefix, "/", "\\")
}
Expand Down
13 changes: 13 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ func TestTimerStopResetRace(t *testing.T) {
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}

// TestHostCryptoTLS checks that a hosted target gets the real crypto/tls and
// not the stub, whose handshake does nothing. Only linux and macOS do.
func TestHostCryptoTLS(t *testing.T) {
t.Parallel()

switch runtime.GOOS {
case "darwin", "linux":
default:
t.Skipf("host GOOS %s keeps the crypto/tls stub", runtime.GOOS)
}
runTest("hostcryptotls.go", optionsFromTarget("", sema), t, nil, nil)
}

func TestESP32QEMU(t *testing.T) {
t.Parallel()

Expand Down
100 changes: 100 additions & 0 deletions testdata/hostcryptotls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

// A real TLS handshake over an in-memory pipe. The stub crypto/tls has a
// handshake that does nothing, so it cannot pass this.

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"io"
"math/big"
"net"
"time"
)

func main() {
cert, pool := selfSigned()

// A client that trusts the certificate completes the handshake and
// exchanges data.
client, server := net.Pipe()
go serve(server, cert)
conn := tls.Client(client, &tls.Config{RootCAs: pool, ServerName: "tinygo.test"})
if err := conn.Handshake(); err != nil {
println("handshake failed:", err.Error())
return
}
if v := conn.ConnectionState().Version; v < tls.VersionTLS12 {
println("negotiated an unexpected version:", v)
return
}
if _, err := conn.Write([]byte("ping")); err != nil {
println("write failed:", err.Error())
return
}
buf := make([]byte, 4)
if _, err := io.ReadFull(conn, buf); err != nil {
println("read failed:", err.Error())
return
}
println("got:", string(buf))
conn.Close()

// A client that does not trust the certificate must refuse it.
client, server = net.Pipe()
go serve(server, cert)
conn = tls.Client(client, &tls.Config{ServerName: "tinygo.test"})
if err := conn.Handshake(); err == nil {
println("an unknown certificate was accepted")
return
}
conn.Close()
println("unknown certificate refused")
}

func serve(conn net.Conn, cert tls.Certificate) {
server := tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{cert}})
if err := server.Handshake(); err != nil {
conn.Close()
return
}
buf := make([]byte, 4)
if _, err := io.ReadFull(server, buf); err != nil {
server.Close()
return
}
server.Write([]byte("pong"))
}

func selfSigned() (tls.Certificate, *x509.CertPool) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "tinygo.test"},
DNSNames: []string{"tinygo.test"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
panic(err)
}
leaf, err := x509.ParseCertificate(der)
if err != nil {
panic(err)
}
pool := x509.NewCertPool()
pool.AddCert(leaf)
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key, Leaf: leaf}, pool
}
2 changes: 2 additions & 0 deletions testdata/hostcryptotls.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
got: pong
unknown certificate refused