diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e0871f9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1ccf12a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: Build + +on: + push: + pull_request: + workflow_dispatch: + workflow_call: + inputs: + branch: + description: "Branch name to use" + required: true + type: string + +jobs: + build: + runs-on: "ubuntu-22.04" + steps: + - uses: actions/checkout@v3 + with: + repository: openpixelsystems/go-fiovb + fetch-depth: "0" + ref: ${{ inputs.branch || github.ref}} + + - name: Install dependencies + run: sudo apt install gcc make cmake gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu golang-go + + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Build fiovb (amd64) + run: make amd64 HOST_COMPILER=gcc + + - name: Build fiovb (arm64) + run: | + CROSS_COMPILER=aarch64-linux-gnu-gcc && \ + export CC_FOR_TARGET=$CROSS_COMPILER && \ + export CC=$CROSS_COMPILER && \ + make arm64 CROSS_COMPILER=$CROSS_COMPILER diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea1472e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f33b8eb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third-party/optee-client"] + path = third-party/optee-client + url = https://github.com/OP-TEE/optee_client.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b1ffec3 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +################################################################################ +### fiovb - Makefile ### +### Date: 08/01/2024 ### +### Version: v1.0.0 ### +################################################################################ + +GO = go +OUTPUT = output + +################################################################################ +### ALL ### +################################################################################ + +all: amd64 arm64 +clean: amd64-clean arm64-clean + +################################################################################ +### INCLUDES ### +################################################################################ + +include build/amd64.mk build/arm64.mk \ No newline at end of file diff --git a/README.md b/README.md index 2bbed7d..0ef09bf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Go fiovb library +[![Build](https://github.com/OpenPixelSystems/go-fiovb/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/OpenPixelSystems/go-fiovb/actions/workflows/build.yml) + ## Summary The `go-fiovb` is a Go library for the Foundries.IO Verified Boot. diff --git a/build/amd64.mk b/build/amd64.mk new file mode 100644 index 0000000..5f82ec3 --- /dev/null +++ b/build/amd64.mk @@ -0,0 +1,60 @@ +################################################################################ +### fiovb - amd64 ### +### Date: 08/01/2024 ### +### Version: v1.0.0 ### +################################################################################ + +HOST_COMPILER = "gcc" +HOST_COMPILER_C = $(shell readlink -f $$(which $(HOST_COMPILER))) + +AMD64 = amd64 +BUILD_AMD64 = $(OUTPUT)/$(AMD64)/build +SYSROOT_AMD64 = $(OUTPUT)/$(AMD64)/sysroot +BIN_AMD64 = $(OUTPUT)/$(AMD64)/bin +PREFIX_AMD64 = GOOS=linux GOARCH=$(AMD64) + +amd64: optee-client-amd64 fiovb-tool-amd64 +amd64-clean: optee-client-clean-amd64 sysroot-clean-amd64 fiovb-tool-clean-amd64 + +################################################################################ +### OPTEE-CLIENT ### +################################################################################ + +optee-client-amd64: + @cmake -Sthird-party/optee-client/ \ + -B$(BUILD_AMD64)/$@ \ + -DCMAKE_INSTALL_PREFIX=$(SYSROOT_AMD64)/usr \ + -DCMAKE_C_COMPILER=$(HOST_COMPILER_C) + + @make -C $(BUILD_AMD64)/$@ + @make -C $(BUILD_AMD64)/$@ install + + @echo Compiled $@ + +optee-client-clean-amd64: + @rm -rf $(BUILD_AMD64)/optee-client-amd64 + @echo Cleaned optee-client-amd64 + +################################################################################ +### SYSROOT ### +################################################################################ + +sysroot-clean-amd64: + @rm -rf $(SYSROOT_AMD64) + @echo Cleaned sysroot-amd64 + +################################################################################ +### FIOVB TOOL ### +################################################################################ + +fiovb-tool-amd64: + @$(PREFIX_AMD64) \ + CGO_CFLAGS="-g -Wall -I$(shell pwd)/$(SYSROOT_AMD64)/usr/include" \ + CGO_LDFLAGS="-L$(shell pwd)/$(SYSROOT_AMD64)/usr/lib -L$(shell pwd)/$(SYSROOT_AMD64)/usr/lib64 -lteec" \ + $(GO) build -o $(BIN_AMD64)/fiovb github.com/OpenPixelSystems/go-fiovb/cmd/fiovb/ + + @echo Compiled $@ + +fiovb-tool-clean-amd64: + @rm -rf $(BIN_AMD64)/fiovb + @echo Cleaned fiovb-tool-amd64 \ No newline at end of file diff --git a/build/arm64.mk b/build/arm64.mk new file mode 100644 index 0000000..e956a86 --- /dev/null +++ b/build/arm64.mk @@ -0,0 +1,60 @@ +################################################################################ +### fiovb - arm64 ### +### Date: 08/01/2024 ### +### Version: v1.0.0 ### +################################################################################ + +CROSS_COMPILER = aarch64-lmp-linux-gcc +CROSS_COMPILER_C = $(shell readlink -f $$(which $(CROSS_COMPILER))) + +ARM64 = arm64 +BUILD_ARM64 = $(OUTPUT)/$(ARM64)/build +SYSROOT_ARM64 = $(OUTPUT)/$(ARM64)/sysroot +BIN_ARM64 = $(OUTPUT)/$(ARM64)/bin +PREFIX_ARM64 = CGO_ENABLED=1 GOOS=linux GOARCH=$(ARM64) + +arm64: optee-client-arm64 fiovb-tool-arm64 +arm64-clean: optee-client-clean-arm64 sysroot-clean-arm64 fiovb-tool-clean-arm64 + +################################################################################ +### OPTEE-CLIENT ### +################################################################################ + +optee-client-arm64: + @cmake -Sthird-party/optee-client/ \ + -B$(BUILD_ARM64)/$@ \ + -DCMAKE_INSTALL_PREFIX=$(SYSROOT_ARM64)/usr \ + -DCMAKE_C_COMPILER=$(CROSS_COMPILER_C) + + @make -C $(BUILD_ARM64)/$@ + @make -C $(BUILD_ARM64)/$@ install + + @echo Compiled $@ + +optee-client-clean-arm64: + @rm -rf $(BUILD_ARM64)/optee-client-arm64 + @echo Cleaned optee-client-arm64 + +################################################################################ +### SYSROOT ### +################################################################################ + +sysroot-clean-arm64: + @rm -rf $(SYSROOT_ARM64) + @echo Cleaned sysroot-arm64 + +################################################################################ +### FIOVB TOOL ### +################################################################################ + +fiovb-tool-arm64: + @$(PREFIX_ARM64) \ + CGO_CFLAGS="-g -Wall -I$(shell pwd)/$(SYSROOT_ARM64)/usr/include" \ + CGO_LDFLAGS="-L$(shell pwd)/$(SYSROOT_ARM64)/usr/lib -lteec" \ + $(GO) build -o $(BIN_ARM64)/fiovb github.com/OpenPixelSystems/go-fiovb/cmd/fiovb/ + + @echo Compiled $@ + +fiovb-tool-clean-arm64: + @rm -rf $(BIN_ARM64)/fiovb + @echo Cleaned fiovb-tool-arm64 \ No newline at end of file diff --git a/cmd/fiovb/main.go b/cmd/fiovb/main.go new file mode 100644 index 0000000..ffe93ff --- /dev/null +++ b/cmd/fiovb/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/user" + + "github.com/OpenPixelSystems/go-fiovb/fiovb" +) + +type Mode int + +const ( + unknownMode Mode = iota + readMode +) + +func isRootUser() bool { + u, err := user.Current() + if err != nil { + return false + } + + return os.Geteuid() == 0 && os.Getuid() == 0 && u.Username == "root" +} + +func parseArgs() (Mode, string) { + var read = flag.String("read", "", "read value") + + flag.Parse() + + if read != nil && *read != "" { + return readMode, *read + } + + return unknownMode, "" +} + +func read(name string) error { + fvb := fiovb.New() + + if err := fvb.Initialize(); err != nil { + return err + } + + value, err := fvb.Read(name) + if err != nil { + return err + } + + if err := fvb.Finalize(); err != nil { + return err + } + + fmt.Println(value) + return nil +} + +func main() { + if !isRootUser() { + fmt.Println("Permission denied") + return + } + + mode, name := parseArgs() + if mode == unknownMode { + flag.Usage() + return + } + + switch mode { + case readMode: + if err := read(name); err != nil { + fmt.Println(err) + os.Exit(1) + } + } +} diff --git a/fiovb/fiovb_api.go b/fiovb/fiovb_api.go new file mode 100644 index 0000000..56d7091 --- /dev/null +++ b/fiovb/fiovb_api.go @@ -0,0 +1,81 @@ +package fiovb + +import "github.com/OpenPixelSystems/go-fiovb/teec" + +const ( + readPersistValue = 0 + writePersistValue = 1 + deletePersistValue = 2 + maxBuffer = 4096 +) + +var ( + // Universally Unique IDentifier (UUID) as defined in RFC4122. + // These UUID values are used to identify the fiovb Trusted Application. + destination = teec.UUID{ + TimeLow: 0x22250a54, + TimeMid: 0x0bf1, + TimeHiAndVersion: 0x48fe, + ClockSeqAndNode: [8]byte{0x80, 0x02, 0x7b, 0x20, 0xf1, 0xc9, 0xc9, 0xb1}, + } +) + +type FIOVB struct { + t *teec.TEEC +} + +func New() *FIOVB { + return &FIOVB{ + t: teec.New(), + } +} + +func (fiovb *FIOVB) Initialize() error { + if err := fiovb.t.Initialize(); err != nil { + return err + } + + if err := fiovb.t.OpenSession(destination); err != nil { + return err + } + + return nil +} + +func (fiovb *FIOVB) Read(name string) (string, error) { + req := []byte(name) + res := make([]byte, maxBuffer-1) + + operation := teec.Operation{ + ParamTypes: [4]teec.ParameterTypes{ + teec.MEMREF_TEMP_INPUT, + teec.MEMREF_TEMP_INOUT, + teec.NONE, + teec.NONE, + }, + Params: [4]teec.Parameter{ + teec.Parameter{Buffer: req, Size: uint32(len(req) + 1)}, + teec.Parameter{Buffer: res, Size: uint32(len(res))}, + }, + } + + origin := uint32(0) + + if err := fiovb.t.InvokeCommand(readPersistValue, &operation, &origin); err != nil { + return "", err + } + + return string(operation.Params[1].Buffer), nil +} + +func (fiovb *FIOVB) Finalize() error { + if err := fiovb.t.CloseSession(); err != nil { + return err + } + + if err := fiovb.t.Finalize(); err != nil { + return err + } + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1d1d48c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/OpenPixelSystems/go-fiovb + +go 1.19 diff --git a/teec/teec_client_api.go b/teec/teec_client_api.go new file mode 100644 index 0000000..a788d24 --- /dev/null +++ b/teec/teec_client_api.go @@ -0,0 +1,278 @@ +package teec + +/* +#include "tee_client_api.h" +#include +#include +#include + +void fillTempMemoryReference(TEEC_Parameter *parameter, void *buffer, size_t size) +{ + parameter->tmpref.buffer = buffer; + parameter->tmpref.size = size; +} +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +type ParameterTypes uint32 + +const ( + NONE ParameterTypes = 0x00000000 + VALUE_INPUT ParameterTypes = 0x00000001 + VALUE_OUTPUT ParameterTypes = 0x00000002 + VALUE_INOUT ParameterTypes = 0x00000003 + MEMREF_TEMP_INPUT ParameterTypes = 0x00000005 + MEMREF_TEMP_OUTPUT ParameterTypes = 0x00000006 + MEMREF_TEMP_INOUT ParameterTypes = 0x00000007 + MEMREF_WHOLE ParameterTypes = 0x0000000C + MEMREF_PARTIAL_INPUT ParameterTypes = 0x0000000D + MEMREF_PARTIAL_OUTPUT ParameterTypes = 0x0000000E + MEMREF_PARTIAL_INOUT ParameterTypes = 0x0000000F +) + +type TEEC struct { + context *C.TEEC_Context + session *C.TEEC_Session +} + +type UUID struct { + TimeLow uint32 + TimeMid uint16 + TimeHiAndVersion uint16 + ClockSeqAndNode [8]byte +} + +type Parameter struct { + Buffer []byte + Size uint32 +} + +type Operation struct { + ParamTypes [4]ParameterTypes + Params [4]Parameter +} + +func New() *TEEC { + return &TEEC{} +} + +func (teec *TEEC) Initialize() error { + teec.context = (*C.TEEC_Context)(C.calloc(1, C.sizeof_TEEC_Context)) + + res := C.TEEC_InitializeContext(nil, teec.context) + + if res != C.TEEC_SUCCESS { + teec.freeContext() + return teec.resultToErrorMessage("Initialize", res) + } + + return nil +} + +func (teec *TEEC) OpenSession(destination UUID) error { + teec.session = (*C.TEEC_Session)(C.calloc(1, C.sizeof_TEEC_Session)) + + dest := (*C.TEEC_UUID)(C.calloc(1, C.sizeof_TEEC_UUID)) + defer C.free(unsafe.Pointer(dest)) + + dest.timeLow = C.uint32_t(destination.TimeLow) + dest.timeMid = C.uint16_t(destination.TimeMid) + dest.timeHiAndVersion = C.uint16_t(destination.TimeHiAndVersion) + C.memcpy(unsafe.Pointer(&dest.clockSeqAndNode), unsafe.Pointer((*C.uint8_t)(&destination.ClockSeqAndNode[0])), 8) + + res := C.TEEC_OpenSession(teec.context, teec.session, dest, 0, nil, nil, nil) + + if res != C.TEEC_SUCCESS { + teec.freeSession() + return teec.resultToErrorMessage("OpenSession", res) + } + + return nil +} + +func (teec *TEEC) InvokeCommand(commandID uint32, operation *Operation, origin *uint32) error { + if teec.session == nil { + return fmt.Errorf("session is nil") + } + + if operation == nil { + return fmt.Errorf("operation is nil") + } + + var op C.TEEC_Operation + + op.paramTypes = teec.parameterTypes(operation) + + var buffers [4]*C.char + + for i, parameter := range operation.Params { + buffers[i] = C.CString(string(parameter.Buffer)) + } + + defer func() { + for _, buffer := range buffers { + C.free(unsafe.Pointer(buffer)) + } + }() + + for i, parameter := range operation.Params { + switch operation.ParamTypes[i] { + case NONE: + // Do nothing + break + case MEMREF_TEMP_INPUT: + C.fillTempMemoryReference(&op.params[i], unsafe.Pointer(buffers[i]), C.size_t(parameter.Size)) + case MEMREF_TEMP_OUTPUT: + C.fillTempMemoryReference(&op.params[i], unsafe.Pointer(buffers[i]), C.size_t(parameter.Size)) + case MEMREF_TEMP_INOUT: + C.fillTempMemoryReference(&op.params[i], unsafe.Pointer(buffers[i]), C.size_t(parameter.Size)) + case VALUE_INPUT: + return fmt.Errorf("VALUE_INPUT not implemented") + case VALUE_OUTPUT: + return fmt.Errorf("VALUE_OUTPUT not implemented") + case VALUE_INOUT: + return fmt.Errorf("VALUE_INOUT not implemented") + case MEMREF_WHOLE: + return fmt.Errorf("MEMREF_WHOLE not implemented") + case MEMREF_PARTIAL_INPUT: + return fmt.Errorf("MEMREF_PARTIAL_INPUT not implemented") + case MEMREF_PARTIAL_OUTPUT: + return fmt.Errorf("MEMREF_PARTIAL_OUTPUT not implemented") + case MEMREF_PARTIAL_INOUT: + return fmt.Errorf("MEMREF_PARTIAL_INOUT not implemented") + } + } + + res := C.TEEC_InvokeCommand(teec.session, (C.uint32_t)(commandID), &op, (*C.uint32_t)(origin)) + + if res != C.TEEC_SUCCESS { + return teec.resultToErrorMessage("InvokeCommand", res) + } + + for i, buffer := range buffers { + switch operation.ParamTypes[i] { + case NONE: + // Do nothing + case MEMREF_TEMP_INPUT: + // Do nothing + case MEMREF_TEMP_OUTPUT: + operation.Params[i].Buffer = []byte(C.GoString(buffer)) + case MEMREF_TEMP_INOUT: + operation.Params[i].Buffer = []byte(C.GoString(buffer)) + case VALUE_INPUT: + return fmt.Errorf("VALUE_INPUT not implemented") + case VALUE_OUTPUT: + return fmt.Errorf("VALUE_OUTPUT not implemented") + case VALUE_INOUT: + return fmt.Errorf("VALUE_INOUT not implemented") + case MEMREF_WHOLE: + return fmt.Errorf("MEMREF_WHOLE not implemented") + case MEMREF_PARTIAL_INPUT: + return fmt.Errorf("MEMREF_PARTIAL_INPUT not implemented") + case MEMREF_PARTIAL_OUTPUT: + return fmt.Errorf("MEMREF_PARTIAL_OUTPUT not implemented") + case MEMREF_PARTIAL_INOUT: + return fmt.Errorf("MEMREF_PARTIAL_INOUT not implemented") + } + } + + return nil +} + +func (teec *TEEC) CloseSession() error { + if teec.session == nil { + return fmt.Errorf("session is nil") + } + + C.TEEC_CloseSession(teec.session) + teec.freeSession() + + return nil +} + +func (teec *TEEC) Finalize() error { + if teec.context == nil { + return fmt.Errorf("context is nil") + } + + C.TEEC_FinalizeContext(teec.context) + teec.freeContext() + + return nil +} + +func (teec *TEEC) parameterTypes(operation *Operation) C.uint32_t { + var paramTypes C.uint32_t + + paramTypes = 0 + + for i, param := range operation.ParamTypes { + paramTypes |= (C.uint32_t)(param) << (i * 4) + } + + return paramTypes +} + +func (teec *TEEC) resultToErrorMessage(name string, res C.TEEC_Result) error { + + switch res { + case C.TEEC_ERROR_STORAGE_NOT_AVAILABLE: + return fmt.Errorf("%s: TEEC_ERROR_STORAGE_NOT_AVAILABLE", name) + case C.TEEC_ERROR_GENERIC: + return fmt.Errorf("%s: TEEC_ERROR_GENERIC", name) + case C.TEEC_ERROR_ACCESS_DENIED: + return fmt.Errorf("%s: TEEC_ERROR_ACCESS_DENIED", name) + case C.TEEC_ERROR_CANCEL: + return fmt.Errorf("%s: TEEC_ERROR_CANCEL", name) + case C.TEEC_ERROR_ACCESS_CONFLICT: + return fmt.Errorf("%s: TEEC_ERROR_ACCESS_CONFLICT", name) + case C.TEEC_ERROR_EXCESS_DATA: + return fmt.Errorf("%s: TEEC_ERROR_EXCESS_DATA", name) + case C.TEEC_ERROR_BAD_FORMAT: + return fmt.Errorf("%s: TEEC_ERROR_BAD_FORMAT", name) + case C.TEEC_ERROR_BAD_PARAMETERS: + return fmt.Errorf("%s: TEEC_ERROR_BAD_PARAMETERS", name) + case C.TEEC_ERROR_BAD_STATE: + return fmt.Errorf("%s: TEEC_ERROR_BAD_STATE", name) + case C.TEEC_ERROR_ITEM_NOT_FOUND: + return fmt.Errorf("%s: TEEC_ERROR_ITEM_NOT_FOUND", name) + case C.TEEC_ERROR_NOT_IMPLEMENTED: + return fmt.Errorf("%s: TEEC_ERROR_NOT_IMPLEMENTED", name) + case C.TEEC_ERROR_NOT_SUPPORTED: + return fmt.Errorf("%s: TEEC_ERROR_NOT_SUPPORTED", name) + case C.TEEC_ERROR_NO_DATA: + return fmt.Errorf("%s: TEEC_ERROR_NO_DATA", name) + case C.TEEC_ERROR_OUT_OF_MEMORY: + return fmt.Errorf("%s: TEEC_ERROR_OUT_OF_MEMORY", name) + case C.TEEC_ERROR_BUSY: + return fmt.Errorf("%s: TEEC_ERROR_BUSY", name) + case C.TEEC_ERROR_COMMUNICATION: + return fmt.Errorf("%s: TEEC_ERROR_COMMUNICATION", name) + case C.TEEC_ERROR_SECURITY: + return fmt.Errorf("%s: TEEC_ERROR_SECURITY", name) + case C.TEEC_ERROR_SHORT_BUFFER: + return fmt.Errorf("%s: TEEC_ERROR_SHORT_BUFFER", name) + case C.TEEC_ERROR_EXTERNAL_CANCEL: + return fmt.Errorf("%s: TEEC_ERROR_EXTERNAL_CANCEL", name) + case C.TEEC_ERROR_TARGET_DEAD: + return fmt.Errorf("%s: TEEC_ERROR_TARGET_DEAD", name) + case C.TEEC_ERROR_STORAGE_NO_SPACE: + return fmt.Errorf("%s: TEEC_ERROR_STORAGE_NO_SPACE", name) + } + + return fmt.Errorf("unknown error") +} + +func (teec *TEEC) freeContext() { + C.free(unsafe.Pointer(teec.context)) + teec.context = nil +} + +func (teec *TEEC) freeSession() { + C.free(unsafe.Pointer(teec.session)) + teec.session = nil +} diff --git a/third-party/optee-client b/third-party/optee-client new file mode 160000 index 0000000..e7cba71 --- /dev/null +++ b/third-party/optee-client @@ -0,0 +1 @@ +Subproject commit e7cba71cc6e2ecd02f412c7e9ee104f0a5dffc6f