From 580d17d077c27ffeaae1e12245ecc2db8290a0e1 Mon Sep 17 00:00:00 2001 From: Ben Krieger Date: Mon, 7 Oct 2024 15:16:51 -0400 Subject: [PATCH] Add TinyGo support for {DI,TO0,TO1}Server Signed-off-by: Ben Krieger --- cbor/cbor.go | 2 +- cbor/map.go | 10 ++ cbor/map_tinygo.go | 15 +++ examples/go.mod | 1 + examples/go.sum | 2 + examples/wasm/main.go | 35 +++++++ http/debug.go | 16 +++ http/handler.go | 39 +------- http/transport.go | 30 +----- http/util.go | 75 ++++++++++++++ http/util_tinygo.go | 223 ++++++++++++++++++++++++++++++++++++++++++ sqlite/sqlite.go | 21 ---- sqlite/wasm.go | 33 +++++++ sqlite/wasm_tinygo.go | 14 +++ 14 files changed, 434 insertions(+), 82 deletions(-) create mode 100644 cbor/map.go create mode 100644 cbor/map_tinygo.go create mode 100644 examples/wasm/main.go create mode 100644 http/util.go create mode 100644 http/util_tinygo.go create mode 100644 sqlite/wasm.go create mode 100644 sqlite/wasm_tinygo.go diff --git a/cbor/cbor.go b/cbor/cbor.go index 9cf965a..55da194 100644 --- a/cbor/cbor.go +++ b/cbor/cbor.go @@ -918,7 +918,7 @@ func (d *Decoder) decodeMap(rv reflect.Value, additional []byte) error { // Clear map rmap := rv - rmap.Clear() + mapClear(rmap) // Get key-value types keyType := rmap.Type().Key() diff --git a/cbor/map.go b/cbor/map.go new file mode 100644 index 0000000..8d677fe --- /dev/null +++ b/cbor/map.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build !tinygo + +package cbor + +import "reflect" + +func mapClear(rv reflect.Value) { rv.Clear() } diff --git a/cbor/map_tinygo.go b/cbor/map_tinygo.go new file mode 100644 index 0000000..e959fe7 --- /dev/null +++ b/cbor/map_tinygo.go @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build tinygo + +package cbor + +import "reflect" + +func mapClear(rv reflect.Value) { + iter := rv.MapRange() + for iter.Next() { + rv.SetMapIndex(iter.Key(), reflect.Value{}) + } +} diff --git a/examples/go.mod b/examples/go.mod index db4c114..05ca9bb 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -17,6 +17,7 @@ require ( github.com/fido-device-onboard/go-fdo/tpm v0.0.0-00010101000000-000000000000 github.com/google/go-tpm v0.9.2-0.20240920144513-364d5f2f78b9 github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba + github.com/syumai/workers v0.26.3 hermannm.dev/devlog v0.4.1 ) diff --git a/examples/go.sum b/examples/go.sum index 04c8c2b..2514588 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -47,6 +47,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/syumai/workers v0.26.3 h1:AF+IBaRccbR4JIj2kNJLJblruPFMD/pAbzkopejGcP8= +github.com/syumai/workers v0.26.3/go.mod h1:ZnqmdiHNBrbxOLrZ/HJ5jzHy6af9cmiNZk10R9NrIEA= github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= diff --git a/examples/wasm/main.go b/examples/wasm/main.go new file mode 100644 index 0000000..3e7f1f3 --- /dev/null +++ b/examples/wasm/main.go @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +// Package main implements a WASM AIO server example (that would panic with +// real use). +package main + +import ( + "log" + "net/http" + + "github.com/syumai/workers" + + "github.com/fido-device-onboard/go-fdo" + "github.com/fido-device-onboard/go-fdo/custom" + fdo_http "github.com/fido-device-onboard/go-fdo/http" + "github.com/fido-device-onboard/go-fdo/sqlite" +) + +// Build with tinygo build -target wasm -no-debug -o rv.wasm ./main.go + +func main() { + db, err := sqlite.Init(nil) + if err != nil { + log.Fatal(err) + } + workers.Serve(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + (&fdo_http.Handler{ + DIResponder: &fdo.DIServer[custom.DeviceMfgInfo]{Session: db}, + TO0Responder: &fdo.TO0Server{Session: db}, + TO1Responder: &fdo.TO1Server{Session: db}, + // TO2Server uses goroutines and cannot be compiled with TinyGo + }).ServeHTTP(w, r) + })) +} diff --git a/http/debug.go b/http/debug.go index f1e6b79..1227a7a 100644 --- a/http/debug.go +++ b/http/debug.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "log/slog" + "github.com/fido-device-onboard/go-fdo/cbor" "github.com/fido-device-onboard/go-fdo/cbor/cdn" ) @@ -22,3 +23,18 @@ func tryDebugNotation(b []byte) string { } return d } + +func debugUnencryptedMessage(msgType uint8, msg any) { + if debugEnabled() { + return + } + body, _ := cbor.Marshal(msg) + slog.Debug("unencrypted request", "msg", msgType, "body", tryDebugNotation(body)) +} + +func debugDecryptedMessage(msgType uint8, decrypted []byte) { + if debugEnabled() { + return + } + slog.Debug("decrypted response", "msg", msgType, "body", tryDebugNotation(decrypted)) +} diff --git a/http/handler.go b/http/handler.go index 067aeba..8a10455 100644 --- a/http/handler.go +++ b/http/handler.go @@ -12,8 +12,6 @@ import ( "io" "log/slog" "net/http" - "net/http/httptest" - "net/http/httputil" "strconv" "strings" "time" @@ -42,12 +40,10 @@ type Handler struct { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Parse message type from request URL - typ, err := strconv.ParseUint(r.PathValue("msg"), 10, 8) - if err != nil { - writeErr(w, 0, fmt.Errorf("invalid message type")) + msgType, ok := msgTypeFromPath(w, r) + if !ok { return } - msgType := uint8(typ) proto := protocol.Of(msgType) // Parse request headers @@ -108,39 +104,14 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if debugEnabled() { - h.debugRequest(ctx, w, r, msgType, resp) + debugRequest(w, r, func(w http.ResponseWriter, r *http.Request) { + h.handleRequest(ctx, w, r, msgType, resp) + }) return } h.handleRequest(ctx, w, r, msgType, resp) } -func (h Handler) debugRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, msgType uint8, resp protocol.Responder) { - // Dump request - debugReq, _ := httputil.DumpRequest(r, false) - var saveBody bytes.Buffer - if _, err := saveBody.ReadFrom(r.Body); err == nil { - r.Body = io.NopCloser(&saveBody) - } - slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), - "body", tryDebugNotation(saveBody.Bytes())) - - // Dump response - rr := httptest.NewRecorder() - h.handleRequest(ctx, rr, r, msgType, resp) - debugResp, _ := httputil.DumpResponse(rr.Result(), false) - slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), - "body", tryDebugNotation(rr.Body.Bytes())) - - // Copy recorded response into response writer - for key, values := range rr.Header() { - for _, value := range values { - w.Header().Add(key, value) - } - } - w.WriteHeader(rr.Code) - _, _ = w.Write(rr.Body.Bytes()) -} - func (h Handler) handleRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, msgType uint8, resp protocol.Responder) { // Validate content length maxSize := h.MaxContentLength diff --git a/http/transport.go b/http/transport.go index 8ff15ab..9b8105f 100644 --- a/http/transport.go +++ b/http/transport.go @@ -10,9 +10,7 @@ import ( "errors" "fmt" "io" - "log/slog" "net/http" - "net/http/httputil" "net/url" "path" "strconv" @@ -51,8 +49,6 @@ type Transport struct { } // Send sends a single message and receives a single response message. -// -//nolint:gocyclo func (t *Transport) Send(ctx context.Context, msgType uint8, msg any, sess kex.Session) (respType uint8, _ io.ReadCloser, _ error) { // Initialize default values if t.Client == nil { @@ -64,10 +60,7 @@ func (t *Transport) Send(ctx context.Context, msgType uint8, msg any, sess kex.S // Encrypt if a key exchange session is provided if sess != nil { - if debugEnabled() { - body, _ := cbor.Marshal(msg) - slog.Debug("unencrypted request", "msg", msgType, "body", tryDebugNotation(body)) - } + debugUnencryptedMessage(msgType, msg) var err error msg, err = sess.Encrypt(rand.Reader, msg) if err != nil { @@ -105,24 +98,12 @@ func (t *Transport) Send(ctx context.Context, msgType uint8, msg any, sess kex.S } // Perform HTTP request - if debugEnabled() { - debugReq, _ := httputil.DumpRequestOut(req, false) - slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), - "body", tryDebugNotation(body.Bytes())) - } + debugRequestOut(req, body) resp, err := t.Client.Do(req) if err != nil { return 0, nil, fmt.Errorf("error making HTTP request for message %d: %w", msgType, err) } - if debugEnabled() { - debugResp, _ := httputil.DumpResponse(resp, false) - var saveBody bytes.Buffer - if _, err := saveBody.ReadFrom(resp.Body); err == nil { - resp.Body = io.NopCloser(&saveBody) - } - slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), - "body", tryDebugNotation(saveBody.Bytes())) - } + debugResponse(resp) return t.handleResponse(resp, sess) } @@ -189,10 +170,7 @@ func (t *Transport) handleResponse(resp *http.Response, sess kex.Session) (msgTy if err != nil { return 0, nil, fmt.Errorf("error decrypting message %d: %w", msgType, err) } - - if debugEnabled() { - slog.Debug("decrypted response", "msg", msgType, "body", tryDebugNotation(decrypted)) - } + debugDecryptedMessage(msgType, decrypted) content = io.NopCloser(bytes.NewBuffer(decrypted)) } diff --git a/http/util.go b/http/util.go new file mode 100644 index 0000000..3270968 --- /dev/null +++ b/http/util.go @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build !tinygo + +package http + +import ( + "bytes" + "fmt" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "net/http/httputil" + "strconv" +) + +func msgTypeFromPath(w http.ResponseWriter, r *http.Request) (uint8, bool) { + typ, err := strconv.ParseUint(r.PathValue("msg"), 10, 8) + if err != nil { + writeErr(w, 0, fmt.Errorf("invalid message type")) + return 0, false + } + return uint8(typ), true +} + +func debugRequest(w http.ResponseWriter, r *http.Request, handler http.HandlerFunc) { + // Dump request + debugReq, _ := httputil.DumpRequest(r, false) + var saveBody bytes.Buffer + if _, err := saveBody.ReadFrom(r.Body); err == nil { + r.Body = io.NopCloser(&saveBody) + } + slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), + "body", tryDebugNotation(saveBody.Bytes())) + + // Dump response + rr := httptest.NewRecorder() + handler(rr, r) + debugResp, _ := httputil.DumpResponse(rr.Result(), false) + slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), + "body", tryDebugNotation(rr.Body.Bytes())) + + // Copy recorded response into response writer + for key, values := range rr.Header() { + for _, value := range values { + w.Header().Add(key, value) + } + } + w.WriteHeader(rr.Code) + _, _ = w.Write(rr.Body.Bytes()) +} + +func debugRequestOut(req *http.Request, body *bytes.Buffer) { + if !debugEnabled() { + return + } + debugReq, _ := httputil.DumpRequestOut(req, false) + slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), + "body", tryDebugNotation(body.Bytes())) +} + +func debugResponse(resp *http.Response) { + if !debugEnabled() { + return + } + debugResp, _ := httputil.DumpResponse(resp, false) + var saveBody bytes.Buffer + if _, err := saveBody.ReadFrom(resp.Body); err == nil { + resp.Body = io.NopCloser(&saveBody) + } + slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), + "body", tryDebugNotation(saveBody.Bytes())) +} diff --git a/http/util_tinygo.go b/http/util_tinygo.go new file mode 100644 index 0000000..5a77e2a --- /dev/null +++ b/http/util_tinygo.go @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build tinygo + +package http + +import ( + "bytes" + "fmt" + "io" + "log/slog" + "net/http" + "strconv" + "strings" +) + +func msgTypeFromPath(w http.ResponseWriter, r *http.Request) (uint8, bool) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return 0, false + } + path := strings.TrimPrefix(r.URL.Path, "/fdo/101/msg/") + if strings.Contains(path, "/") { + w.WriteHeader(http.StatusNotFound) + return 0, false + } + typ, err := strconv.ParseUint(path, 10, 8) + if err != nil { + writeErr(w, 0, fmt.Errorf("invalid message type")) + return 0, false + } + return uint8(typ), true +} + +func debugRequest(w http.ResponseWriter, r *http.Request, handler http.HandlerFunc) { + // Dump request + debugReq, _ := dumpRequest(r) + var saveBody bytes.Buffer + if _, err := saveBody.ReadFrom(r.Body); err == nil { + r.Body = io.NopCloser(&saveBody) + } + slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), + "body", tryDebugNotation(saveBody.Bytes())) + + // Dump response + rr := new(responseRecorder) + handler(rr, r) + resp := rr.Result() + debugResp, _ := dumpResponse(resp) + slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), + "body", tryDebugNotation(rr.body.Bytes())) + + // Copy recorded response into response writer + for key, values := range rr.Header() { + for _, value := range values { + w.Header().Add(key, value) + } + } + w.WriteHeader(resp.StatusCode) + _, _ = w.Write(rr.body.Bytes()) +} + +func debugRequestOut(req *http.Request, body *bytes.Buffer) { + if !debugEnabled() { + return + } + + // Unlike httputil.DumpRequestOut, this does not use an actual HTTP + // transport to ensure that the output has all relevant headers updated and + // canonicalized. Improvements are welcome. + debugReq, _ := dumpRequest(req) + slog.Debug("request", "dump", string(bytes.TrimSpace(debugReq)), + "body", tryDebugNotation(body.Bytes())) +} + +func debugResponse(resp *http.Response) { + if !debugEnabled() { + return + } + + var saveBody bytes.Buffer + if _, err := saveBody.ReadFrom(resp.Body); err == nil { + _ = resp.Body.Close() + resp.Body = io.NopCloser(&saveBody) + } + debugResp, _ := dumpResponse(resp) + slog.Debug("response", "dump", string(bytes.TrimSpace(debugResp)), + "body", tryDebugNotation(saveBody.Bytes())) +} + +func dumpRequest(req *http.Request) ([]byte, error) { + var out bytes.Buffer + + fmt.Fprintf(&out, "%s %s HTTP/%d.%d\r\n", req.Method, req.RequestURI, req.ProtoMajor, req.ProtoMinor) + + absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") + if !absRequestURI { + host := req.Host + if host == "" && req.URL != nil { + host = req.URL.Host + } + if host != "" { + fmt.Fprintf(&out, "Host: %s\r\n", host) + } + } + + if len(req.TransferEncoding) > 0 { + fmt.Fprintf(&out, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) + } + + if err := req.Header.WriteSubset(&out, map[string]bool{ + "Transfer-Encoding": true, + "Trailer": true, + }); err != nil { + return nil, err + } + + io.WriteString(&out, "\r\n") + + return out.Bytes(), nil +} + +var errNoBody = fmt.Errorf("no body") + +type failureToReadBody struct{} + +func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } +func (failureToReadBody) Close() error { return nil } + +func dumpResponse(resp *http.Response) ([]byte, error) { + var out bytes.Buffer + savecl := resp.ContentLength + if resp.ContentLength == 0 { + resp.Body = io.NopCloser(strings.NewReader("")) + } else { + resp.Body = failureToReadBody{} + } + err := resp.Write(&out) + resp.ContentLength = savecl + if err != nil && err != errNoBody { + return nil, err + } + return out.Bytes(), nil +} + +type responseRecorder struct { + body *bytes.Buffer + code int + + header http.Header + headerAtFirstWrite http.Header + wroteHeader bool + + result *http.Response +} + +func (rr *responseRecorder) Header() http.Header { + if rr.header == nil { + rr.header = make(http.Header) + } + return rr.header +} + +func (rr *responseRecorder) Write(p []byte) (int, error) { + if !rr.wroteHeader { + m := rr.Header() + if _, hasType := m["Content-Type"]; !hasType && m.Get("Transfer-Encoding") == "" { + m.Set("Content-Type", http.DetectContentType(p)) + } + rr.WriteHeader(200) + } + if rr.body == nil { + rr.body = new(bytes.Buffer) + } + return rr.body.Write(p) +} + +func (rr *responseRecorder) WriteHeader(statusCode int) { + if rr.wroteHeader { + return + } + + rr.code = statusCode + rr.wroteHeader = true + rr.headerAtFirstWrite = rr.Header().Clone() +} + +func (rr *responseRecorder) Result() *http.Response { + if rr.result != nil { + return rr.result + } + if rr.code == 0 { + rr.code = 200 + } + if rr.headerAtFirstWrite == nil { + rr.headerAtFirstWrite = rr.Header().Clone() + } + + res := &http.Response{ + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + StatusCode: rr.code, + Header: rr.headerAtFirstWrite, + } + if res.StatusCode == 0 { + res.StatusCode = 200 + } + res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode)) + res.Body = io.NopCloser(bytes.NewReader(rr.body.Bytes())) + res.ContentLength = func(length string) int64 { + n, err := strconv.ParseUint(strings.TrimSpace(length), 10, 63) + if err != nil { + return -1 + } + return int64(n) + }(res.Header.Get("Content-Length")) + // Trailers are not used in FDO + + rr.result = res + return res +} diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index f812494..f7ca9df 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -19,21 +19,16 @@ import ( "fmt" "io" "maps" - "path/filepath" "slices" "strings" "time" - "github.com/ncruces/go-sqlite3/driver" // Load database/sql driver - _ "github.com/ncruces/go-sqlite3/embed" // Load sqlite WASM binary - "github.com/fido-device-onboard/go-fdo" "github.com/fido-device-onboard/go-fdo/cbor" "github.com/fido-device-onboard/go-fdo/cose" "github.com/fido-device-onboard/go-fdo/custom" "github.com/fido-device-onboard/go-fdo/kex" "github.com/fido-device-onboard/go-fdo/protocol" - _ "github.com/fido-device-onboard/go-fdo/sqlite/xts" // Encryption VFS ) // DB implements FDO server state persistence. @@ -44,22 +39,6 @@ type DB struct { db *sql.DB } -// New creates or opens a SQLite database file using a single non-pooled -// connection. If a password is specified, then the xts VFS will be used -// with a text key. -func New(filename, password string) (*DB, error) { - var query string - if password != "" { - query += fmt.Sprintf("&vfs=xts&_pragma=textkey(%q)", password) - } - connector, err := (&driver.SQLite{}).OpenConnector("file:" + filepath.Clean(filename) + query) - if err != nil { - return nil, fmt.Errorf("error creating sqlite connector: %w", err) - } - db := sql.OpenDB(connector) - return Init(db) -} - // Init ensures all tables are created and pragma are set. It does not // recognize if tables have been created with invalid schemas. // diff --git a/sqlite/wasm.go b/sqlite/wasm.go new file mode 100644 index 0000000..cc5c71d --- /dev/null +++ b/sqlite/wasm.go @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build !tinygo + +package sqlite + +import ( + "database/sql" + "fmt" + "path/filepath" + + "github.com/ncruces/go-sqlite3/driver" // Load database/sql driver + _ "github.com/ncruces/go-sqlite3/embed" // Load sqlite WASM binary + + _ "github.com/fido-device-onboard/go-fdo/sqlite/xts" // Encryption VFS +) + +// New creates or opens a SQLite database file using a single non-pooled +// connection. If a password is specified, then the xts VFS will be used +// with a text key. +func New(filename, password string) (*DB, error) { + var query string + if password != "" { + query += fmt.Sprintf("&vfs=xts&_pragma=textkey(%q)", password) + } + connector, err := (&driver.SQLite{}).OpenConnector("file:" + filepath.Clean(filename) + query) + if err != nil { + return nil, fmt.Errorf("error creating sqlite connector: %w", err) + } + db := sql.OpenDB(connector) + return Init(db) +} diff --git a/sqlite/wasm_tinygo.go b/sqlite/wasm_tinygo.go new file mode 100644 index 0000000..246f54a --- /dev/null +++ b/sqlite/wasm_tinygo.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build tinygo + +package sqlite + +import "fmt" + +// New is not implemented for tinygo, because it requires embedding a WASM +// runtime in the binary. +func New(filename, password string) (*DB, error) { + return nil, fmt.Errorf("not supported in tinygo") +}