diff --git a/cbor/cbor.go b/cbor/cbor.go index f3cdb2a..69ed614 100644 --- a/cbor/cbor.go +++ b/cbor/cbor.go @@ -1243,15 +1243,10 @@ func (e *Encoder) encodeTextOrBinary(rv reflect.Value) error { // Unaddressable arrays cannot be made into slices, so we must create a // slice and copy contents into it - slice := reflect.MakeSlice( - reflect.SliceOf(rv.Type().Elem()), - rv.Len(), - rv.Len(), - ) - if n := reflect.Copy(slice, rv); n != rv.Len() { + b = make([]byte, rv.Len()) + if n := reflect.Copy(reflect.ValueOf(b), rv); n != rv.Len() { panic("array contents were not fully copied into a slice for encoding") } - b = slice.Bytes() } info := u64Bytes(uint64(len(b))) diff --git a/di.go b/di.go index 49426b2..a0c8ffe 100644 --- a/di.go +++ b/di.go @@ -137,7 +137,7 @@ func appStart(ctx context.Context, transport Transport, info any) (*VoucherHeade // Make request typ, resp, err := transport.Send(ctx, protocol.DIAppStartMsgType, msg, nil) if err != nil { - return nil, fmt.Errorf("error sending DI.AppStart: %w", err) + return nil, fmt.Errorf("DI.AppStart: %w", err) } defer func() { _ = resp.Close() }() @@ -296,7 +296,7 @@ func setHmac(ctx context.Context, transport Transport, hmac hash.Hash, ovh *Vouc // Make request typ, resp, err := transport.Send(ctx, protocol.DISetHmacMsgType, msg, nil) if err != nil { - return fmt.Errorf("error sending DI.SetHMAC: %w", err) + return fmt.Errorf("DI.SetHMAC: %w", err) } defer func() { _ = resp.Close() }() diff --git a/examples/cmd/server.go b/examples/cmd/server.go index d8c2b68..b82ec76 100644 --- a/examples/cmd/server.go +++ b/examples/cmd/server.go @@ -19,6 +19,7 @@ import ( "errors" "flag" "fmt" + "io/fs" "iter" "log" "log/slog" @@ -110,11 +111,19 @@ func server() error { //nolint:gocyclo if dbPath == "" { return errors.New("db flag is required") } + _, dbStatErr := os.Stat(dbPath) state, err := sqlite.Open(dbPath, dbPass) if err != nil { return err } + // Generate keys only if the db wasn't already created + if errors.Is(dbStatErr, fs.ErrNotExist) { + if err := generateKeys(state); err != nil { + return err + } + } + // If printing owner public key, do so and exit if printOwnerPubKey != "" { return doPrintOwnerPubKey(state) @@ -125,36 +134,21 @@ func server() error { //nolint:gocyclo return doImportVoucher(state) } + // Normalize address flags useTLS = insecureTLS - - // RV Info - prot := protocol.RVProtHTTP - if useTLS { - prot = protocol.RVProtHTTPS - } - rvInfo := [][]protocol.RvInstruction{{{Variable: protocol.RVProtocol, Value: mustMarshal(prot)}}} if extAddr == "" { extAddr = addr } - host, portStr, err := net.SplitHostPort(extAddr) - if err != nil { - return fmt.Errorf("invalid external addr: %w", err) - } - if host == "" { - rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(net.IP{127, 0, 0, 1})}) - } else if hostIP := net.ParseIP(host); hostIP.To4() != nil || hostIP.To16() != nil { - rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(hostIP)}) + + // RV Info + var rvInfo [][]protocol.RvInstruction + if to0Addr != "" { + rvInfo, err = to0AddrToRvInfo() } else { - rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDns, Value: mustMarshal(host)}) + rvInfo, err = extAddrToRvInfo() } - portNum, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - return fmt.Errorf("invalid external port: %w", err) - } - port := uint16(portNum) - rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDevPort, Value: mustMarshal(port)}) - if rvBypass { - rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVBypass}) + return err } // Test RVDelay by introducing a delay before TO1 @@ -162,7 +156,7 @@ func server() error { //nolint:gocyclo // Invoke TO0 client if a GUID is specified if to0GUID != "" { - return registerRvBlob(host, port, state) + return registerRvBlob(state) } // Invoke resale protocol if a GUID is specified @@ -173,6 +167,110 @@ func server() error { //nolint:gocyclo return serveHTTP(rvInfo, state) } +func generateKeys(state *sqlite.DB) error { //nolint:gocyclo + // Generate manufacturing component keys + rsa2048MfgKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + rsa3072MfgKey, err := rsa.GenerateKey(rand.Reader, 3072) + if err != nil { + return err + } + ec256MfgKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + ec384MfgKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return err + } + generateCA := func(key crypto.Signer) ([]*x509.Certificate, error) { + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "Test CA"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(30 * 365 * 24 * time.Hour), + BasicConstraintsValid: true, + IsCA: true, + } + der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, err + } + return []*x509.Certificate{cert}, nil + } + rsa2048Chain, err := generateCA(rsa2048MfgKey) + if err != nil { + return err + } + rsa3072Chain, err := generateCA(rsa3072MfgKey) + if err != nil { + return err + } + ec256Chain, err := generateCA(ec256MfgKey) + if err != nil { + return err + } + ec384Chain, err := generateCA(ec384MfgKey) + if err != nil { + return err + } + if err := state.AddManufacturerKey(protocol.Rsa2048RestrKeyType, rsa2048MfgKey, rsa2048Chain); err != nil { + return err + } + if err := state.AddManufacturerKey(protocol.RsaPkcsKeyType, rsa3072MfgKey, rsa3072Chain); err != nil { + return err + } + if err := state.AddManufacturerKey(protocol.RsaPssKeyType, rsa3072MfgKey, rsa3072Chain); err != nil { + return err + } + if err := state.AddManufacturerKey(protocol.Secp256r1KeyType, ec256MfgKey, ec256Chain); err != nil { + return err + } + if err := state.AddManufacturerKey(protocol.Secp384r1KeyType, ec384MfgKey, ec384Chain); err != nil { + return err + } + + // Generate owner keys + rsa2048OwnerKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + rsa3072OwnerKey, err := rsa.GenerateKey(rand.Reader, 3072) + if err != nil { + return err + } + ec256OwnerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + ec384OwnerKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return err + } + if err := state.AddOwnerKey(protocol.Rsa2048RestrKeyType, rsa2048OwnerKey, nil); err != nil { + return err + } + if err := state.AddOwnerKey(protocol.RsaPkcsKeyType, rsa3072OwnerKey, nil); err != nil { + return err + } + if err := state.AddOwnerKey(protocol.RsaPssKeyType, rsa3072OwnerKey, nil); err != nil { + return err + } + if err := state.AddOwnerKey(protocol.Secp256r1KeyType, ec256OwnerKey, nil); err != nil { + return err + } + if err := state.AddOwnerKey(protocol.Secp384r1KeyType, ec384OwnerKey, nil); err != nil { + return err + } + return nil +} + func serveHTTP(rvInfo [][]protocol.RvInstruction, state *sqlite.DB) error { // Create FDO responder handler, err := newHandler(rvInfo, state) @@ -264,7 +362,75 @@ func doImportVoucher(state *sqlite.DB) error { return state.AddVoucher(context.Background(), &ov) } -func registerRvBlob(host string, port uint16, state *sqlite.DB) error { +func to0AddrToRvInfo() ([][]protocol.RvInstruction, error) { + url, err := url.Parse(to0Addr) + if err != nil { + return nil, fmt.Errorf("cannot parse TO0 addr: %w", err) + } + prot := protocol.RVProtHTTP + if url.Scheme == "https" { + prot = protocol.RVProtHTTPS + } + rvInfo := [][]protocol.RvInstruction{{{Variable: protocol.RVProtocol, Value: mustMarshal(prot)}}} + host, portStr, err := net.SplitHostPort(url.Host) + if err != nil { + host = url.Host + } + if portStr == "" { + portStr = "80" + if url.Scheme == "https" { + portStr = "443" + } + } + if host == "" { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(net.IP{127, 0, 0, 1})}) + } else if hostIP := net.ParseIP(host); hostIP.To4() != nil || hostIP.To16() != nil { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(hostIP)}) + } else { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDns, Value: mustMarshal(host)}) + } + portNum, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid TO0 port: %w", err) + } + port := uint16(portNum) + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDevPort, Value: mustMarshal(port)}) + if rvBypass { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVBypass}) + } + return rvInfo, nil +} + +func extAddrToRvInfo() ([][]protocol.RvInstruction, error) { + prot := protocol.RVProtHTTP + if useTLS { + prot = protocol.RVProtHTTPS + } + rvInfo := [][]protocol.RvInstruction{{{Variable: protocol.RVProtocol, Value: mustMarshal(prot)}}} + host, portStr, err := net.SplitHostPort(extAddr) + if err != nil { + return nil, fmt.Errorf("invalid external addr: %w", err) + } + if host == "" { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(net.IP{127, 0, 0, 1})}) + } else if hostIP := net.ParseIP(host); hostIP.To4() != nil || hostIP.To16() != nil { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVIPAddress, Value: mustMarshal(hostIP)}) + } else { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDns, Value: mustMarshal(host)}) + } + portNum, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid external port: %w", err) + } + port := uint16(portNum) + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVDevPort, Value: mustMarshal(port)}) + if rvBypass { + rvInfo[0] = append(rvInfo[0], protocol.RvInstruction{Variable: protocol.RVBypass}) + } + return rvInfo, nil +} + +func registerRvBlob(state *sqlite.DB) error { if to0Addr == "" { return fmt.Errorf("to0-guid depends on to0 flag being set") } @@ -280,11 +446,23 @@ func registerRvBlob(host string, port uint16, state *sqlite.DB) error { var guid protocol.GUID copy(guid[:], guidBytes) + // Construct TO2 addr proto := protocol.HTTPTransport if useTLS { proto = protocol.HTTPSTransport } - + host, portStr, err := net.SplitHostPort(extAddr) + if err != nil { + return fmt.Errorf("invalid external addr: %w", err) + } + if host == "" { + host = "localhost" + } + portNum, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return fmt.Errorf("invalid external port: %w", err) + } + port := uint16(portNum) to2Addrs := []protocol.RvTO2Addr{ { DNSAddress: &host, @@ -292,6 +470,8 @@ func registerRvBlob(host string, port uint16, state *sqlite.DB) error { TransportProtocol: proto, }, } + + // Register RV blob with RV server refresh, err := (&fdo.TO0Client{ Vouchers: state, OwnerKeys: state, @@ -359,109 +539,7 @@ func mustMarshal(v any) []byte { return data } -//nolint:gocyclo func newHandler(rvInfo [][]protocol.RvInstruction, state *sqlite.DB) (*transport.Handler, error) { - // Generate manufacturing component keys - rsa2048MfgKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - rsa3072MfgKey, err := rsa.GenerateKey(rand.Reader, 3072) - if err != nil { - return nil, err - } - ec256MfgKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - ec384MfgKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - generateCA := func(key crypto.Signer) ([]*x509.Certificate, error) { - template := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: "Test CA"}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(30 * 365 * 24 * time.Hour), - BasicConstraintsValid: true, - IsCA: true, - } - der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) - if err != nil { - return nil, err - } - cert, err := x509.ParseCertificate(der) - if err != nil { - return nil, err - } - return []*x509.Certificate{cert}, nil - } - rsa2048Chain, err := generateCA(rsa2048MfgKey) - if err != nil { - return nil, err - } - rsa3072Chain, err := generateCA(rsa3072MfgKey) - if err != nil { - return nil, err - } - ec256Chain, err := generateCA(ec256MfgKey) - if err != nil { - return nil, err - } - ec384Chain, err := generateCA(ec384MfgKey) - if err != nil { - return nil, err - } - if err := state.AddManufacturerKey(protocol.Rsa2048RestrKeyType, rsa2048MfgKey, rsa2048Chain); err != nil { - return nil, err - } - if err := state.AddManufacturerKey(protocol.RsaPkcsKeyType, rsa3072MfgKey, rsa3072Chain); err != nil { - return nil, err - } - if err := state.AddManufacturerKey(protocol.RsaPssKeyType, rsa3072MfgKey, rsa3072Chain); err != nil { - return nil, err - } - if err := state.AddManufacturerKey(protocol.Secp256r1KeyType, ec256MfgKey, ec256Chain); err != nil { - return nil, err - } - if err := state.AddManufacturerKey(protocol.Secp384r1KeyType, ec384MfgKey, ec384Chain); err != nil { - return nil, err - } - - // Generate owner keys - rsa2048OwnerKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - rsa3072OwnerKey, err := rsa.GenerateKey(rand.Reader, 3072) - if err != nil { - return nil, err - } - ec256OwnerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - ec384OwnerKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - if err := state.AddOwnerKey(protocol.Rsa2048RestrKeyType, rsa2048OwnerKey, nil); err != nil { - return nil, err - } - if err := state.AddOwnerKey(protocol.RsaPkcsKeyType, rsa3072OwnerKey, nil); err != nil { - return nil, err - } - if err := state.AddOwnerKey(protocol.RsaPssKeyType, rsa3072OwnerKey, nil); err != nil { - return nil, err - } - if err := state.AddOwnerKey(protocol.Secp256r1KeyType, ec256OwnerKey, nil); err != nil { - return nil, err - } - if err := state.AddOwnerKey(protocol.Secp384r1KeyType, ec384OwnerKey, nil); err != nil { - return nil, err - } - // Auto-register RV blob so that TO1 can be tested unless a TO0 address is // given or RV bypass is set var autoTO0 fdo.AutoTO0 @@ -526,6 +604,7 @@ func newHandler(rvInfo [][]protocol.RvInstruction, state *sqlite.DB) (*transport }, nil } +//nolint:gocyclo func ownerModules(ctx context.Context, guid protocol.GUID, info string, chain []*x509.Certificate, devmod serviceinfo.Devmod, modules []string) iter.Seq2[string, serviceinfo.OwnerModule] { return func(yield func(string, serviceinfo.OwnerModule) bool) { if slices.Contains(modules, "fdo.download") { diff --git a/examples/go.mod b/examples/go.mod index 108a3eb..9c26344 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 7cad18f..e79be3f 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.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550= github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= diff --git a/examples/wasm/.gitignore b/examples/wasm/.gitignore new file mode 100644 index 0000000..524856e --- /dev/null +++ b/examples/wasm/.gitignore @@ -0,0 +1,3 @@ +/build/ +node_modules/ +.wrangler/ diff --git a/examples/wasm/README.md b/examples/wasm/README.md new file mode 100644 index 0000000..f822db6 --- /dev/null +++ b/examples/wasm/README.md @@ -0,0 +1,78 @@ +# Rendzvous Service on Cloudflare Workers + +## Prerequisites + +Download TinyGo from your OS package manager and Wrangler from npm. + +## Deploy + +Create a Cloudflare Worker with the name "rv" and start a new repo with the configuration in `wrangler.toml`. + +Create a D1 database instance with name "rv" and update `wrangler.toml` with its UUID. + +Execute the included schema.sql setup. + +```console +wrangler d1 execute rv --remote --file=./schema.sql +``` + +Then deploy the application. + +```console +wrangler deploy +``` + +## Usage + +Add users by email address. + +```console +wrangler d1 execute rv --remote --command 'INSERT INTO trusted_emails (email) VALUES ("user@example.com")' +``` + +Add owner keys, connected to user accounts for auditability. + +```console +wrangler d1 execute rv --remote --command "INSERT INTO trusted_owners (email, pkix) VALUES ('user@example.com', UNHEX('$OWNER_KEY'))" +``` + +### Example + +Get the owner key from the example application to add to RV server. + +``` +OWNER_KEY=$(go run ./examples/cmd server -db db.test -print-owner-public SECP384R1 | head -n -1 | tail -n +2 | tr -d '\n' | base64 -d | xxd -p -c 0) +``` + +Initialize device credentials. + +``` +$ go run ./examples/cmd server -db db.test -http 127.0.0.1:9999 -to0 https://rv.${SUBDOMAIN}.workers.dev +[2024-11-01 00:00:00] INFO: Listening + local: 127.0.0.1:9999 + external: 127.0.0.1:9999 +``` + +```console +$ go run ./examples/cmd client -di http://127.0.0.1:9999 +$ go run ./examples/cmd client -print +blobcred[ + ... + GUID d21d841a3f54f4e89a60ed9b9779e9e8 + ... +] +$ go run ./examples/cmd client -rv-only +``` + +Register RV blob with RV server. + +```console +$ go run ./examples/cmd server -db db.test -to0 https://rv.${SUBDOMAIN}.workers.dev -to0-guid d21d841a3f54f4e89a60ed9b9779e9e8 +``` + +Transfer ownership using the Cloudflare RV service and local owner service. + +```console +$ go run ./examples/cmd client +Success +``` diff --git a/examples/wasm/main.go b/examples/wasm/main.go new file mode 100644 index 0000000..0df40be --- /dev/null +++ b/examples/wasm/main.go @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build tinygo + +// Package main implements a Rendezvous Server which can be compiled with +// TinyGo and run on Cloudflare Workers within the free tier (under reasonable +// load). +package main + +import ( + "context" + "crypto/x509" + "database/sql" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "time" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare/cron" + _ "github.com/syumai/workers/cloudflare/d1" + + "github.com/fido-device-onboard/go-fdo" + fdo_http "github.com/fido-device-onboard/go-fdo/http" + "github.com/fido-device-onboard/go-fdo/sqlite" +) + +const oneWeekInSeconds uint32 = 7 * 24 * 60 * 60 + +func main() { + slog.SetLogLoggerLevel(slog.LevelDebug) + + db, err := sql.Open("d1", "RendezvousDB") + if err != nil { + slog.Error("d1 connect", "error", err) + os.Exit(1) + } + + // Handle FDO protocol endpoint + state := sqlite.New(db) + handler := http.NewServeMux() + + // If building with Go instead of TinyGo, use: + // handler.Handle("POST /fdo/101/{msg}", &fdo_http.Handler{ + handler.Handle("/fdo/101/", &fdo_http.Handler{ + TO0Responder: &fdo.TO0Server{ + Session: state, + RVBlobs: state, + AcceptVoucher: func(ctx context.Context, ov fdo.Voucher) (accept bool, err error) { + owner, err := ov.OwnerPublicKey() + if err != nil { + return false, fmt.Errorf("error getting voucher owner key: %w", err) + } + der, err := x509.MarshalPKIXPublicKey(owner) + if err != nil { + return false, fmt.Errorf("error marshaling voucher owner key: %w", err) + } + return trustedOwner(ctx, db, der) + }, + NegotiateTTL: func(requestedSeconds uint32, ov fdo.Voucher) (waitSeconds uint32) { + return min(requestedSeconds, oneWeekInSeconds) + }, + }, + TO1Responder: &fdo.TO1Server{ + Session: state, + RVBlobs: state, + }, + }) + + // Schedule a daily task to cleanup expired RV blobs + go cron.ScheduleTask(func(ctx context.Context) error { + e, err := cron.NewEvent(ctx) + if err != nil { + return err + } + return removeExpiredBlobs(ctx, db, e.ScheduledTime) + }) + + workers.Serve(handler) +} + +func trustedOwner(ctx context.Context, db *sql.DB, pkixKey []byte) (bool, error) { + var email string + row := db.QueryRowContext(ctx, `SELECT email FROM trusted_owners WHERE pkix = ?`, pkixKey) + if err := row.Scan(&email); errors.Is(err, sql.ErrNoRows) { + return false, nil + } else if err != nil { + return false, err + } + slog.Info("accepting voucher", "user", email) + + return true, nil +} + +func removeExpiredBlobs(ctx context.Context, db *sql.DB, nowish time.Time) error { + _, err := db.ExecContext(ctx, `DELETE FROM rv_blobs WHERE exp < ?`, nowish.Unix()) + return err +} diff --git a/examples/wasm/schema.sql b/examples/wasm/schema.sql new file mode 100644 index 0000000..294b522 --- /dev/null +++ b/examples/wasm/schema.sql @@ -0,0 +1,31 @@ +PRAGMA foreign_keys = ON; +CREATE TABLE IF NOT EXISTS sessions + ( id BLOB PRIMARY KEY + , protocol INTEGER NOT NULL + ); +CREATE TABLE IF NOT EXISTS to0_sessions + ( session BLOB UNIQUE NOT NULL + , nonce BLOB + , FOREIGN KEY(session) REFERENCES sessions(id) ON DELETE CASCADE + ); +CREATE TABLE IF NOT EXISTS to1_sessions + ( session BLOB UNIQUE NOT NULL + , nonce BLOB + , alg INTEGER + , FOREIGN KEY(session) REFERENCES sessions(id) ON DELETE CASCADE + ); +CREATE TABLE IF NOT EXISTS rv_blobs + ( guid BLOB PRIMARY KEY + , rv BLOB NOT NULL + , voucher BLOB NOT NULL + , exp INTEGER NOT NULL + ); +CREATE INDEX IF NOT EXISTS rv_blob_exp ON rv_blobs(exp ASC); +CREATE TABLE IF NOT EXISTS trusted_emails + ( email TEXT PRIMARY KEY + ); +CREATE TABLE IF NOT EXISTS trusted_owners + ( pkix BLOB PRIMARY KEY + , email TEXT NOT NULL + , FOREIGN KEY(email) REFERENCES trusted_emails(email) ON DELETE CASCADE + ); diff --git a/examples/wasm/wrangler.toml b/examples/wasm/wrangler.toml new file mode 100644 index 0000000..5fe7aea --- /dev/null +++ b/examples/wasm/wrangler.toml @@ -0,0 +1,20 @@ +name = "rv" +main = "./build/worker.mjs" +compatibility_date = "2024-10-29" + +[build] +command = """ +go run github.com/syumai/workers/cmd/workers-assets-gen@v0.26.3 -mode tinygo && +tinygo build -o ./build/app.wasm -target wasm -no-debug ./main.go +""" + +[triggers] +crons = [ "0 3 * * *"] # 3AM daily + +[observability] +enabled = true + +[[ d1_databases ]] +binding = "RendezvousDB" +database_name = "rv" +database_id = "COPY_YOUR_UUID_HERE" 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..0116439 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) } @@ -149,6 +130,10 @@ func (t *Transport) handleResponse(resp *http.Response, sess kex.Session) (msgTy } msgType = uint8(typ) case http.StatusInternalServerError: + if resp.Header.Get("Content-Type") != "application/cbor" { + _ = resp.Body.Close() + return 0, nil, fmt.Errorf("%s did not include an error message body", resp.Status) + } msgType = 255 default: _ = resp.Body.Close() @@ -189,10 +174,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 168a34b..ff369fd 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -19,15 +19,10 @@ 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/ncruces/go-sqlite3/vfs/xts" // Encryption VFS - "github.com/fido-device-onboard/go-fdo" "github.com/fido-device-onboard/go-fdo/cbor" "github.com/fido-device-onboard/go-fdo/cose" @@ -44,25 +39,6 @@ type DB struct { db *sql.DB } -// Open 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 Open(filename, password string) (*DB, error) { - var query string - if password != "" { - query += fmt.Sprintf("?vfs=xts&_pragma=textkey(%q)&_pragma=temp_store(memory)", 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) - if err := Init(db); err != nil { - return nil, err - } - return New(db), nil -} - // New creates a DB. The expected tables must already be created and pragmas // must already be set, including foreign_keys=ON. func New(db *sql.DB) *DB { return &DB{db: db} } @@ -96,6 +72,8 @@ func Init(db *sql.DB) error { , voucher BLOB NOT NULL , exp INTEGER NOT NULL )`, + `CREATE INDEX IF NOT EXISTS rv_blob_exp + ON rv_blobs(exp ASC)`, `CREATE TABLE IF NOT EXISTS sessions ( id BLOB PRIMARY KEY , protocol INTEGER NOT NULL diff --git a/sqlite/wasm.go b/sqlite/wasm.go new file mode 100644 index 0000000..24ac00a --- /dev/null +++ b/sqlite/wasm.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache 2.0 + +//go:build !tinygo + +// Open is not implemented for tinygo, because it requires embedding a WASM +// runtime in the binary. + +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/ncruces/go-sqlite3/vfs/xts" // Encryption VFS +) + +// Open 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 Open(filename, password string) (*DB, error) { + var query string + if password != "" { + query += fmt.Sprintf("?vfs=xts&_pragma=textkey(%q)&_pragma=temp_store(memory)", 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) + if err := Init(db); err != nil { + return nil, err + } + return New(db), nil +} diff --git a/to0.go b/to0.go index d8fab00..06e2262 100644 --- a/to0.go +++ b/to0.go @@ -65,7 +65,7 @@ func (c *TO0Client) hello(ctx context.Context, transport Transport) (protocol.No // Make request typ, resp, err := transport.Send(ctx, protocol.TO0HelloMsgType, msg, nil) if err != nil { - return protocol.Nonce{}, fmt.Errorf("error sending TO0.Hello: %w", err) + return protocol.Nonce{}, fmt.Errorf("TO0.Hello: %w", err) } defer func() { _ = resp.Close() }() @@ -182,7 +182,7 @@ func (c *TO0Client) ownerSign(ctx context.Context, transport Transport, guid pro // Make request typ, resp, err := transport.Send(ctx, protocol.TO0OwnerSignMsgType, msg, nil) if err != nil { - return 0, fmt.Errorf("error sending TO0.OwnerSign: %w", err) + return 0, fmt.Errorf("TO0.OwnerSign: %w", err) } defer func() { _ = resp.Close() }() diff --git a/to1.go b/to1.go index 346a080..990d690 100644 --- a/to1.go +++ b/to1.go @@ -80,7 +80,7 @@ func helloRv(ctx context.Context, transport Transport, cred DeviceCredential, ke // Make request typ, resp, err := transport.Send(ctx, protocol.TO1HelloRVMsgType, msg, nil) if err != nil { - return protocol.Nonce{}, fmt.Errorf("error sending TO1.HelloRV: %w", err) + return protocol.Nonce{}, fmt.Errorf("TO1.HelloRV: %w", err) } defer func() { _ = resp.Close() }() @@ -157,7 +157,7 @@ func proveToRv(ctx context.Context, transport Transport, cred DeviceCredential, // Make request typ, resp, err := transport.Send(ctx, protocol.TO1ProveToRVMsgType, msg, nil) if err != nil { - return nil, fmt.Errorf("error sending TO1.ProveToRV: %w", err) + return nil, fmt.Errorf("TO1.ProveToRV: %w", err) } defer func() { _ = resp.Close() }() diff --git a/to2.go b/to2.go index eb43e48..e41bbcc 100644 --- a/to2.go +++ b/to2.go @@ -706,7 +706,7 @@ func sendNextOVEntry(ctx context.Context, transport Transport, i int) (*cose.Sig // Make request typ, resp, err := transport.Send(ctx, protocol.TO2GetOVNextEntryMsgType, msg, nil) if err != nil { - return nil, fmt.Errorf("error sending TO2.GetOVNextEntry: %w", err) + return nil, fmt.Errorf("TO2.GetOVNextEntry: %w", err) } defer func() { _ = resp.Close() }() @@ -811,7 +811,7 @@ func proveDevice(ctx context.Context, transport Transport, proveDeviceNonce prot // Make request typ, resp, err := transport.Send(ctx, protocol.TO2ProveDeviceMsgType, msg, kex.DecryptOnly{Session: sess}) if err != nil { - return protocol.Nonce{}, nil, fmt.Errorf("error sending TO2.ProveDevice: %w", err) + return protocol.Nonce{}, nil, fmt.Errorf("TO2.ProveDevice: %w", err) } defer func() { _ = resp.Close() }() @@ -1046,7 +1046,7 @@ func sendReadyServiceInfo(ctx context.Context, transport Transport, alg protocol // Make request typ, resp, err := transport.Send(ctx, protocol.TO2DeviceServiceInfoReadyMsgType, msg, sess) if err != nil { - return 0, fmt.Errorf("error sending TO2.DeviceServiceInfoReady: %w", err) + return 0, fmt.Errorf("TO2.DeviceServiceInfoReady: %w", err) } defer func() { _ = resp.Close() }() @@ -1308,7 +1308,7 @@ func sendDone(ctx context.Context, transport Transport, proveDvNonce, setupDvNon // Make request typ, resp, err := transport.Send(ctx, protocol.TO2DoneMsgType, msg, sess) if err != nil { - return fmt.Errorf("error sending TO2.Done: %w", err) + return fmt.Errorf("TO2.Done: %w", err) } defer func() { _ = resp.Close() }() @@ -1419,7 +1419,7 @@ func sendDeviceServiceInfo(ctx context.Context, transport Transport, msg deviceS // Make request typ, resp, err := transport.Send(ctx, protocol.TO2DeviceServiceInfoMsgType, msg, sess) if err != nil { - return nil, fmt.Errorf("error sending TO2.DeviceServiceInfo: %w", err) + return nil, fmt.Errorf("TO2.DeviceServiceInfo: %w", err) } defer func() { _ = resp.Close() }()