fdo

package module
v0.0.0-...-1219b78 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 2, 2025 License: Apache-2.0 Imports: 32 Imported by: 5

README

FIDO Device Onboard - Go Library

OpenSSF Scorecard Lint Test License Building Go Reference

[!WARNING] This library has not yet completed interop testing with the FIDO Alliance, but will at the next opportunity.

go-fdo is a lightweight stdlib-only library for implementing FDO device, owner service, and device initialization server roles.

It implements FIDO Device Onboard Specification 1.1 as well as necessary dependencies such as CBOR and COSE. Implementations of dependencies are not meant to be complete implementations of their relative specifications, but are supported and any breaking changes to their APIs will be considered a breaking change to go-fdo.

Building the Example Application

The example client and server application can be built with go build directly, but requires a Go workspace to build from the root package directory.

$ go work init
$ go work use -r .
$ go run ./examples/cmd

Usage:
  fdo [global_options] [client|server] [--] [options]

Global options:
  -debug
        Run subcommand with debug enabled

Client options:
  -blob string
        File path of device credential blob (default "cred.bin")
  -cipher suite
        Name of cipher suite to use for encryption (see usage) (default "A128GCM")
  -debug
        Print HTTP contents
  -di URL
        HTTP base URL for DI server
  -di-key string
        Key for device credential [options: ec256, ec384, rsa2048, rsa3072] (default "ec384")
  -di-key-enc string
        Public key encoding to use for manufacturer key [x509,x5chain,cose] (default "x509")
  -download dir
        A dir to download files into (FSIM disabled if empty)
  -echo-commands
        Echo all commands received to stdout (FSIM disabled if false)
  -insecure-tls
        Skip TLS certificate verification
  -kex suite
        Name of cipher suite to use for key exchange (see usage) (default "ECDH384")
  -print
        Print device credential blob and stop
  -rv-only
        Perform TO1 then stop
  -tpm path
        Use a TPM at path for device credential secrets
  -upload files
        List of dirs and files to upload files from, comma-separated and/or flag provided multiple times (FSIM disabled if empty)
  -wget-dir dir
        A dir to wget files into (FSIM disabled if empty)

Server options:
  -command-date
        Use fdo.command FSIM to have device run "date +%s"
  -db string
        SQLite database file path
  -db-pass string
        SQLite database encryption-at-rest passphrase
  -debug
        Print HTTP contents
  -download file
        Use fdo.download FSIM for each file (flag may be used multiple times)
  -ext-http addr
        External address devices should connect to (default "127.0.0.1:${LISTEN_PORT}")
  -http addr
        The address to listen on (default "localhost:8080")
  -import-voucher path
        Import a PEM encoded voucher file at path
  -insecure-tls
        Listen with a self-signed TLS certificate
  -print-owner-public type
        Print owner public key of type and exit
  -resale-guid guid
        Voucher guid to extend for resale
  -resale-key path
        The path to a PEM-encoded x.509 public key for the next owner
  -reuse-cred
        Perform the Credential Reuse Protocol in TO2
  -rv-bypass
        Skip TO1
  -rv-delay seconds
        Delay TO1 by N seconds
  -to0 addr
        Rendezvous server address to register RV blobs (disables self-registration)
  -to0-guid guid
        Device guid to immediately register an RV blob (requires to0 flag)
  -upload file
        Use fdo.upload FSIM for each file (flag may be used multiple times)
  -upload-dir path
        The directory path to put file uploads (default "uploads")
  -wget url
        Use fdo.wget FSIM for each url (flag may be used multiple times)

Key types:
  - RSA2048RESTR
  - RSAPKCS
  - RSAPSS
  - SECP256R1
  - SECP384R1

Encryption suites:
  - A128GCM
  - A192GCM
  - A256GCM
  - AES-CCM-64-128-128 (not implemented)
  - AES-CCM-64-128-256 (not implemented)
  - COSEAES128CBC
  - COSEAES128CTR
  - COSEAES256CBC
  - COSEAES256CTR

Key exchange suites:
  - DHKEXid14
  - DHKEXid15
  - ASYMKEX2048
  - ASYMKEX3072
  - ECDH256
  - ECDH384
Testing Device Onboard

First, start a server in a separate console.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -db ./test.db
[2024-09-01 00:00:00] INFO: Listening
  local: 127.0.0.1:9999
  external: 127.0.0.1:9999

Then DI, followed by TO1 and TO2 may be run. Passing the -debug flag allows message payloads to be viewed.

$ go run ./examples/cmd client -di http://127.0.0.1:9999
Success
$ go run ./examples/cmd client
Success

Running TO1 and TO2 again will fail, because the new voucher has not been registered for rendezvous.

$ go run ./examples/cmd client
[2024-09-01 00:00:00] ERROR: TO1 failed
  base URL: http://127.0.0.1:9999
  error: error received from TO1.HelloRV request: 2024-09-01 00:00:00 UTC [code=6,prevMsgType=30,id=0] not found
client error: transfer of ownership not successful
exit status 2

If the server had been started with the -rv-bypass flag, then the second onboarding attempt would have failed with not found, because unextended vouchers are not automatically allowed for re-onboarding.

[2024-09-01 00:00:00] ERROR: TO2 failed
  base URL: http://127.0.0.1:9999
  error: error received from TO2.HelloDevice request: 2024-09-01 00:00:00 UTC [code=6,prevMsgType=60,id=0] error retrieving voucher for device fa667c70e50b696086bbd8e05ba2773b: not found
client error: transfer of ownership not successful
exit status 2

To test repeatedly without the device credential changing, run the server with the -reuse-cred flag to enable the Credential Reuse Protocol.

Testing RV Blob Registration

First, start a server in a separate console.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -to0 http://127.0.0.1:9999 -db ./test.db
[2024-09-01 00:00:00] INFO: Listening
  local: 127.0.0.1:9999
  external: 127.0.0.1:9999

Next, initialize the device and check that TO1 fails.

$ 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
[2024-09-01 00:00:00] ERROR: TO1 failed
  base URL: http://127.0.0.1:9999
  error: error received from TO1.HelloRV request: 2024-09-01 00:00:00 +0000 UTC [code=6,prevMsgType=30,id=0] not found

Then register an RV blob with the server.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -to0 http://127.0.0.1:9999 -to0-guid d21d841a3f54f4e89a60ed9b9779e9e8 -db ./test.db
[2024-09-01 00:00:00] INFO: RV blob registered
  ttl: 1193046h28m15s

Finally, check that TO1 now succeeds.

$ go run ./examples/cmd client -rv-only
TO1 Blob: to1d[
  RV:
    - http://127.0.0.1:9999
  To0dHash:
    Algorithm: Sha256Hash
    Value: 340129067ad5839e2a5424baa3e7aa4bb984f610f29123b47b56353f47d71145
]
Testing Key Exchanges

First, start a server in a separate console.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -db ./test.db
[2024-09-01 00:00:00] INFO: Listening
  local: 127.0.0.1:9999
  external: 127.0.0.1:9999

Then DI, followed by TO1 and TO2 may be run.

Because in the example the device key type and owner key type will always match and to use ASYMKEX* key exchange the owner key must be RSA, the device key must also be RSA. To specify the device key type, use -di-key when running DI.

$ go run ./examples/cmd client -di http://127.0.0.1:9999 -di-key rsa2048
Success
$ go run ./examples/cmd client -kex ASYMKEX2048
Success
Testing Resale Protocol

First, start a server in a separate console.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -to0 http://127.0.0.1:9999 -db ./test.db
[2024-09-01 00:00:00] INFO: Listening
  local: 127.0.0.1:9999
  external: 127.0.0.1:9999

Next, initialize the device and perform transfer of ownership.

$ go run ./examples/cmd client -di http://127.0.0.1:9999
$ go run ./examples/cmd client
Success
$ go run ./examples/cmd client -print
blobcred[
  ...
  GUID          d21d841a3f54f4e89a60ed9b9779e9e8
  ...
]

Then, using a randomly-generated SHA384 public key, perform resale:

$ cat <<EOF >key.pem
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEqS9eSmpzrxw74krScl3+uOr5XU0nb3sZ
UB8rNQaXd7CACcjqlihEnJQIr3BWC6quWV8wnoghsW1zT6Ufw22yJ1twtkOphrW7
lw0a/66AlYljvN0Bq5RX924IWu8vlNz9
-----END PUBLIC KEY-----
EOF
$ go run ./examples/cmd server -resale-guid d21d841a3f54f4e89a60ed9b9779e9e8 -resale-key key.pem -db ./test.db
-----BEGIN OWNERSHIP VOUCHER-----
hRhlWOaGGGVQ18NXTN2UDTKMCY7F/ckKtYGDggxBAYIFSmlsb2NhbGhvc3SCA0MZ
H5BmZ290ZXN0gwsBWHgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZfbKj0Hfzztvd
BlxP6xvcNLArHhn2hHIetTOJ3jK/kMJljCyD/e7kEySuNI3ZkbanWQlwQJSNpdmc
WqurNM9rF6GP+ovDKiXtJk0wIEr7LVSbuk7KzAucy/rAimFAnk6COCpYMAQyXU7V
FfmqG8K3DtkUSPB102O8vN7cmVzDpbVmtWvlGtqUS01fkQFPS4vljVtZ8YIGWDD3
/LT9iLHTHCROt1FE9zApA9JBuOftcfDhnONYyWa2vfYfZ3T/fHQ65jS8edGn0DyC
WQGfMIIBmzCCASGgAwIBAgIRALr6K7WkGYUBYitf2Tfw5tMwCgYIKoZIzj0EAwMw
EjEQMA4GA1UEAxMHVGVzdCBDQTAgFw0yNDA5MTcwMTI0NThaGA8yMDU0MDQxMzAx
MjQ1OFowGDEWMBQGA1UEAxMNZGV2aWNlLmdvLWZkbzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABIKuaRfY831T//0D+qpVNznhj8iRRWUUEFQIR3h58ZKKaN+Grwrp+k5q
ov9tWvtM+/cbI+E2sD5XgwSJwHku2AkcBtGNsvohMkjq5OXXLtwLPmVi0CnAdXxS
NzNJNmofn6MzMDEwDgYDVR0PAQH/BAQDAgeAMB8GA1UdIwQYMBaAFOFx/qD3xlTs
iKpls6oIzO5tcta9MAoGCCqGSM49BAMDA2gAMGUCMCpfigiEdodr5oIB+9t93C8o
e1E99b4+/Zi316X9hCaYAsOLcXS9JvnNoJv1Pu4MfQIxAJAHV8199THTxVbTnoA0
VGkDlYAMgTNdRFl8fjINEFERjx5p9metcYhQdVWJDfWMrFkBiDCCAYQwggEKoAMC
AQICAQEwCgYIKoZIzj0EAwMwEjEQMA4GA1UEAxMHVGVzdCBDQTAgFw0yNDA5MTUx
NDI3MDhaGA8yMDU0MDkwODE0MjcwOFowEjEQMA4GA1UEAxMHVGVzdCBDQTB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABJoEXAUK7ZgV87mH49gI7XnFLw1k8vFPm4lxdTUz
F8lLMJHACcTXAnsYWaFCTKnyTA7avGimBLMGxIWWQH2kL2QhDsgM5XmAWRN4jD/E
cf1SEbUFwe7KNJFpGVWGZeTPSaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU4XH+oPfGVOyIqmWzqgjM7m1y1r0wCgYIKoZIzj0EAwMDaAAwZQIxAJ6TF7ms
PQb3fBx7kPH87ne9kkOu5fJAK1y+KrHdRNCwy+pmzbsLexx4wjookPpBEwIwMj1b
M1wAKzERNOnxhbKe17t9MgP54sNKpDjsKM6I7JSfOCOC83KYvAyBnF3cLKnxgdKE
RKEBOCKgWOqEgjgqWDBftCgxPk1Do9rcJHZcimJMwzvKgPUP5cSb+eUMelCOM3qi
xn9DM4Bf9fCIQoqy11aCOCpYMFehu5uT7NJQEXuy569NxVYYXX8ClhTH+HK6wDPN
9/SgPFXhxbQl9i/LcJh2lOCoBkGggwsBWHgwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AASpL15KanOvHDviStJyXf646vldTSdvexlQHys1Bpd3sIAJyOqWKESclAivcFYL
qq5ZXzCeiCGxbXNPpR/DbbInW3C2Q6mGtbuXDRr/roCViWO83QGrlFf3bgha7y+U
3P1YYPSf746ATSncxVbMYy+iAZwssR14hPDyqXz9RvMfF52a6Us6sKu06jd4Yprc
i2op2Hc819qjlgzt0kCmpOs75TtIIcOr2pSMy6pB+1bCr3QLdKH4bf7y8p9Hh8Tu
s0hciw==
-----END OWNERSHIP VOUCHER-----
Testing with a TPM

First, start a server in a separate console.

$ go run ./examples/cmd server -http 127.0.0.1:9999 -db ./test.db
[2024-09-01 00:00:00] INFO: Listening
  local: 127.0.0.1:9999
  external: 127.0.0.1:9999

Then run DI, with the TPM resource manager path specified. The key type must always be explicit through the -di-key flag.

$ go run ./examples/cmd client -di http://127.0.0.1:9999 -di-key ec384 -tpm /dev/tpmrm0
[2024-09-01 00:00:00] INFO: tpm: max input buffer size undefined, using default
  size: 1024
Success

Finally, run TO1/TO2.

$ go run ./examples/cmd client -di-key ec384 -tpm /dev/tpmrm0
[2024-09-01 00:00:00] INFO: tpm: max input buffer size undefined, using default
  size: 1024
Success

The TPM simulator may be used with 3 caveats:

  1. RSA3072 keys are not supported
  2. OpenSSL libraries and headers must be installed
  3. The executable must be built with cgo enabled
$ go run ./examples/cmd client -di http://127.0.0.1:9999 -di-key rsa2048 -tpm simulator
[2024-09-01 00:00:00] INFO: tpm: max input buffer size undefined, using default
  size: 1024
Success

$ go run ./examples/cmd client -di-key rsa2048 -tpm simulator
[2024-09-01 00:00:00] INFO: tpm: max input buffer size undefined, using default
  size: 1024
Success

FIPS Compliance

To build a FIPS 140-2 certifiable binary, use the Microsoft Go toolchain and be sure to deploy with a FIPS-compliant version of OpenSSL 3.0.

As an example, the following multi-stage Dockerfile will build the included example FDO application with FIPS-compliant crypto.

FROM mcr.microsoft.com/oss/go/microsoft/golang:1.23-fips-cbl-mariner2.0 AS build
WORKDIR /build
COPY . .
RUN go work; go work use -r . && \
    go build -tags=requirefips -o fdo ./examples/cmd

FROM gcr.io/distroless/cc-debian12
COPY --from=build /build/fdo .
# COPY in a FIPS-compliant OpenSSL 3.0 library!
ENTRYPOINT [ "./fdo" ]

Note that for FIPS certification, the NIST 800-108 key derivation function in internal/nistkdf/kdf.go would still need to be inspected.

Documentation

Overview

Package fdo implements FDO 1.1 protocol.

Many of the protocol types and values are located in the protocol subpackage. This domain package includes the core "entrypoint" types.

For client devices, DI is called, using an HMAC and private key, to generate a credential. After this, TO1 (unless using rendezvous bypass) and TO2 are called successively. When calling TO2, service info modules that the device is capable of performing are provided.

Device secrets (HMAC and private key) use interfaces from the Go standard library so there are many ways to generate and provide them. Two implementations are included in the library. [blob.DeviceCredential] stores secrets in a binary-encoded file and [tpm.DeviceCredential] uses unexportable keys secured inside a TPM 2.0.

For owner services, message handling protocol.Responder implementations are provided: DIServer, TO0Server, TO1Server, and TO2Server. These require state management.

There are a handful of state management interfaces to allow for combining backends. A practical example of this is that one may wish to store some state inside a JWT/CWT cookie, while more persistent state (lasts beyond a session) is stored in a SQL database. As an example implementation, [sqlite.DB] is provided in a separate, optional module, which runs SQLite inside a WASM runtime running as part of the same process.

The other type in this package is Voucher, which represents an FDO ownership voucher. It is not the direct input or output of either device or owner service protocols, since it is loaded/stored via the persistence interfaces. However, it is included for symmetry with DeviceCredential, as the anchor of trust (combined with the associated private key - same as device credential) on the opposite end of the device onboarding handshake.

Index

Constants

View Source
const DefaultRVBlobTTL = 4_294_967_295 // max uint32

DefaultRVBlobTTL is the default requested TTL for a rendezvous blob mapping.

Variables

View Source
var ErrCryptoVerifyFailed = errors.New("cryptographic verification failed")

ErrCryptoVerifyFailed indicates that the wrapping error originated from a case of cryptographic verification failing rather than a broken invariant.

View Source
var ErrInvalidSession = fmt.Errorf("invalid session")

ErrInvalidSession is used when the token is invalid or the backend fails during validation.

View Source
var ErrNotFound = fmt.Errorf("not found")

ErrNotFound is used when the resource does not exist for the session.

Functions

func TO1

func TO1(ctx context.Context, transport Transport, cred DeviceCredential, key crypto.Signer, opts *TO1Options) (*cose.Sign1[protocol.To1d, []byte], error)

TO1 runs the TO1 protocol and returns the owner service (TO2) addresses. It requires that a device credential, hmac secret, and key are all configured on the client.

Types

type AutoExtend

type AutoExtend interface {
	// ManufacturerKey returns the signer of a given key type and its certificate
	// chain (required).
	ManufacturerKey(keyType protocol.KeyType) (crypto.Signer, []*x509.Certificate, error)

	// OwnerKey returns the private key matching a given key type and optionally
	// its certificate chain.
	OwnerKey(keyType protocol.KeyType) (crypto.Signer, []*x509.Certificate, error)
}

AutoExtend provides the necessary methods for automatically extending a device voucher upon the completion of DI.

type AutoTO0

type AutoTO0 interface {
	// OwnerKey returns the private key matching a given key type and optionally
	// its certificate chain.
	OwnerKey(keyType protocol.KeyType) (crypto.Signer, []*x509.Certificate, error)

	// SetRVBlob sets the owner rendezvous blob for a device.
	SetRVBlob(context.Context, *Voucher, *cose.Sign1[protocol.To1d, []byte], time.Time) error
}

AutoTO0 provides the necessary methods for setting a rendezvous blob upon device voucher auto-extension.

type DIConfig

type DIConfig struct {
	// HMAC-SHA256 with a device secret that does not change when ownership is
	// transferred. HMAC-SHA256 support is always required by spec, so this
	// field must be non-nil.
	//
	// This hash.Hash may optionally implement the following interface to
	// return errors from Reset/Write/Sum, noting that implementations of
	// hash.Hash are not supposed to return non-nil errors from Write.
	//
	// 	type FallibleHash interface {
	// 		Err() error
	// 	}
	HmacSha256 hash.Hash

	// HMAC-SHA384 with a device secret that does not change when ownership is
	// transferred. HMAC-SHA384 support is optional by spec, so this field may
	// be nil iff Key is RSA 2048 or EC P-256.
	//
	// This hash.Hash may optionally implement the following interface to
	// return errors from Reset/Write/Sum, noting that implementations of
	// hash.Hash are not supposed to return non-nil errors from Write.
	//
	// 	type FallibleHash interface {
	// 		Err() error
	// 	}
	HmacSha384 hash.Hash

	// An ECDSA or RSA private key
	Key crypto.Signer

	// When true and an RSA key is used as a crypto.Signer argument, RSA-SSAPSS
	// will be used for signing
	PSS bool
}

DIConfig contains required device secrets and optional configuration.

type DIServer

type DIServer[T any] struct {
	Session  DISessionState
	Vouchers ManufacturerVoucherPersistentState

	// SignDeviceCertChain creates a device certificate chain based on info
	// provided in the DI.AppStart message.
	SignDeviceCertificate func(*T) ([]*x509.Certificate, error)

	// DeviceInfo returns the device info string to use for a given device,
	// based on its self-reported info and certificate chain.
	DeviceInfo func(context.Context, *T, []*x509.Certificate) (string, protocol.KeyType, protocol.KeyEncoding, error)

	// When set, new vouchers will be extended using the appropriate owner key.
	AutoExtend AutoExtend

	// When set, new vouchers will be registered for rendezvous.
	AutoTO0      AutoTO0
	AutoTO0Addrs []protocol.RvTO2Addr

	// Rendezvous directives
	RvInfo func(context.Context, *Voucher) ([][]protocol.RvInstruction, error)
}

DIServer implements the DI protocol.

func (*DIServer[T]) Respond

func (s *DIServer[T]) Respond(ctx context.Context, msgType uint8, msg io.Reader) (respType uint8, resp any)

Respond validates a request and returns the appropriate response message.

type DISessionState

type DISessionState interface {
	// SetDeviceCertChain sets the device certificate chain generated from
	// DI.AppStart info.
	SetDeviceCertChain(context.Context, []*x509.Certificate) error

	// DeviceCertChain gets a device certificate chain from the current
	// session.
	DeviceCertChain(context.Context) ([]*x509.Certificate, error)

	// SetIncompleteVoucherHeader stores an incomplete (missing HMAC) voucher
	// header tied to a session.
	SetIncompleteVoucherHeader(context.Context, *VoucherHeader) error

	// IncompleteVoucherHeader gets an incomplete (missing HMAC) voucher header
	// which has not yet been persisted.
	IncompleteVoucherHeader(context.Context) (*VoucherHeader, error)
}

DISessionState stores DI protocol state for a particular session.

type DeviceCredential

type DeviceCredential struct {
	Version       uint16
	DeviceInfo    string
	GUID          protocol.GUID
	RvInfo        [][]protocol.RvInstruction
	PublicKeyHash protocol.Hash // expected to be a hash of the entire CBOR structure (not just pkBody) for Voucher.VerifyEntries to succeed
}

DeviceCredential is non-normative, but the TPM Draft Spec proposes a CBOR encoding, so that will be used, excluding the key type/handle.

DCTPM = [
    DCProtVer: protver,
    DCDeviceInfo: tstr,
    DCGuid: bstr
    DCRVInfo: RendezvousInfo,
    DCPubKeyHash: Hash
    DeviceKeyType: uint
    DeviceKeyHandle: uint
]

func DI

func DI(ctx context.Context, transport Transport, info any, c DIConfig) (*DeviceCredential, error)

DI runs the DI protocol and returns the voucher header and manufacturer public key hash. It requires that the client is configured with an HMAC secret, but not necessarily a key.

The device is identified to the manufacturing component by the ID string, which may be a device serial, MAC address, or similar. There is generally an expectation of network trust for DI.

The device certificate chain should be created before DI is performed, because the manufacturing component signs the ownership voucher, but isn't necessarily the root of trust for the device's identity and may or may not validate the device's presented certificate chain.

However, the Java server implementation expects a certificate signing request marshaled in the device info and performs certificate signing, so PKI and voucher signing duties may be simultaneously handled by the manufacturing component.

func TO2

func TO2(ctx context.Context, transport Transport, to1d *cose.Sign1[protocol.To1d, []byte], c TO2Config) (*DeviceCredential, error)

TO2 runs the TO2 protocol and returns a DeviceCredential with replaced GUID, rendezvous info, and owner public key. It requires that a device credential, hmac secret, and key are all provided as configuration.

A to1d signed blob is expected if rendezvous bypass is not used. This blob is output from TO1.

It has the side effect of performing service info modules, which may include actions such as downloading files.

If the Credential Reuse protocol is allowed and occurs, then the returned device credential will be nil.

type ErrUnsupportedKeyType

type ErrUnsupportedKeyType protocol.KeyType

ErrUnsupportedKeyType is used when no key of the given type has been added to the server.

func (ErrUnsupportedKeyType) Error

func (err ErrUnsupportedKeyType) Error() string

type ManufacturerVoucherPersistentState

type ManufacturerVoucherPersistentState interface {
	// NewVoucher creates and stores a voucher for a newly initialized device.
	// Note that the voucher may have entries if the server was configured for
	// auto voucher extension.
	NewVoucher(context.Context, *Voucher) error
}

ManufacturerVoucherPersistentState maintains vouchers created during DI which have not yet been extended.

type OwnerKeyPersistentState

type OwnerKeyPersistentState interface {
	// OwnerKey returns the private key matching a given key type and optionally
	// its certificate chain.
	OwnerKey(protocol.KeyType) (crypto.Signer, []*x509.Certificate, error)
}

OwnerKeyPersistentState maintains the owner service keys.

type OwnerVoucherPersistentState

type OwnerVoucherPersistentState interface {
	// AddVoucher stores the voucher of a device owned by the service.
	AddVoucher(context.Context, *Voucher) error

	// ReplaceVoucher stores a new voucher, possibly deleting or marking the
	// previous voucher as replaced.
	ReplaceVoucher(context.Context, protocol.GUID, *Voucher) error

	// RemoveVoucher untracks a voucher, possibly by deleting it or marking it
	// as removed, and returns it for extension.
	RemoveVoucher(context.Context, protocol.GUID) (*Voucher, error)

	// Voucher retrieves a voucher by GUID.
	Voucher(context.Context, protocol.GUID) (*Voucher, error)
}

OwnerVoucherPersistentState maintains vouchers owned by the service.

type RendezvousBlobPersistentState

type RendezvousBlobPersistentState interface {
	// SetRVBlob sets the owner rendezvous blob for a device.
	SetRVBlob(context.Context, *Voucher, *cose.Sign1[protocol.To1d, []byte], time.Time) error

	// RVBlob returns the owner rendezvous blob for a device.
	RVBlob(context.Context, protocol.GUID) (*cose.Sign1[protocol.To1d, []byte], *Voucher, error)
}

RendezvousBlobPersistentState maintains device to owner info state used in TO0 and TO1.

type TO0Client

type TO0Client struct {
	// Vouchers is used to lookup the ownership voucher for registering a
	// rendezvous blob for a given device.
	Vouchers OwnerVoucherPersistentState

	// OwnerKeys are used for signing the rendezvous blob.
	OwnerKeys OwnerKeyPersistentState

	// TTL is the amount of time to recommend that the Rendezvous Server allows
	// the rendezvous blob mapping to remain active.
	//
	// If TTL is 0, [DefaultRVBlobTTL] will be used.
	TTL uint32
}

TO0Client is used by owner services communicating with rendezvous services. Unlike [DOClient], it is not used by devices.

func (*TO0Client) RegisterBlob

func (c *TO0Client) RegisterBlob(ctx context.Context, transport Transport, guid protocol.GUID, addrs []protocol.RvTO2Addr) (uint32, error)

RegisterBlob tells a Rendezvous Server where to direct a given device to its owner service for onboarding. The returned uint32 is the number of seconds before the rendezvous blob must be refreshed by calling [RegisterBlob] again.

type TO0Server

type TO0Server struct {
	Session TO0SessionState
	RVBlobs RendezvousBlobPersistentState

	// AcceptVoucher is an optional function which, when given, is used to
	// determine whether to accept a voucher from a client.
	//
	// If AcceptVoucher is not set, then all vouchers will be accepted. It is
	// expected that some other means of authorization is used in this case.
	AcceptVoucher func(context.Context, Voucher) (accept bool, err error)

	// NegotiateTTL is an optional function to select a validity period for a
	// rendezvous blob based on the requested number of seconds and the
	// ownership voucher contents.
	//
	// If NegotiateTTL is not set, the requested TTL will be used.
	NegotiateTTL func(requestedSeconds uint32, ov Voucher) (waitSeconds uint32)
}

TO0Server implements the TO0 protocol.

func (*TO0Server) Respond

func (s *TO0Server) Respond(ctx context.Context, msgType uint8, msg io.Reader) (respType uint8, resp any)

Respond validates a request and returns the appropriate response message.

type TO0SessionState

type TO0SessionState interface {
	// SetTO0SignNonce sets the Nonce expected in TO0.OwnerSign.
	SetTO0SignNonce(context.Context, protocol.Nonce) error

	// TO0SignNonce returns the Nonce expected in TO0.OwnerSign.
	TO0SignNonce(context.Context) (protocol.Nonce, error)
}

TO0SessionState stores TO0 protocol state for a particular session.

type TO1Options

type TO1Options struct {
	// When true and an RSA key is used as a crypto.Signer argument, RSA-SSAPSS
	// will be used for signing.
	PSS bool
}

TO1Options contains optional configuration values.

type TO1Server

type TO1Server struct {
	Session TO1SessionState
	RVBlobs RendezvousBlobPersistentState
}

TO1Server implements the TO1 protocol.

func (*TO1Server) Respond

func (s *TO1Server) Respond(ctx context.Context, msgType uint8, msg io.Reader) (respType uint8, resp any)

Respond validates a request and returns the appropriate response message.

type TO1SessionState

type TO1SessionState interface {
	// SetTO1ProofNonce sets the Nonce expected in TO1.ProveToRV.
	SetTO1ProofNonce(context.Context, protocol.Nonce) error

	// TO1ProofNonce returns the Nonce expected in TO1.ProveToRV.
	TO1ProofNonce(context.Context) (protocol.Nonce, error)
}

TO1SessionState stores TO1 protocol state for a particular session.

type TO2Config

type TO2Config struct {
	// Non-secret device credential data.
	Cred DeviceCredential

	// HMAC-SHA256 with a device secret that does not change when ownership is
	// transferred. HMAC-SHA256 support is always required by spec, so this
	// field must be non-nil.
	//
	// This hash.Hash may optionally implement the following interface to
	// return errors from Reset/Write/Sum, noting that implementations of
	// hash.Hash are not supposed to return non-nil errors from Write.
	//
	// 	type FallibleHash interface {
	// 		Err() error
	// 	}
	HmacSha256 hash.Hash

	// HMAC-SHA384 with a device secret that does not change when ownership is
	// transferred. HMAC-SHA384 support is optional by spec, so this field may
	// be nil iff Key is RSA 2048 or EC P-256.
	//
	// This hash.Hash may optionally implement the following interface to
	// return errors from Reset/Write/Sum, noting that implementations of
	// hash.Hash are not supposed to return non-nil errors from Write.
	//
	// 	type FallibleHash interface {
	// 		Err() error
	// 	}
	HmacSha384 hash.Hash

	// An ECDSA or RSA private key that may or may not be implemented with the
	// stdlib ecdsa and rsa packages.
	Key crypto.Signer

	// When true and an RSA key is used as a crypto.Signer argument, RSA-SSAPSS
	// will be used for signing.
	PSS bool

	// Devmod contains all required and any number of optional messages.
	//
	// Alternatively to setting this field, a devmod module may be provided in
	// the arguments to TransferOwnership2 where the module must provide any
	// devmod messages EXCEPT nummodules and modules via its Yield method.
	//
	// Note: The device plugin will be yielded to exactly once and is expected
	// to provide all required and desired fields and yield. It may then exit.
	Devmod serviceinfo.Devmod

	// Each ServiceInfo module will be reported in devmod and potentially
	// activated and used. If a devmod module is included in this list, it
	// overrides the Devmod field in TO2Config. The custom devmod should not
	// send nummodules or modules messages, as these will always be sent upon
	// module completion.
	DeviceModules map[string]serviceinfo.DeviceModule

	// Selects the key exchange suite to use. If unset, it defaults to ECDH384.
	KeyExchange kex.Suite

	// Selects the cipher suite to use for encryption. If unset, it defaults to
	// A256GCM.
	CipherSuite kex.CipherSuiteID

	// Maximum transmission unit (MTU) to tell owner service to send with. If
	// zero, the default of 1300 will be used. The value chosen can make a
	// difference for performance when using service info to exchange large
	// amounts of data, but choosing the best value depends on network
	// configuration (e.g. jumbo packets) and transport (overhead size).
	MaxServiceInfoSizeReceive uint16

	// Allow for the Credential Reuse Protocol (Section 7) to be used. If not
	// enabled, TO2 will fail with CredReuseErrCode (102) if reuse is
	// attempted by the owner service.
	AllowCredentialReuse bool
}

TO2Config contains the device credential, including secrets and keys, optional configuration, and service info modules.

type TO2Server

type TO2Server struct {
	Session   TO2SessionState
	Vouchers  OwnerVoucherPersistentState
	OwnerKeys OwnerKeyPersistentState

	// Choose the replacement rendezvous directives based on the current
	// voucher of the onboarding device.
	RvInfo func(context.Context, Voucher) ([][]protocol.RvInstruction, error)

	// Create an iterator of service info modules for a given device. The
	// iterator returns the name of the module and its implementation.
	OwnerModules func(ctx context.Context, replacementGUID protocol.GUID, info string, chain []*x509.Certificate, devmod serviceinfo.Devmod, modules []string) iter.Seq2[string, serviceinfo.OwnerModule]

	// ReuseCredential, if not nil, will be called to determine whether to
	// apply the Credential Reuse Protocol based on the current voucher of an
	// onboarding device.
	ReuseCredential func(context.Context, Voucher) bool

	// VerifyVoucher, if not nil, will be called before creating and responding
	// with a TO2.ProveOVHdr message. Any error will cause TO2 to fail with a
	// not found status code.
	//
	// If VerifyVoucher is nil, the default behavior is to reject all vouchers
	// with zero extensions.
	VerifyVoucher func(context.Context, Voucher) error

	// Optional configuration
	MaxDeviceServiceInfoSize uint16
	// contains filtered or unexported fields
}

TO2Server implements the TO2 protocol.

func (*TO2Server) CryptSession

func (s *TO2Server) CryptSession(ctx context.Context) (kex.Session, error)

CryptSession returns the current encryption session.

func (*TO2Server) Resell

func (s *TO2Server) Resell(ctx context.Context, guid protocol.GUID, nextOwner crypto.PublicKey, extra map[int][]byte) (*Voucher, error)

Resell implements the FDO Resale Protocol by removing a voucher from ownership, extending it to a new owner, and then returning it for out-of-band transport.

func (*TO2Server) Respond

func (s *TO2Server) Respond(ctx context.Context, msgType uint8, msg io.Reader) (respType uint8, resp any)

Respond validates a request and returns the appropriate response message.

type TO2SessionState

type TO2SessionState interface {
	// SetGUID associates a voucher GUID with a TO2 session.
	SetGUID(context.Context, protocol.GUID) error

	// GUID retrieves the GUID of the voucher associated with the session.
	GUID(context.Context) (protocol.GUID, error)

	// SetRvInfo stores the rendezvous instructions to store at the end of TO2.
	SetRvInfo(context.Context, [][]protocol.RvInstruction) error

	// RvInfo retrieves the rendezvous instructions to store at the end of TO2.
	RvInfo(context.Context) ([][]protocol.RvInstruction, error)

	// SetReplacementGUID stores the device GUID to persist at the end of TO2.
	SetReplacementGUID(context.Context, protocol.GUID) error

	// ReplacementGUID retrieves the device GUID to persist at the end of TO2.
	ReplacementGUID(context.Context) (protocol.GUID, error)

	// SetReplacementHmac stores the voucher HMAC to persist at the end of TO2.
	SetReplacementHmac(context.Context, protocol.Hmac) error

	// ReplacementHmac retrieves the voucher HMAC to persist at the end of TO2.
	ReplacementHmac(context.Context) (protocol.Hmac, error)

	// SetXSession updates the current key exchange/encryption session based on
	// an opaque "authorization" token.
	//
	// The Session value is not safe to use after the function returns.
	SetXSession(context.Context, kex.Suite, kex.Session) error

	// XSession returns the current key exchange/encryption session based on an
	// opaque "authorization" token.
	XSession(context.Context) (kex.Suite, kex.Session, error)

	// SetProveDeviceNonce stores the Nonce used in TO2.ProveDevice for use in
	// TO2.Done.
	SetProveDeviceNonce(context.Context, protocol.Nonce) error

	// ProveDeviceNonce returns the Nonce used in TO2.ProveDevice and TO2.Done.
	ProveDeviceNonce(context.Context) (protocol.Nonce, error)

	// SetSetupDeviceNonce stores the Nonce used in TO2.SetupDevice for use in
	// TO2.Done2.
	SetSetupDeviceNonce(context.Context, protocol.Nonce) error

	// SetupDeviceNonce returns the Nonce used in TO2.SetupDevice and
	// TO2.Done2.
	SetupDeviceNonce(context.Context) (protocol.Nonce, error)

	// SetMTU sets the max service info size the device may receive.
	SetMTU(context.Context, uint16) error

	// MTU returns the max service info size the device may receive.
	MTU(context.Context) (uint16, error)
}

TO2SessionState stores TO2 protocol state for a particular session.

type Transport

type Transport interface {
	// Send a message and receive a response. The response reader should always
	// be closed.
	Send(ctx context.Context, msgType uint8, msg any, sess kex.Session) (respType uint8, _ io.ReadCloser, _ error)
}

Transport abstracts the underlying TCP/HTTP/CoAP transport for sending a message and receiving a response.

type Voucher

type Voucher struct {
	Version   uint16
	Header    cbor.Bstr[VoucherHeader]
	Hmac      protocol.Hmac
	CertChain *[]*cbor.X509Certificate
	Entries   []cose.Sign1Tag[VoucherEntryPayload, []byte]
}

Voucher is the top level structure.

OwnershipVoucher = [
    OVProtVer:      protver,           ;; protocol version
    OVHeaderTag:    bstr .cbor OVHeader,
    OVHeaderHMac:   HMac,              ;; hmac[DCHmacSecret, OVHeader]
    OVDevCertChain: OVDevCertChainOrNull,
    OVEntryArray:   OVEntries
]

;; Device certificate chain
;; use null for Intel® EPID.
OVDevCertChainOrNull     = X5CHAIN / null  ;; CBOR null for Intel® EPID device key

;; Ownership voucher entries array
OVEntries = [ * OVEntry ]

func ExtendVoucher

func ExtendVoucher[T protocol.PublicKeyOrChain](v *Voucher, owner crypto.Signer, nextOwner T, extra map[int][]byte) (*Voucher, error)

ExtendVoucher adds a new signed voucher entry to the list and returns the new extended voucher. Vouchers should be treated as immutable structures.

ExtraInfo may be used to pass additional supply-chain information along with the Ownership Voucher. The Device implicitly verifies the plaintext of OVEExtra along with the verification of the Ownership Voucher. An Owner which trusts the Device' verification of the Ownership Voucher may also choose to trust OVEExtra.

func (*Voucher) DevicePublicKey

func (v *Voucher) DevicePublicKey() (crypto.PublicKey, error)

DevicePublicKey extracts the device's public key from from the certificate chain. Before calling this method, the voucher must be fully verified. For certain key types, such as Intel EPID, the public key will be nil.

func (*Voucher) OwnerPublicKey

func (v *Voucher) OwnerPublicKey() (crypto.PublicKey, error)

OwnerPublicKey extracts the voucher owner's public key from either the header or the entries list.

func (*Voucher) VerifyCertChainHash

func (v *Voucher) VerifyCertChainHash() error

VerifyCertChainHash uses the hash in the voucher header to verify that the certificate chain of the voucher has not been tampered with. This method should therefore not be called before VerifyHeader.

func (*Voucher) VerifyDeviceCertChain

func (v *Voucher) VerifyDeviceCertChain(roots *x509.CertPool) error

VerifyDeviceCertChain using trusted roots. If roots is nil then the last certificate in the chain will be implicitly trusted.

func (*Voucher) VerifyEntries

func (v *Voucher) VerifyEntries() error

VerifyEntries checks the chain of signatures on each voucher entry payload.

func (*Voucher) VerifyHeader

func (v *Voucher) VerifyHeader(hmacSha256, hmacSha384 hash.Hash) error

VerifyHeader checks that the OVHeader was not modified by comparing the HMAC generated using the secret from the device credentials.

func (*Voucher) VerifyManufacturerCertChain

func (v *Voucher) VerifyManufacturerCertChain(roots *x509.CertPool) error

VerifyManufacturerCertChain using trusted roots. If roots is nil then the last certificate in the chain will be implicitly trusted.

If the manufacturer public key is X509 encoded rather than X5Chain, then this method will fail if a non-nil root certificate pool is given.

func (*Voucher) VerifyManufacturerKey

func (v *Voucher) VerifyManufacturerKey(keyHash protocol.Hash) error

VerifyManufacturerKey by using a public key hash (generally stored as part of the device credential).

type VoucherEntryPayload

type VoucherEntryPayload struct {
	PreviousHash protocol.Hash
	HeaderHash   protocol.Hash
	Extra        *cbor.Bstr[map[int][]byte]
	PublicKey    protocol.PublicKey
}

VoucherEntryPayload is an entry in a voucher's list of recorded transfers.

;; ...each entry is a COSE Sign1 object with a payload OVEntry = CoseSignature $COSEProtectedHeaders //= (

1: OVSignType

) $COSEPayloads /= (

OVEntryPayload

) ;; ... each payload contains the hash of the previous entry ;; and the signature of the public key to verify the next signature ;; (or the Owner, in the last entry). OVEntryPayload = [

OVEHashPrevEntry: Hash,
OVEHashHdrInfo:   Hash,  ;; hash[GUID||DeviceInfo] in header
OVEExtra:         null / bstr .cbor OVEExtraInfo
OVEPubKey:        PublicKey

]

OVEExtraInfo = { * OVEExtraInfoType: bstr } OVEExtraInfoType = int

;;OVSignType = Supporting COSE signature types

func (*VoucherEntryPayload) VerifyOwnerCertChain

func (e *VoucherEntryPayload) VerifyOwnerCertChain(roots *x509.CertPool) error

VerifyOwnerCertChain validates the certificate chain of the owner public key using trusted roots. If roots is nil then the last certificate in the chain will be implicitly trusted. If the public key is X509 encoded rather than X5Chain, then this method will fail if a non-nil root certificate pool is given.

type VoucherHeader

type VoucherHeader struct {
	Version         uint16
	GUID            protocol.GUID
	RvInfo          [][]protocol.RvInstruction
	DeviceInfo      string
	ManufacturerKey protocol.PublicKey
	CertChainHash   *protocol.Hash
}

VoucherHeader is the Ownership Voucher header, also used in TO1 protocol.

OVHeader = [
    OVHProtVer:        protver,        ;; protocol version
    OVGuid:            Guid,           ;; guid
    OVRVInfo:          RendezvousInfo, ;; rendezvous instructions
    OVDeviceInfo:      tstr,           ;; DeviceInfo
    OVPubKey:          PublicKey,      ;; mfg public key
    OVDevCertChainHash:OVDevCertChainHashOrNull
]

;; Hash of Device certificate chain
;; use null for Intel® EPID
OVDevCertChainHashOrNull = Hash / null     ;; CBOR null for Intel® EPID device key

func (*VoucherHeader) Equal

func (ovh *VoucherHeader) Equal(otherOVH *VoucherHeader) bool

Equal compares two ownership voucher headers for equality.

Directories

Path Synopsis
Package blob implements a device credential that may be stored to disk as a marshaled blob.
Package blob implements a device credential that may be stored to disk as a marshaled blob.
Package cbor implements a basic encoding/decoding API for RFC 8949 Concise Binary Object Representation (CBOR).
Package cbor implements a basic encoding/decoding API for RFC 8949 Concise Binary Object Representation (CBOR).
cdn
Package cdn implements CBOR Diagnotic Notation.
Package cdn implements CBOR Diagnotic Notation.
Package cose implements CBOR Object Signing and Encryption (COSE) defined in RFC8152.
Package cose implements CBOR Object Signing and Encryption (COSE) defined in RFC8152.
Package custom implements non-normative types and functions for DI.
Package custom implements non-normative types and functions for DI.
examples module
Package fdotest contains test harnesses for the main fdo package.
Package fdotest contains test harnesses for the main fdo package.
internal
Package internal includes internal test helpers.
Package internal includes internal test helpers.
internal/memory
Package memory implements server state using non-persistent memory to complement internal/token.Service for state that must persist between protocol sessions.
Package memory implements server state using non-persistent memory to complement internal/token.Service for state that must persist between protocol sessions.
internal/token
Package token implements all server state interfaces possible using a stateless token.
Package token implements all server state interfaces possible using a stateless token.
fsim module
Package http implements FDO transport interfaces using an HTTP protocol.
Package http implements FDO transport interfaces using an HTTP protocol.
internal/httputil
Package httputil implements APIs misssing from the TinyGo stdlib.
Package httputil implements APIs misssing from the TinyGo stdlib.
internal
build
Package build is used to hold code related to build tags.
Package build is used to hold code related to build tags.
nistkdf
Package nistkdf implements a NIST 800-108 KDF using the parameters defined in FDO.
Package nistkdf implements a NIST 800-108 KDF using the parameters defined in FDO.
Package kex implements the Key Exchange subprotocol of FDO.
Package kex implements the Key Exchange subprotocol of FDO.
Package plugin defines a line-based protocol and implements service info device and owner module adapters to plugins communicating over a reader- writer pair.
Package plugin defines a line-based protocol and implements service info device and owner module adapters to plugins communicating over a reader- writer pair.
Package protocol contains common protocol-related types and values.
Package protocol contains common protocol-related types and values.
Package serviceinfo handles FDO Service Info and Service Info Modules.
Package serviceinfo handles FDO Service Info and Service Info Modules.
sqlite module
tpm module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL