diff --git a/README.md b/README.md index 2c5627c..e849169 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ initializing console (text) go-boot • tamago/amd64 (go1.24.1) • UEFI x64 . # load and start EFI image +bt (none|offline|online)? # show/set boot-transparency status build # build information cat # show file contents clear # clear screen @@ -266,6 +267,16 @@ For a stand-alone AMD SEV-SNP unikernel, based on go-boot code but capable of terminating EFI Boot Services and using additional network drivers, see [tamago-sev-example](https://github.com/usbarmory/tamago-sev-example). +Boot transparency +================= + +The interaction with a transparency ecosystem for boot loading operations is provided +by the [boot-transparency](https://github.com/usbarmory/boot-transparency) Go library. + +The following example demonstrates how to enable, and configure, the boot transparency support: + +* [Boot transparency](https://github.com/usbarmory/go-boot/wiki/Boot-Transparency) + License ======= diff --git a/cmd/auth.go b/cmd/auth.go new file mode 100644 index 0000000..440f381 --- /dev/null +++ b/cmd/auth.go @@ -0,0 +1,98 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +package cmd + +import ( + "errors" + "fmt" + "io/fs" + "net" + "regexp" + "strings" + + "github.com/usbarmory/go-boot/shell" + "github.com/usbarmory/go-boot/transparency" + "github.com/usbarmory/go-boot/uapi" + + "github.com/usbarmory/boot-transparency/artifact" + _ "github.com/usbarmory/boot-transparency/engine/sigsum" + _ "github.com/usbarmory/boot-transparency/engine/tessera" + "github.com/usbarmory/boot-transparency/policy" + bt_transparency "github.com/usbarmory/boot-transparency/transparency" +) + +var btConfig transparency.Config + +func init() { + shell.Add(shell.Cmd{ + Name: "bt", + Args: 2, + Pattern: regexp.MustCompile(`^(?:bt)( none| offline| online)?( sigsum| tessera)?$`), + Syntax: "(none|offline|online)? (sigsum|tessera)?", + Help: "show/change boot-transparency configuration", + Fn: btCmd, + }) +} + +func btCmd(_ *shell.Interface, arg []string) (res string, err error) { + if len(arg[0]) > 0 { + switch strings.TrimSpace(arg[0]) { + case "none": + btConfig.Status = transparency.None + case "offline": + btConfig.Status = transparency.Offline + case "online": + if net.SocketFunc == nil { + return "", errors.New("network unavailable") + } + btConfig.Status = transparency.Online + } + + if btConfig.Status != transparency.None && len(arg[1]) == 0 { + return "", errors.New("invalid transparency engine") + } + + if len(arg[1]) > 0 { + e := bt_transparency.EngineCodeFromString[strings.TrimSpace(arg[1])] + if _, err = bt_transparency.GetEngine(e); err != nil { + return "", fmt.Errorf("unable to configure the transparency engine, %v", err) + } + btConfig.Engine = e + } + } + + switch btConfig.Status { + case transparency.None: + res = fmt.Sprintf("boot-transparency is disabled\n") + case transparency.Offline, transparency.Online: + res = fmt.Sprintf("boot-transparency is enabled in %s mode, %s engine selected\n", btConfig.Status, btConfig.Engine) + } + + return +} + +func btValidateLinux(entry *uapi.Entry, root fs.FS) (err error) { + if entry == nil || len(entry.Linux) == 0 { + return errors.New("invalid kernel entry") + } + + btConfig.Root = root + + btEntry := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: entry.Linux, + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: entry.Initrd, + }, + }, + } + + return transparency.Validate(&btConfig, btEntry) +} diff --git a/cmd/linux.go b/cmd/linux.go index 04652ba..0cbca50 100644 --- a/cmd/linux.go +++ b/cmd/linux.go @@ -15,6 +15,7 @@ import ( "github.com/usbarmory/armory-boot/exec" "github.com/usbarmory/go-boot/shell" + "github.com/usbarmory/go-boot/transparency" "github.com/usbarmory/go-boot/uapi" "github.com/usbarmory/go-boot/uefi" "github.com/usbarmory/go-boot/uefi/x64" @@ -245,6 +246,13 @@ func linuxCmd(_ *shell.Interface, arg []string) (res string, err error) { return "", errors.New("empty kernel entry") } + // boot transparency validation (if enabled) + if btConfig.Status != transparency.None { + if err = btValidateLinux(entry, root); err != nil { + return "", fmt.Errorf("boot transparency, %v", err) + } + } + image := &exec.LinuxImage{ Kernel: entry.Linux, InitialRamDisk: entry.Initrd, diff --git a/go.mod b/go.mod index cf9e54a..5b6dc4b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b github.com/u-root/u-root v0.15.0 github.com/usbarmory/armory-boot v0.0.0-20260202115234-edf170b30f66 + github.com/usbarmory/boot-transparency v0.0.0-20260414075008-89b76c203db5 github.com/usbarmory/go-net v0.0.0-20251003201608-93d9ffe808de github.com/usbarmory/tamago v1.26.1 golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e @@ -18,17 +19,34 @@ require ( require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/btree v1.1.2 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/therootcompany/xz v1.0.1 // indirect + github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 // indirect + github.com/transparency-dev/merkle v0.0.2 // indirect + github.com/transparency-dev/tessera v1.0.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.12.0 // indirect gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + sigsum.org/sigsum-go v0.11.2 // indirect ) diff --git a/go.sum b/go.sum index b624cb4..d9ff2e3 100644 --- a/go.sum +++ b/go.sum @@ -2,24 +2,47 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/arl/statsviz v0.8.0 h1:O6GjjVxEDxcByAucOSl29HaGYLXsuwA3ujJw8H9E7/U= github.com/arl/statsviz v0.8.0/go.mod h1:XlrbiT7xYT03xaW9JMMfD8KFUhBOESJwfyNJu83PbB0= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE= +github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw= +github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= +github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= +github.com/transparency-dev/tessera v1.0.0 h1:4OT1V9xJLa5NnYlFWWlCdZkCm18/o12rdd+bCTje7XE= +github.com/transparency-dev/tessera v1.0.0/go.mod h1:TLvfjlkbmsmKVEJUtzO2eb9Q2IBnK3EJ0dI4G0oxEOU= github.com/u-root/u-root v0.15.0 h1:8JXfjAA/Vs8EXfZUA2ftvoHbiYYLdaU8umJ461aq+Jw= github.com/u-root/u-root v0.15.0/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -28,23 +51,47 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/usbarmory/armory-boot v0.0.0-20260202115234-edf170b30f66 h1:EKfiE4TIqVtO9Nyj7Izg16JPWpg+QaFkg0N3kBzgCZU= github.com/usbarmory/armory-boot v0.0.0-20260202115234-edf170b30f66/go.mod h1:2cCdG4eUnVtrKyfbCc2A+0SHJl62Cgf4jEXTw/OvlW4= +github.com/usbarmory/boot-transparency v0.0.0-20260414075008-89b76c203db5 h1:LtdptgdqZ4BHlOOe2KrMPrnIKqN5ztsiv41uJzfYvH0= +github.com/usbarmory/boot-transparency v0.0.0-20260414075008-89b76c203db5/go.mod h1:sRG+Jvx0W7gw7ATXOGPgF93DhjHYZ0I9CnT0myhtdKo= github.com/usbarmory/go-net v0.0.0-20251003201608-93d9ffe808de h1:5O20CXXbFwjrqyDFtCyFUlxiRLCdc+ksT8MUihbjIDg= github.com/usbarmory/go-net v0.0.0-20251003201608-93d9ffe808de/go.mod h1:+6WiKCFJtJQZdNM2VpwQsYGo/aBJ39pN7nWx6Td3Z8s= github.com/usbarmory/tamago v1.26.1 h1:ZJkxM/+qNZTO631bJz5x/flhYb/ww1ura4H2BrZbX5I= github.com/usbarmory/tamago v1.26.1/go.mod h1:7x0kUe5eE9S1z7Pi/C9RjF8E4JHWzqnE4cKzGl0hyug= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e h1:G2pNJMnWEb60ip90TMo++m9fpt+d3YIZa0er3buPKRo= golang.org/x/crypto/x509roots/fallback v0.0.0-20260209214922-2f26647a795e/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068 h1:95kdltF/maTDk/Wulj7V81cSLgjB/Mg/6eJmOKsey4U= gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068/go.mod h1:K16uJjZ+hSqDVsXhU2Rg2FpMN7kBvjZp/Ibt5BYZJjw= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +sigsum.org/sigsum-go v0.11.2 h1:7HhDPC8gVJzl3wB3gAg3j6gTpO2t0UPHC0ogwhKuNRc= +sigsum.org/sigsum-go v0.11.2/go.mod h1:pGa/r4QsNYom+RqRMkdhcG5E00ty1nlTmALEizdRWPk= diff --git a/transparency/boot_entry.go b/transparency/boot_entry.go new file mode 100644 index 0000000..3682138 --- /dev/null +++ b/transparency/boot_entry.go @@ -0,0 +1,97 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +// Package transparency implements an interface to the +// boot-transparency library functions to ease boot bundle +// validation. +package transparency + +import ( + "fmt" + + "github.com/usbarmory/boot-transparency/policy" + "github.com/usbarmory/boot-transparency/transparency" +) + +// Validate applies boot-transparency validation (e.g. inclusion proof, +// boot policy and claims consistency) for the argument [Config] representing +// the boot transparency configuration. +// Returns error if the boot artifacts are not passing the validation. +func Validate(c *Config, be *policy.BootEntry) (err error) { + if c.Status == None { + return + } + + if len(be.Artifacts) == 0 { + return fmt.Errorf("invalid boot entry") + } + + // Automatically load the configuration from the configured root filesystem. + if c.Root != nil { + entryPath, err := c.Path(be) + if err != nil { + return fmt.Errorf("cannot load boot-transparency configuration, %v", err) + } + + if err = c.loadFromRoot(entryPath); err != nil { + return fmt.Errorf("cannot load boot-transparency configuration, %v", err) + } + } + + te, err := transparency.GetEngine(c.Engine) + if err != nil { + return fmt.Errorf("unable to get transparency engine, %v", err) + } + + if err = te.SetKey(c.LogKey, c.SubmitKey); err != nil { + return fmt.Errorf("unable to set log and submitter keys, %v", err) + } + + if err = te.SetWitnessPolicy(c.WitnessPolicy); err != nil { + return fmt.Errorf("unable to set witness policy, %v", err) + } + + format, statement, proof, probe, _, err := transparency.ParseProofBundle(c.ProofBundle) + if err != nil { + return fmt.Errorf("unable to parse the proof bundle, %v", err) + } + + if format != c.Engine { + return fmt.Errorf("proof bundle format doesn't match the transparency engine.") + } + + // If network access is available the inclusion proof verification + // is performed using the proof fetched from the log. + if c.Status == Online { + proof, err = te.GetProof(statement, probe) + if err != nil { + return err + } + } + + if err = te.VerifyProof(statement, proof, nil); err != nil { + return err + } + + requirements, err := policy.ParseRequirements(c.BootPolicy) + if err != nil { + return + } + + claims, err := policy.ParseStatement(statement) + if err != nil { + return + } + + // Validate the matching between the logged claims and the policy requirements, + // fot the given set of boot artifacts. + if err = be.Validate(requirements, claims); err != nil { + // The boot bundle is NOT authorized. + return + } + + // boot-transparency validation passed, boot bundle is authorized. + return +} diff --git a/transparency/boot_entry_test.go b/transparency/boot_entry_test.go new file mode 100644 index 0000000..84f9347 --- /dev/null +++ b/transparency/boot_entry_test.go @@ -0,0 +1,140 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +// Package transparency implements an interface to the +// boot-transparency library functions to ease boot bundle +// validation. +package transparency + +import ( + "errors" + "testing" + + "github.com/usbarmory/boot-transparency/artifact" + _ "github.com/usbarmory/boot-transparency/engine/sigsum" + _ "github.com/usbarmory/boot-transparency/engine/tessera" + "github.com/usbarmory/boot-transparency/policy" + "github.com/usbarmory/boot-transparency/transparency" +) + +var testConfig = &Config{ + Status: Offline, + Engine: transparency.Sigsum, + + BootPolicy: []byte(testBootPolicy), + WitnessPolicy: []byte(testWitnessPolicy), + SubmitKey: []byte(testSubmitKey), + LogKey: []byte(testLogKey), + ProofBundle: []byte(testProofBundle), +} + +func TestOfflineValidate(t *testing.T) { + testConfig.Status = Offline + + b := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + if err := Validate(testConfig, b); err != nil { + t.Fatal(err) + } +} + +func TestOnlineValidate(t *testing.T) { + testConfig.Status = Online + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + if err := Validate(testConfig, be); err != nil { + t.Fatal(err) + } +} + +func TestOfflineValidateInvalidBootEntry(t *testing.T) { + testConfig.Status = Offline + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + // missing Data + }, + }, + } + + // Error expected: invalid boot entry error. + if err := Validate(testConfig, be); err == nil || !errors.Is(err, policy.ErrInvalidBootEntry) { + t.Fatal("missing invalid boot entry error") + } +} + +func TestOfflineValidateHashMismatch(t *testing.T) { + testConfig.Status = Offline + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testIncorrectKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + // Error expected: incorrect hash. + if err := Validate(testConfig, be); err == nil || !errors.Is(err, policy.ErrValidate) { + t.Fatal("missing incorrect hash error") + } +} + +func TestOfflineValidatePolicyNotMet(t *testing.T) { + testConfig.Status = Offline + testConfig.BootPolicy = []byte(testBootPolicyUnauthorized) + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + // Error expected: requirement not met. + if err := Validate(testConfig, be); err == nil || !errors.Is(err, policy.ErrValidate) { + t.Fatal("missing policy validation error") + } +} diff --git a/transparency/config.go b/transparency/config.go new file mode 100644 index 0000000..faff215 --- /dev/null +++ b/transparency/config.go @@ -0,0 +1,153 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +// Package transparency implements an interface to the +// boot-transparency library functions to ease boot bundle +// validation. +package transparency + +import ( + "encoding/hex" + "fmt" + "io/fs" + "path/filepath" + "sort" + + "github.com/usbarmory/boot-transparency/policy" + "github.com/usbarmory/boot-transparency/transparency" +) + +// Status represents the status of the boot-transparency functionality. +type Status int + +// Status codes. +const ( + // Boot-transparency disabled. + None Status = iota + + // Boot-transparency enabled in offline mode. + Offline + + // Boot-transparency enabled in online mode. + Online +) + +// String resolves Status codes into a human-readable strings. +func (s Status) String() string { + statusName := map[Status]string{ + None: "none", + Offline: "offline", + Online: "online", + } + + return statusName[s] +} + +// Boot-transparency configuration root directory and filenames. +const ( + // DefaultPathPrefix represents the default prefix to the asset paths. + DefaultPathPrefix = `transparency` + + bootPolicy = `policy.json` + witnessPolicy = `trust_policy` + proofBundle = `proof-bundle.json` + submitKey = `submit-key.pub` + logKey = `log-key.pub` +) + +// Config represents the configuration for the boot-transparency functionality. +type Config struct { + // Engine represents the transparency engine chosen among the ones + // supported by boot-transparency library. + Engine transparency.EngineCode + + // Status represents the status of the boot-transparency functionality. + Status Status + + // Root represents the filesystem root directory where the transparency assets + // are stored. + Root fs.FS + + // PathPrefix represent the directory used to load the boot entry + // assets from their correspondent unique paths (see [*Config Path()]). + // If left not configured, by default the [DefaultPathPrefix] is pre-pended + // to all the asset paths. + PathPrefix string + + // BootPolicy represents the boot policy in JSON format + // following the policy syntax supported by boot-transparency library. + BootPolicy []byte + + // WitnessPolicy represents the witness policy following + // the Sigsum plaintext witness policy format. + WitnessPolicy []byte + + // SubmitKey represents the log submitter public keys. + // The format should match the one(s) supported by the + // chosen transparency engine. + SubmitKey []byte + + // LogKey represents the log public keys. + // The format should match the one(s) supported by the + // chosen transparency engine. + LogKey []byte + + // ProofBundle represents the proof bundle in JSON format + // following the proof bundle format supported by boot-transparency library. + ProofBundle []byte +} + +// Path returns a unique configuration path for a given set of +// artifacts (i.e. boot entry). +// Returns error if one of the artifacts does not include a valid +// SHA-256 hash. +func (c *Config) Path(be *policy.BootEntry) (entryPath string, err error) { + if len(be.Artifacts) == 0 { + return "", fmt.Errorf("invalid boot entry") + } + + // Sort the passed artifacts, by their Category, to ensure + // consistency in the way the entry path is built. + sort.Slice(be.Artifacts, func(i, j int) bool { + return be.Artifacts[i].Category < be.Artifacts[j].Category + }) + + entryPath = DefaultPathPrefix + if c.PathPrefix != "" { + entryPath = c.PathPrefix + } + + for _, artifact := range be.Artifacts { + entryPath = filepath.Join(entryPath, hex.EncodeToString(artifact.Hash())) + } + + return +} + +// loadFromRoot reads the transparency configuration files from +// the disk root filesystem. The entry argument allows per-bundle configurations. +func (c *Config) loadFromRoot(entryPath string) (err error) { + assets := map[string]*[]byte{ + bootPolicy: &c.BootPolicy, + witnessPolicy: &c.WitnessPolicy, + submitKey: &c.SubmitKey, + logKey: &c.LogKey, + proofBundle: &c.ProofBundle, + } + + if c.Root == nil { + return fmt.Errorf("cannot open root filesystem") + } + + for filename, dst := range assets { + p := filepath.Join(entryPath, filename) + + if *dst, err = fs.ReadFile(c.Root, p); err != nil { + return fmt.Errorf("cannot load configuration file: %v", filename) + } + } + + return +} diff --git a/transparency/config_test.go b/transparency/config_test.go new file mode 100644 index 0000000..f51e4d2 --- /dev/null +++ b/transparency/config_test.go @@ -0,0 +1,120 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +// Package transparency implements an interface to the +// boot-transparency library functions to ease boot bundle +// validation. +package transparency + +import ( + "testing" + + "github.com/usbarmory/boot-transparency/artifact" + "github.com/usbarmory/boot-transparency/policy" + "github.com/usbarmory/boot-transparency/transparency" +) + +func TestPath(t *testing.T) { + c := Config{ + Status: Offline, + Engine: transparency.Sigsum, + } + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + p, err := c.Path(be) + if err != nil { + t.Fatal(err) + } + + if p != testEntryPath { + t.Fatal("got an invalid path.") + } +} + +func TestLoadFromRoot(t *testing.T) { + c := Config{ + Status: Offline, + Engine: transparency.Sigsum, + Root: testRoot, + } + + if err := c.loadFromRoot(""); err != nil { + t.Fatal(err) + } +} + +func TestLoadFromRootWithPathPrefix(t *testing.T) { + c := Config{ + Status: Offline, + Engine: transparency.Sigsum, + Root: testRootWithPrefix, + PathPrefix: `transparency`, + } + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + entryPath, err := c.Path(be) + if err != nil { + t.Fatal(err) + } + + if err = c.loadFromRoot(entryPath); err != nil { + t.Fatal(err) + } +} + +func TestLoadFromRootInCorrectPathPrefix(t *testing.T) { + c := Config{ + Status: Offline, + Engine: transparency.Sigsum, + Root: testRootWithPrefix, + PathPrefix: `FOO`, + } + + be := &policy.BootEntry{ + Artifacts: []policy.BootArtifact{ + policy.BootArtifact{ + Category: artifact.LinuxKernel, + Data: []byte(testKernel), + }, + policy.BootArtifact{ + Category: artifact.Initrd, + Data: []byte(testInitrd), + }, + }, + } + + entryPath, err := c.Path(be) + if err != nil { + t.Fatal(err) + } + + if err = c.loadFromRoot(entryPath); err == nil { + t.Fatal("missing error due to incorrect path prefix") + } +} diff --git a/transparency/data_test.go b/transparency/data_test.go new file mode 100644 index 0000000..696af53 --- /dev/null +++ b/transparency/data_test.go @@ -0,0 +1,189 @@ +// Copyright (c) The go-boot authors. All Rights Reserved. +// +// Use of this source code is governed by the license +// that can be found in the LICENSE file. + +// Package transparency implements an interface to the +// boot-transparency library functions to ease boot bundle +// validation. +package transparency + +import ( + "path/filepath" + "testing/fstest" +) + +const ( + testKernel = `test linux kernel` + testInitrd = `test initrd` + testIncorrectKernel = `test incorrect linux kernel` + testEntryPath = "transparency/5e6d8e01d75e3e0396d672b0e8c3e31f78532eef9fa2a3f464299ee7cc44a12e/b868d20383e979c588e7b16d24b9d3fcb9c1213c89135e6c656edf94cbf31542" + + testBootPolicy = `[ + { + "artifacts": [ + { + "category": 1, + "requirements": { + "min_version": "v6.14.0-29", + "tainted": false, + "build_args": { + "CONFIG_STACKPROTECTOR_STRONG": "y" + } + } + }, + { + "category": 2, + "requirements": { + "tainted": false + } + } + ], + "signatures": { + "signers": [ + { + "name": "dist signatory I", + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP5rbNcIOcwqBHzLOhJEfdKFHa+pIs10idfTm8c+HDnK" + }, + { + "name": "dist signatory II", + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0zV5fSWzzXa4R7Kpk6RAXkvWsJGpvkQ+9/xxpHC49J" + } + ], + "quorum": 2 + } + } +]` + + testWitnessPolicy = `log 4644af2abd40f4895a003bca350f9d5912ab301a49c77f13e5b6d905c20a5fe6 https://test.sigsum.org/barreleye + +witness poc.sigsum.org/nisse 1c25f8a44c635457e2e391d1efbca7d4c2951a0aef06225a881e46b98962ac6c +witness rgdd.se/poc-witness 28c92a5a3a054d317c86fc2eeb6a7ab2054d6217100d0be67ded5b74323c5806 + +group demo-quorum-rule any poc.sigsum.org/nisse rgdd.se/poc-witness +quorum demo-quorum-rule +` + + testProofBundle = `{ + "format": 1, + "statement": { + "header": { + "description": "Linux bundle", + "revision": "v1" + }, + "artifacts": [ + { + "category": 1, + "claims": { + "file_name": "test-vmlinuz-6.14.0-29-generic", + "file_hash": "5e6d8e01d75e3e0396d672b0e8c3e31f78532eef9fa2a3f464299ee7cc44a12e", + "version": "v6.14.0-29-generic", + "tainted": false, + "build_args": { + "CONFIG_STACKPROTECTOR_STRONG": "y" + } + } + }, + { + "category": 2, + "claims": { + "file_name": "test-initrd.img-6.14.0-29-generic", + "file_hash": "b868d20383e979c588e7b16d24b9d3fcb9c1213c89135e6c656edf94cbf31542", + "version": "v6.14.0-29-generic", + "tainted": false + } + } + ], + "signatures": [ + { + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP5rbNcIOcwqBHzLOhJEfdKFHa+pIs10idfTm8c+HDnK", + "signature": "8d984b482ab45de5a2f0171a338b6ce8e64d95a70d6ea14b9d2a5f772c21d339d4cd51091b8f4c93f6dc289ee32ad94d048c8badb4fc3cc0a3136bfb4886ba0f" + }, + { + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0zV5fSWzzXa4R7Kpk6RAXkvWsJGpvkQ+9/xxpHC49J", + "signature": "99af82c7c559cf98902ac299f10a608353f2447729115667e924719d4ffb2b3cf80dc6715959afafaa5d255ae45e880351245cba6233ae093716136670b7d409" + } + ] + }, + "probe": { + "origin": "https://test.sigsum.org/barreleye", + "leaf_signature": "cf4e40c8bef8eb2742327ba8037bc531ca8929a58b38ba7ca4c48152eff3f62d8c9c71a720d4ea601bfe47a5c543409e2e0116958230a5e2e9356c123464920c", + "log_public_key_hash": "4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d", + "submit_public_key_hash": "302928c2e0e01da52e3b161c54906de9b55ce250f0f47e80e022d04036e2765c" + }, + "proof": "version=2\nlog=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\nleaf=302928c2e0e01da52e3b161c54906de9b55ce250f0f47e80e022d04036e2765c cf4e40c8bef8eb2742327ba8037bc531ca8929a58b38ba7ca4c48152eff3f62d8c9c71a720d4ea601bfe47a5c543409e2e0116958230a5e2e9356c123464920c\n\nsize=162296\nroot_hash=2f63a7e021ae6bf254df038a89439c0867cb0b6845f235f2c52cb1f556ac9ba4\nsignature=5c4074ead7bd3b2f053790adb45ca776455f193e6370091a00159b711bfd7323b75c72cc667e8b27a8120ba4ea397ac964434a23363a6e35b24e81b1b925de0b\ncosignature=70b861a010f25030de6ff6a5267e0b951e70c04b20ba4a3ce41e7fba7b9b7dfc 1773223723 97ba8d7c37485a30507fef8cf1c67b5772ec3e5f9aee74c2a8412f56a1676b6a757e8b9f7483668a9320afb1ed8deaf2134ff2f5a439651fafc9e1096d9c6406\ncosignature=49c4cd6124b7c572f3354d854d50b2a4b057a750f786cf03103c09de339c4ea3 1773223723 44e29bed024f21be6d057abbbc6dbd2269351602cf5ead6e2fbcf24b04645047036779df90be5a3410a4446c3ede815a0972c9971e1bce1dff022d2f5bfca503\ncosignature=f308ac5bf00ef954f70fbe5e769258203cc792469154a1b9cf3003a6286a138d 1773223723 c60790b30b1a1cd5a8413e7568524dbd954d13ca93bfa884c2f836dc2fe7d8fbe72cfa6692223e080c8537b856d6ffb1d4cea3da875cd98fccd3dea4d613d00e\ncosignature=86b5414ae57f45c2953a074640bb5bedebad023925d4dc91a31de1350b710089 1773223723 9ae72e1972fe49aa0de13df3d7148f542ba42548b8146b4c731e71d753f491eff1cb033b8e7229fc10d70d3327a6a8226ab8b62251e6488eaefbc8b6e8a5c20c\ncosignature=c1d2d6935c2fb43bef395792b1f3c1dfe4072d4c6cadd05e0cc90b28d7141ed3 1773223723 b5758eed296cea8df0729d5f794ca173001991b588f81abe2264ff2111f04725a892760440fc1d5a39b95a114a1cb6e563a0afe7980cd72545a2c372425cd20b\ncosignature=26983895bdf491838dd4885848670562e7b728b6efa15fd9047b5b97a9a0618f 1773223723 7ea0e9bb590d7f8bfe5fc8ae532cb2da82bc9dfb55c393df1df33857f7b9b98b0ba153fd5f8627db44a905669648a3afc45c594381f9327dae17876ec194300a\ncosignature=e4a6a1e4657d8d7a187cc0c20ed51055d88c72f340d29534939aee32d86b4021 1773223723 5dad8a314a0fd11bc6f17b394294d07fa04c559ee6f0f19dce9136da31ecb787e1d192b78d76e1254cb4a71834cd433dd4ae7983107fbb1ff9d6c605109be809\ncosignature=d960fcff859a34d677343e4789c6843e897c9ff195ea7140a6ef382566df3b65 1773223723 8f5c9f0dc40c14e895e2d4ddcc2e21b9f79cb72f7aea1a186150ef2eb788c18943b8a69dffd77d81dd2418da612b01b8028c0b327d2f4213d58f87f6af7be40d\ncosignature=1c997261f16e6e81d13f420900a2542a4b6a049c2d996324ee5d82a90ca3360c 1773223723 cac4549af290fe0871a60f61ae78c5935783b99ed3230cd8cbaada7a344aafb9c154f69479411f8b3b5541b50ca52c2e4808770b94e5e1dcb18ddabf9871290a\ncosignature=42351ad474b29c04187fd0c8c7670656386f323f02e9a4ef0a0055ec061ecac8 1773223723 d8eecc0efe851467abd3eb4cb7c1092b5139b3261261167a1041edaa57ac30a866427e9d8f8dafbb3ede69a828f1d84c1b4ea2843f225f88d4fe915f9aaf1609\n\nleaf_index=162295\nnode_hash=c07ee3cc7acf42d84055f55e1ed33b87f28dde8da34831b3dea5885f6cbcbb43\nnode_hash=90dc785de30daf434287c6c01be8873feccb678f53e1230c1a72ce32ab44444d\nnode_hash=238cbbee2e28f5be71e876382bfbe104838e91cea920ea4b17dffd552b208ce4\nnode_hash=c509aad460c158ae33b1136e116d9140fb704917bd752828b24fcf865a2a420b\nnode_hash=444b347c61b994f387b67dab3d44476967ff7821d5087012783b1ab589d0dc63\nnode_hash=dacdfb38e8b1d5228164ebf5f124d5e967b96b209a5475b58bd58d566cece960\nnode_hash=8f53b17df95a109fcae2c9e7a630709c592ca9e6d481015f04b1a0da17ab6d4c\nnode_hash=4b2a225d2562949d50812ce7682fc23c53d630e686fdf012dda6414f08d481f6\nnode_hash=fec9d1376932767b7774a00fe6c72b9fef2d6156cf1195b11f1b252ad1a5b932\nnode_hash=cc6f233eb1e8e56292545c00f429318c19501a5609db82510dd128849025ce03\nnode_hash=a4909e6ea2bca271bee1338a87ea2617cf0354e7a894b6da017b7d1c08d7cdb7\nnode_hash=3d848e383e4b77edb95a496ad00f1e8764b8ddbfedd90f9eaeadae17449e2af6\nnode_hash=43bd28c79dec46786a85bdf0fe72eac3985a8fa172979cdbf7dc04d6c506d43d\n" +}` + + testSubmitKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMqym9S/tFn6B/Eri5hGJiEV8BpGumEPcm65uxC+FG6K sigsum key` + testLogKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEZEryq9QPSJWgA7yjUPnVkSqzAaScd/E+W22QXCCl/m` + + testBootPolicyUnauthorized = `[ + { + "artifacts": [ + { + "category": 1, + "requirements": { + "build_args": { + "I_WANT_CANDY": "y" + } + } + }, + { + "category": 2, + "requirements": { + "tainted": false + } + } + ], + "signatures": { + "signers": [ + { + "name": "dist signatory I", + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP5rbNcIOcwqBHzLOhJEfdKFHa+pIs10idfTm8c+HDnK" + }, + { + "name": "dist signatory II", + "pub_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0zV5fSWzzXa4R7Kpk6RAXkvWsJGpvkQ+9/xxpHC49J" + } + ], + "quorum": 2 + } + } +]` +) + +var testRoot = fstest.MapFS{ + bootPolicy: { + Data: []byte(testBootPolicy), + }, + witnessPolicy: { + Data: []byte(testWitnessPolicy), + }, + submitKey: { + Data: []byte(testSubmitKey), + }, + logKey: { + Data: []byte(testLogKey), + }, + proofBundle: { + Data: []byte(testProofBundle), + }, +} + +var testRootWithPrefix = fstest.MapFS{ + filepath.Join(testEntryPath, bootPolicy): { + Data: []byte(testBootPolicy), + }, + filepath.Join(testEntryPath, witnessPolicy): { + Data: []byte(testWitnessPolicy), + }, + filepath.Join(testEntryPath, submitKey): { + Data: []byte(testSubmitKey), + }, + filepath.Join(testEntryPath, logKey): { + Data: []byte(testLogKey), + }, + filepath.Join(testEntryPath, proofBundle): { + Data: []byte(testProofBundle), + }, +}