pax_global_header 0000666 0000000 0000000 00000000064 14646004755 0014525 g ustar 00root root 0000000 0000000 52 comment=cfd532959af333da7546d3853bacbf536180b949
go-configfs-tsm-0.3.2/ 0000775 0000000 0000000 00000000000 14646004755 0014531 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/.gitignore 0000664 0000000 0000000 00000000002 14646004755 0016511 0 ustar 00root root 0000000 0000000 *~ go-configfs-tsm-0.3.2/CONTRIBUTING.md 0000664 0000000 0000000 00000002115 14646004755 0016761 0 ustar 00root root 0000000 0000000 # How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).
go-configfs-tsm-0.3.2/LICENSE 0000664 0000000 0000000 00000026136 14646004755 0015546 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
go-configfs-tsm-0.3.2/README.md 0000664 0000000 0000000 00000010412 14646004755 0016006 0 ustar 00root root 0000000 0000000 # go-configfs-tsm
This library wraps the configfs/tsm Linux subsystem for Trusted Security Module operations.
## `report` library
This library wraps the configfs/tsm/report subsystem for safely generating
attestation reports.
The TSM `report` subsystem provides a vendor-agnostic interface for collecting a
signed document for the Trusted Execution Environment's (TEE) state for remote
verification. For simplicity, we call this document an "attestation report",
though other sources may sometimes refer to it as a "quote".
Signing keys are expected to be rooted back to the manufacturer. Certificates
may be present in the `auxblob` attribute or as part of the report in `outblob`.
The core functionality of attestation report interaction is nonce in, report
out. For testability, we abstract the file operations that are needed for
creating configfs report entries, reading and writing attributes, and final
reclaiming of resources.
```golang
func Get(client configfsi.Client, req *report.Request) (*report.Response, error)
```
Where
```golang
type Request struct {
InBlob []byte
Privilege *Privilege
GetAuxBlob bool
}
type Response struct {
Provider string
OutBlob []byte
AuxBlob []byte
}
type Privilege struct {
Level int
}
```
The provider may not implement an `AuxBlob` delivery mechanism, so if
`GetAuxBlob` is true, then `AuxBlob` still must be checked for length 0.
### Errors
Since this is a file-based system, there's always a chance that an operation may
fail with a permission error. By default, the TSM system requires root access.
The host may also add rate limiting to requests, such that an outblob read fails
with `EBUSY`. The kernel may or may not try again on behalf of the user.
Finally, due to the fact that the TSM report system only requests an attestation
report when reading `outblob` or `auxblob`, there is a chance the input
attributes may have been changed to unexpected values from an interfering
process. This interference is a bug in user space that the kernel does not block
for simplicity. Interference is evident through the `generation` attribute. When
`generation` does not match the expectations that the `report` package tracks,
`report.Get` returns a `*report.GenerationErr` or an error that wraps
`*report.GenerationErr`.
Use `func GetGenerationErr(error) *GenerationErr` to extract a `*GenerationErr`
from an error if it is or contains a `*GenerationErr`. If present, the caller
should try to identify the source of interference and remove it. Meanwhile, the
caller may try again.
## `configfsi.Client` interface
Most users will only want to use the client from `linuxtsm.MakeClient`.
A client on real hardware is just the filesystem, since the configfs
interactions will interact with the hardware. In unit tests though, we can
emulate the behavior that has been proposed in v7 of the patch series
```golang
type Client interface {
MkdirTemp(dir, pattern string) (string, error)
ReadFile(name string) ([]byte, error)
WriteFile(name string, contents []byte) error
RemoveAll(path string) error
}
```
The `RemoveAll` function is the only oddly named method, since the real
interface would just `rmdir` the report directory
([`os.Remove`](https://pkg.go.dev/os#Remove) in Golang), even when there are
apparent files underneath. Non-empty directory removal is generally not allowed,
so the `RemoveAll` name is clearer with what it does.
## `linuxtsm` package
The `linuxtsm` package defines an implementation of `configfsi.Client` with
```golang
func MakeClient() (configfsi.Client, error)
```
For further convenience, `linuxtsm` provides an alias for `MakeClient` combined with `report.Get` as
```golang
func GetReport(req *report.Request) (*report.Response, error)
```
The usage is the same as for `report.Get`.
## `faketsm` package
The `faketsm.Client` implementation allows tests to provide custom behavior for subsystems by name:
```golang
type Client struct {
Subsystems map[string]configfsi.Client
}
```
The `faketsm.ReportSubsystem` type implements a client that emulates the
concurrent behavior and `generation` attribute semantics. To test negative
behavior as well, the subsystem allows the user to override `Mkdir`, `ReadFile`,
existing entries' values, and the error behavior of `WriteFile`.
## Disclaimer
This is not an officially supported Google product.
go-configfs-tsm-0.3.2/configfs/ 0000775 0000000 0000000 00000000000 14646004755 0016327 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/configfs/configfsi/ 0000775 0000000 0000000 00000000000 14646004755 0020276 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/configfs/configfsi/configfsi.go 0000664 0000000 0000000 00000003006 14646004755 0022573 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package configfsi defines an interface for interaction with the TSM configfs subsystem.
package configfsi
import (
"os"
)
// Client abstracts the filesystem operations for interacting with configfs files.
type Client interface {
// MkdirTemp creates a new temporary directory in the directory dir and returns the pathname
// of the new directory. Pattern semantics follow os.MkdirTemp.
MkdirTemp(dir, pattern string) (string, error)
// ReadFile reads the named file and returns the contents.
ReadFile(name string) ([]byte, error)
// ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
ReadDir(dirname string) ([]os.DirEntry, error)
// WriteFile writes data to the named file, creating it if necessary. The permissions
// are implementation-defined.
WriteFile(name string, contents []byte) error
// RemoveAll removes path and any children it contains.
RemoveAll(path string) error
}
go-configfs-tsm-0.3.2/configfs/configfsi/parse.go 0000664 0000000 0000000 00000001662 14646004755 0021744 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configfsi
import (
"strconv"
"strings"
)
// Kstrtouint returns the unsigned integer represented in data, following the same grammar
// allowances as Linux, i.e., data may have a single trailing newline.
func Kstrtouint(data []byte, base, bits int) (uint64, error) {
return strconv.ParseUint(strings.TrimRight(string(data), "\n"), base, bits)
}
go-configfs-tsm-0.3.2/configfs/configfsi/path.go 0000664 0000000 0000000 00000006275 14646004755 0021573 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configfsi
import (
"fmt"
"io"
"path"
"strings"
)
const (
// TsmPrefix is the path to the configfs tsm system.
TsmPrefix = "/sys/kernel/config/tsm"
// How many random characters to use when replacing * in a temporary path pattern.
randomPathSize = 10
)
// TsmPath represents a configfs file path decomposed into the components
// that are expected for TSM.
type TsmPath struct {
// Subsystem is the TSM subsystem the path is targeting, e.g., "report"
Subsystem string
// Entry is the directory under the subsystem that represents a single
// user's interface with the subsystem.
Entry string
// Attribute is a file under Entry that may be readable or writable depending
// on its name.
Attribute string
}
// String returns the configfs path that the TsmPath stands for.
func (p *TsmPath) String() string {
return path.Join(TsmPrefix, p.Subsystem, p.Entry, p.Attribute)
}
// ParseTsmPath decomposes a configfs path to TSM into its expected format, or returns
// an error.
func ParseTsmPath(filepath string) (*TsmPath, error) {
p := path.Clean(filepath)
if !strings.HasPrefix(p, TsmPrefix) {
return nil, fmt.Errorf("%q does not begin with %q", p, TsmPrefix)
}
// If just the tsm folder is given, there won't be a "/", but if there is a subpath,
// then it will have the leading "/".
rest := strings.TrimPrefix(strings.TrimPrefix(p, TsmPrefix), "/")
if rest == "" {
return nil, fmt.Errorf("%q does not contain a subsystem", p)
}
dir := path.Dir(rest)
file := path.Base(rest)
if dir == "." {
return &TsmPath{Subsystem: file}, nil
}
gdir := path.Dir(dir) // grand-dir
mfile := path.Base(dir)
if gdir == "." {
return &TsmPath{Subsystem: mfile, Entry: file}, nil
}
ggdir := path.Dir(gdir) // grand-grand-dir
subsystem := path.Base(gdir)
if ggdir != "." {
return nil, fmt.Errorf("%q suffix expected to be of form subsystem[/entry[/attribute]] (debug %q)", rest, ggdir)
}
return &TsmPath{Subsystem: subsystem, Entry: mfile, Attribute: file}, nil
}
func readableString(data []byte) string {
var sb strings.Builder
for _, b := range data {
sb.WriteRune(rune('0' + (b % 10)))
}
return sb.String()
}
// TempName returns a random filename following the pattern semantics
// of os.MkdirTemp. Does not have a root directory.
func TempName(rand io.Reader, pattern string) string {
data := make([]byte, randomPathSize)
if n, err := rand.Read(data); err != nil || n != len(data) {
return "rdfail"
}
randString := readableString(data)
lastAsterisk := strings.LastIndex(pattern, "*")
if lastAsterisk == -1 {
return pattern + randString
}
return pattern[0:lastAsterisk] + randString + pattern[lastAsterisk+1:]
}
go-configfs-tsm-0.3.2/configfs/configfsi/path_test.go 0000664 0000000 0000000 00000007232 14646004755 0022624 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configfsi
import (
"crypto/rand"
"strings"
"testing"
)
func TestTsmPathString(t *testing.T) {
tcs := []struct {
input *TsmPath
want string
}{
{input: &TsmPath{}, want: "/sys/kernel/config/tsm"},
{input: &TsmPath{Subsystem: "rebort"}, want: "/sys/kernel/config/tsm/rebort"},
{
input: &TsmPath{Subsystem: "repart", Entry: "j"},
want: "/sys/kernel/config/tsm/repart/j",
},
{
input: &TsmPath{Subsystem: "report", Entry: "r", Attribute: "inblob"},
want: "/sys/kernel/config/tsm/report/r/inblob",
},
}
for _, tc := range tcs {
got := tc.input.String()
if got != tc.want {
t.Errorf("%v.String() = %q, want %q", tc.input, got, tc.want)
}
}
}
func match(err error, want string) bool {
if err == nil && want == "" {
return true
}
return (err != nil && want != "" && strings.Contains(err.Error(), want))
}
func TestParseTsmPath(t *testing.T) {
tcs := []struct {
input string
want *TsmPath
wantErr string
}{
{
input: "not/to/configfs",
wantErr: `"not/to/configfs" does not begin with "/sys/kernel/config/tsm"`,
},
{
input: "///sys/kernel/config/tsm",
wantErr: `"/sys/kernel/config/tsm" does not contain a subsystem`,
},
{
input: "/sys/kernel/config/tsm/report/is/way/too/long",
wantErr: `"report/is/way/too/long" suffix expected to be of form`,
},
{
input: "/sys/kernel/config/tsm/a",
want: &TsmPath{Subsystem: "a"},
},
{
input: "/sys/kernel/config/tsm/a/b",
want: &TsmPath{Subsystem: "a", Entry: "b"},
},
{
input: "/sys/kernel/config/tsm/a/b/c",
want: &TsmPath{Subsystem: "a", Entry: "b", Attribute: "c"},
},
}
for _, tc := range tcs {
got, err := ParseTsmPath(tc.input)
if !match(err, tc.wantErr) {
t.Errorf("ParseTsmPath(%q) = %v, %v errored unexpectedly. Want %s",
tc.input, got, err, tc.wantErr)
}
if tc.wantErr == "" && *got != *tc.want {
t.Errorf("ParseTsmPath(%q) = %v, nil. Want %v", tc.input, *got, *tc.want)
}
}
}
func TestTempName(t *testing.T) {
tcs := []struct {
name string
pattern string
wantPrefix string
wantSuffix string
}{
{name: "empty"},
{
name: "no asterisk",
pattern: "hi",
wantPrefix: "hi",
},
{name: "1 asterisk at end",
pattern: "friend*",
wantPrefix: "friend",
},
{name: "many asterisks",
pattern: "friend*ly*monster",
wantPrefix: "friend*ly",
wantSuffix: "monster",
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got := TempName(rand.Reader, tc.pattern)
wantReplaceLen := len(tc.pattern) + randomPathSize
if strings.LastIndex(tc.pattern, "*") != -1 {
wantReplaceLen -= 1 // The * gets replaced, so subtract it.
}
if len(got) != wantReplaceLen {
t.Errorf("TempName(_, %q) = %q, whose length is not %d", tc.pattern, got, wantReplaceLen)
}
if !strings.HasPrefix(got, tc.wantPrefix) {
t.Errorf("TempName(_, %q) = %q, does not have prefix %q", tc.pattern, got, tc.wantPrefix)
}
if !strings.HasSuffix(got, tc.wantSuffix) {
t.Errorf("TempName(_, %q) = %q, does not have suffix %q", tc.pattern, got, tc.wantSuffix)
}
})
}
}
go-configfs-tsm-0.3.2/configfs/fakertmr/ 0000775 0000000 0000000 00000000000 14646004755 0020142 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/configfs/fakertmr/fakertmr.go 0000664 0000000 0000000 00000015022 14646004755 0022304 0 ustar 00root root 0000000 0000000 // Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package fakertmr defines a configfsi.Client for faking TSM behavior.
// The current implementation only supports TDX.
package fakertmr
import (
"crypto"
"crypto/rand"
"crypto/sha512"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strconv"
"syscall"
"github.com/google/go-configfs-tsm/configfs/configfsi"
)
const (
tsmRtmrDigest = "digest"
tsmPathIndex = "index"
tsmPathTcgMap = "tcg_map"
tsmRtmrSubsystem = "rtmr"
)
// RtmrSubsystem represents a fake configfs-tsm rtmr subsystem.
type RtmrSubsystem struct {
// WriteAttr called on any WriteFile to an attribute.
WriteAttr func(dirname string, attr string, contents []byte, indexMap map[int]bool) error
// ReadAttr is called on any non-InAddr key.
ReadAttr func(dirname string, attr string) ([]byte, error)
// Random is the source of randomness to use for MkdirTemp
Random io.Reader
// We use a temp folder to store the rtmr entries.
// The path to the fake rtmr subsystem.
Path string
// rtmrIndexMap contains set of rtmr indexes that have been initialized.
// If true, the rtmr index is initialized.
rtmrIndexMap map[int]bool
}
// RemoveAll implements configfsi.Client.
func (r *RtmrSubsystem) RemoveAll(path string) error {
return errors.New("rtmr subsystem does not support RemoveAll")
}
func readTdx(entry string, attr string) ([]byte, error) {
return os.ReadFile(path.Join(entry, attr))
}
func makeWriteTdx(root string) func(entry string, attr string, content []byte, indexMap map[int]bool) error {
return func(entry string, attr string, content []byte, indexMap map[int]bool) error {
switch attr {
case tsmRtmrDigest:
// Check if the content is a valid SHA384 hash.
if len(content) != crypto.SHA384.Size() {
return syscall.EINVAL
}
// Check if the entry is initialized.
content, err := os.ReadFile(filepath.Join(entry, tsmPathIndex))
if err != nil {
return err
}
rtmrIndex, err := strconv.Atoi(string(content))
if err != nil {
return err
}
if rtmrIndex != 2 && rtmrIndex != 3 {
return os.ErrPermission
}
oldDigest, err := os.ReadFile(filepath.Join(entry, tsmRtmrDigest))
if err != nil {
return err
}
newDigest := sha512.Sum384(append(oldDigest[:], content...))
if err := os.WriteFile(filepath.Join(entry, tsmRtmrDigest), newDigest[:], 0666); err != nil {
return err
}
case tsmPathIndex:
rtmrIndex, e := strconv.Atoi(string(content))
if e != nil {
return fmt.Errorf("WriteTdx: %v", e)
}
if rtmrIndex < 0 || rtmrIndex > 3 {
return fmt.Errorf("WriteTdx: invalid rtmr index %d. Index can only be a non-negative number", rtmrIndex)
}
if indexMap[rtmrIndex] {
return syscall.EBUSY
}
indexMap[rtmrIndex] = true
if err := os.WriteFile(filepath.Join(entry, tsmPathIndex), content, 0666); err != nil {
return err
}
var rtmrPcrMaps = map[int]string{
0: "1,7\n",
1: "2-6\n",
2: "8-15\n",
3: "\n",
}
// Write the tcgmap into a temp file and rename it to keep the read-only permission.
tempTsmPathTcgMap := filepath.Join(root, tsmPathTcgMap)
if err := os.WriteFile(tempTsmPathTcgMap, []byte(rtmrPcrMaps[rtmrIndex]), 0400); err != nil {
return err
}
if err := os.Rename(tempTsmPathTcgMap, filepath.Join(entry, tsmPathTcgMap)); err != nil {
return err
}
case tsmPathTcgMap:
return os.ErrPermission
default:
return fmt.Errorf("WriteTdx: unknown attribute %q", attr)
}
return nil
}
}
// ReadDir reads the directory named by dirname
// and returns a list of directory entries sorted by filename.
func (r *RtmrSubsystem) ReadDir(dirname string) ([]os.DirEntry, error) {
p, err := configfsi.ParseTsmPath(dirname)
if err != nil {
return nil, fmt.Errorf("ReadDir: %v", err)
}
if p.Entry != "" {
return nil, fmt.Errorf("ReadDir: rtmr tsm %q cannot have subdirectories", dirname)
}
return os.ReadDir(r.Path)
}
// MkdirTemp creates a new temporary directory in the rtmr subsystem.
func (r *RtmrSubsystem) MkdirTemp(dir, pattern string) (string, error) {
p, err := configfsi.ParseTsmPath(dir)
if err != nil {
return "", fmt.Errorf("MkdirTemp: Error %v", err)
}
if p.Entry != "" {
return "", fmt.Errorf("MkdirTemp: rtmr entry %q cannot have subdirectories", dir)
}
if err = os.MkdirAll(r.Path, 0755); err != nil {
return "", fmt.Errorf("MkdirTemp: %v", err)
}
name := configfsi.TempName(r.Random, pattern)
fakeRtmrPath := path.Join(r.Path, name)
if err = os.Mkdir(fakeRtmrPath, 0755); err != nil {
return "", fmt.Errorf("MkdirTemp: %v", err)
}
// Create empty index, digest and tcg_map files.
perms := []int{os.O_RDWR, os.O_RDWR, os.O_RDONLY}
modes := []os.FileMode{0600, 0600, 0400}
for i, attr := range []string{tsmPathIndex, tsmRtmrDigest, tsmPathTcgMap} {
p := filepath.Join(fakeRtmrPath, attr)
f, err := os.OpenFile(p, perms[i]|os.O_CREATE, modes[i])
if err != nil {
return "", fmt.Errorf("MkdirTemp: %v", err)
}
f.Close()
}
return path.Join(dir, name), nil
}
// ReadFile reads the contents of a file in the rtmr subsystem.
func (r *RtmrSubsystem) ReadFile(name string) ([]byte, error) {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return nil, fmt.Errorf("ReadFile: Error %v", err)
}
return r.ReadAttr(path.Join(r.Path, p.Entry), p.Attribute)
}
// WriteFile writes the contents to a file in the rtmr subsystem.
func (r *RtmrSubsystem) WriteFile(name string, content []byte) error {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return fmt.Errorf("WriteFile: %v", err)
}
if p.Attribute == "" {
return fmt.Errorf("WriteFile: no attribute specified to %q", name)
}
return r.WriteAttr(path.Join(r.Path, p.Entry), p.Attribute, content, r.rtmrIndexMap)
}
// CreateRtmrSubsystem creates a new rtmr subsystem.
// The current subsystem only supports TDX.
func CreateRtmrSubsystem(tempDir string) *RtmrSubsystem {
return &RtmrSubsystem{
Random: rand.Reader,
WriteAttr: makeWriteTdx(tempDir),
ReadAttr: readTdx,
Path: path.Join(tempDir, tsmRtmrSubsystem),
rtmrIndexMap: make(map[int]bool),
}
}
go-configfs-tsm-0.3.2/configfs/faketsm/ 0000775 0000000 0000000 00000000000 14646004755 0017761 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/configfs/faketsm/fakereport.go 0000664 0000000 0000000 00000021646 14646004755 0022463 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package faketsm
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path"
"sync"
"syscall"
"unicode/utf8"
"github.com/google/go-configfs-tsm/configfs/configfsi"
)
var ErrPrivLevelFormat = errors.New("privlevel must be 0-3")
const (
// subsystemName is the expected subsystem path entry under tsm for reports.
subsystemName = "report"
tsmInBlobSize = 64
renderBase = 10
)
// ReportAttributeState rewrites a writable attribute's value state. May also be readable.
type ReportAttributeState struct {
Value []byte
ReadWrite bool
}
// ReportEntry represents a report entry in the TSM report subsystem.
type ReportEntry struct {
mu sync.RWMutex
destroyed bool
ReadGeneration uint64
WriteGeneration uint64
// InAttrs represents the value of all WO attributes by name (relative to entry).
// All possible attributes ought to be mapped on creation.
InAttrs map[string]*ReportAttributeState
// ROAttrs is populated on ReadFile under mu and acts as a cache when
// generations align before calling ReadAttr.
ROAttrs map[string][]byte
}
// ReportSubsystem represents the general behavior of the configfs-tsm report subsystem
type ReportSubsystem struct {
// CheckInAttr called on any WriteFile to an attribute. If non-nil, WriteFile returns
// the error instead of writing. Called while holding client and entry locks.
CheckInAttr func(e *ReportEntry, attr string, contents []byte) error
// ReadAttr is called on any non-InAddr key while holding the client and entry locks.
ReadAttr func(e *ReportEntry, attr string) ([]byte, error)
// MakeEntry returns a fresh entry with all expected InAttrs. Called while holding
// the client lock.
MakeEntry func() *ReportEntry
mu sync.RWMutex
Entries map[string]*ReportEntry
// Random is the source of randomness to use for MkdirTemp
Random io.Reader
}
// Called while mu is held
func (e *ReportEntry) tryAdvanceWriteGeneration() error {
if e.destroyed {
return os.ErrNotExist
}
if e.WriteGeneration == e.ReadGeneration-1 {
return syscall.EBUSY
}
e.WriteGeneration += 1
return nil
}
// MkdirTemp creates a new temporary directory in the directory dir and returns the pathname
// of the new directory. Pattern semantics follow os.MkdirTemp.
func (r *ReportSubsystem) MkdirTemp(dir, pattern string) (string, error) {
p, err := configfsi.ParseTsmPath(dir)
if err != nil {
return "", fmt.Errorf("MkdirTemp: %v", err)
}
if p.Entry != "" {
return "", fmt.Errorf("report entry %q cannot have subdirectories", dir)
}
r.mu.Lock()
defer r.mu.Unlock()
if r.Entries == nil {
r.Entries = make(map[string]*ReportEntry)
}
name := configfsi.TempName(r.Random, pattern)
if _, ok := r.Entries[name]; ok {
return "", os.ErrExist
}
r.Entries[name] = r.MakeEntry()
return path.Join(dir, name), nil
}
func (e *ReportEntry) readCached(attr string) ([]byte, error) {
e.mu.RLock()
defer e.mu.RUnlock()
if e.destroyed {
return nil, os.ErrNotExist
}
// The only special attribute is "generation", since it peers into the
// mechanics of mutation.
if attr == "generation" {
return []byte(fmt.Sprintf("%d\n", e.WriteGeneration)), nil
}
if e.ReadGeneration != e.WriteGeneration {
return nil, syscall.EWOULDBLOCK
}
if a, ok := e.InAttrs[attr]; ok {
if !a.ReadWrite {
return nil, fmt.Errorf("%q is not readable", attr)
}
return bytes.Clone(a.Value), nil
}
if e.ROAttrs != nil {
if a, ok := e.ROAttrs[attr]; ok {
if len(a) != 0 {
return bytes.Clone(a), nil
}
return nil, nil
}
}
return nil, os.ErrNotExist
}
// ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
func (r *ReportSubsystem) ReadDir(dirname string) ([]os.DirEntry, error) {
return nil, errors.New("report subsystem does not support ReadDir")
}
// ReadFile reads the named file and returns the contents.
func (r *ReportSubsystem) ReadFile(name string) ([]byte, error) {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return nil, fmt.Errorf("ReadFile: %v", err)
}
if p.Attribute == "" {
return nil, fmt.Errorf("not an attribute: %q", name)
}
r.mu.RLock()
if r.Entries == nil {
return nil, os.ErrNotExist
}
e, ok := r.Entries[p.Entry]
if !ok {
r.mu.RUnlock()
return nil, os.ErrNotExist
}
r.mu.RUnlock()
if b, err := e.readCached(p.Attribute); (err == nil && len(b) != 0) || err != syscall.EWOULDBLOCK {
return b, err
}
e.mu.Lock()
defer e.mu.Unlock()
if e.ROAttrs == nil {
e.ROAttrs = make(map[string][]byte)
}
// It's possible another thread has populated the report between RUnlock and Lock.
if b, ok := e.ROAttrs[p.Attribute]; ok && e.ReadGeneration == e.WriteGeneration {
return b, nil
}
e.ROAttrs[p.Attribute] = nil
b, err := r.ReadAttr(e, p.Attribute)
if err != nil {
return nil, fmt.Errorf("ReadAttr(_, %q): %v", p.Attribute, err)
}
e.ROAttrs[p.Attribute] = b
return b, nil
}
// WriteFile writes data to the named file, creating it if necessary. The permissions
// are implementation-defined.
func (r *ReportSubsystem) WriteFile(name string, contents []byte) error {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return fmt.Errorf("WriteFile: %v", err)
}
if p.Attribute == "" {
return fmt.Errorf("cannot write to non-attribute: %q", name)
}
r.mu.Lock()
defer r.mu.Unlock()
e, ok := r.Entries[p.Entry]
if !ok {
return os.ErrNotExist
}
e.mu.Lock()
defer e.mu.Unlock()
if e.destroyed {
return os.ErrNotExist
}
if err := r.CheckInAttr(e, p.Attribute, contents); err != nil {
return fmt.Errorf("could not write %q: %v", name, err)
}
if err := e.tryAdvanceWriteGeneration(); err != nil {
return err
}
e.InAttrs[p.Attribute].Value = contents
return nil
}
// RemoveAll removes path and any children it contains.
func (r *ReportSubsystem) RemoveAll(name string) error {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return fmt.Errorf("RemoveAll: %v", err)
}
if p.Attribute != "" || p.Entry == "" || p.Subsystem != subsystemName {
return fmt.Errorf("RemoveAll(%q) expected report subsystem entry path", name)
}
r.mu.Lock()
defer r.mu.Unlock()
if r.Entries == nil {
return os.ErrNotExist
}
e, ok := r.Entries[p.Entry]
if !ok {
return os.ErrNotExist
}
// Don't delete while another operation is using the entry.
e.mu.Lock()
e.destroyed = true
delete(r.Entries, p.Entry)
e.mu.Unlock()
return nil
}
func renderOutBlob(privlevel, inblob []byte) []byte {
// checkv7 already ensures this does not error
priv, _ := configfsi.Kstrtouint(privlevel, renderBase, 2)
return []byte(fmt.Sprintf("privlevel: %d\ninblob: %s",
priv,
hex.EncodeToString(inblob)))
}
func readV7(privlevelFloor uint) func(*ReportEntry, string) ([]byte, error) {
return func(e *ReportEntry, attr string) ([]byte, error) {
switch attr {
case "provider":
return []byte("fake\n"), nil
case "auxblob":
return []byte(`auxblob`), nil
case "outblob":
privlevel := []byte("")
if a, ok := e.InAttrs["privlevel"]; ok && len(a.Value) > 0 {
privlevel = a.Value
}
inblob, ok := e.InAttrs["inblob"]
if !ok || len(inblob.Value) == 0 {
return nil, syscall.EINVAL
}
return renderOutBlob(privlevel, inblob.Value), nil
case "privlevel_floor":
return []byte(fmt.Sprintf("%d\n", privlevelFloor)), nil
}
return nil, os.ErrNotExist
}
}
func makeV7() *ReportEntry {
return &ReportEntry{
InAttrs: map[string]*ReportAttributeState{
"privlevel": {Value: []byte("0\n")},
"inblob": {},
},
}
}
func checkV7(privlevelFloor uint) func(*ReportEntry, string, []byte) error {
return func(e *ReportEntry, attr string, contents []byte) error {
switch attr {
case "inblob":
if len(contents) > tsmInBlobSize {
return syscall.EINVAL
}
case "privlevel":
if !utf8.Valid(contents) {
return ErrPrivLevelFormat
}
level, err := configfsi.Kstrtouint(contents, renderBase, 2)
if err != nil {
return ErrPrivLevelFormat
}
if uint(level) < privlevelFloor {
return fmt.Errorf("privlevel %d cannot be less than %d",
level, privlevelFloor)
}
default:
return fmt.Errorf("unwritable attribute: %q", attr)
}
return nil
}
}
// ReportV7 returns an empty report subsystem with attributes as specified in the configfs-tsm
// Patch v7 series.
func ReportV7(privlevelFloor uint) *ReportSubsystem {
return &ReportSubsystem{
MakeEntry: makeV7,
ReadAttr: readV7(privlevelFloor),
CheckInAttr: checkV7(privlevelFloor),
Random: rand.Reader,
}
}
go-configfs-tsm-0.3.2/configfs/faketsm/fakereport_test.go 0000664 0000000 0000000 00000011157 14646004755 0023516 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package faketsm
import (
"bytes"
"fmt"
"math/big"
"path"
"testing"
"github.com/google/go-configfs-tsm/configfs/configfsi"
"github.com/google/go-configfs-tsm/report"
)
func checkOutblobExpectation(inblob []byte, privlevel uint, outblob []byte) error {
want := renderOutBlob([]byte(fmt.Sprintf("%d\n", privlevel)), inblob)
if !bytes.Equal(want, outblob) {
return fmt.Errorf("got %q, want %q", string(outblob), string(want))
}
return nil
}
func makeNonce(id uint) []byte {
// The nonce is currently expected to always be size 64.
result := make([]byte, 64)
copy(result, []byte(big.NewInt(int64(id)).String()))
return result
}
func checkErr(err error) (bool, error) {
if err != nil {
if err := report.GetGenerationErr(err); err != nil {
return true, nil
}
return false, err
}
return false, nil
}
func runIteration(t testing.TB, c configfsi.Client, r *report.OpenReport, tc *runner) error {
t.Helper()
nonce := makeNonce(tc.id)
t.Logf("Writing inblob %v", nonce)
if err := r.WriteOption("inblob", nonce); err != nil {
return fmt.Errorf("could not write inblob in %d: %v", tc.id, err)
}
t.Logf("Writing privlevel %d", (tc.id % 4))
if err := r.WriteOption("privlevel", []byte(fmt.Sprintf("%d", (tc.id%4)))); err != nil {
return fmt.Errorf("could not set privlevel: %v", err)
}
t.Logf("Reading outblob")
out, err := r.ReadOption("outblob")
if done, err := checkErr(err); done || err != nil {
if err != nil {
return fmt.Errorf("outblob read on client %d failed: %v", tc.id, err)
}
return nil
}
t.Logf("Checking outblob %v", out)
if err := checkOutblobExpectation(nonce, (tc.id % 4), out); err != nil {
return fmt.Errorf("attestation invariant violated: %v", err)
}
return nil
}
type runner struct {
iterations int
id uint
done chan int
}
func runInterference(t testing.TB, c configfsi.Client, entryPath string, tc *runner) {
t.Helper()
for i := 0; i < tc.iterations; i++ {
r, err := report.UnsafeWrap(c, entryPath)
if err != nil {
t.Errorf("could not create report entry: %v", err)
tc.done <- 1
return
}
if err := runIteration(t, c, r, tc); err != nil {
t.Error(err)
tc.done <- 1
return
}
}
t.Logf("Posting done for %d", tc.id)
tc.done <- 0
}
func runNoninterference(t testing.TB, c configfsi.Client, tc *runner) {
t.Helper()
for i := 0; i < tc.iterations; i++ {
nonce := makeNonce(tc.id)
resp, err := report.Get(c, &report.Request{
InBlob: nonce,
Privilege: &report.Privilege{Level: (tc.id % 4)},
})
if err == nil {
err = checkOutblobExpectation(nonce, (tc.id % 4), resp.OutBlob)
}
if err != nil {
t.Error(err)
tc.done <- 1
return
}
}
t.Logf("Posting done for %d", tc.id)
tc.done <- 0
}
// clients-many concurrent routines attempt to get an output on the same entry with
// different inblobs.
func nonceAnonceB(t testing.TB, clients, iterations int) {
t.Helper()
c := ReportV7(0)
entryPath, err := c.MkdirTemp(path.Join(configfsi.TsmPrefix, "report"), "entry")
if err != nil {
t.Fatalf("could not create entry: %v", err)
}
t.Logf("made entry %s", entryPath)
defer c.RemoveAll(entryPath)
complete := make(chan int)
for i := 0; i < clients; i++ {
go runInterference(t, c, entryPath, &runner{
iterations: iterations,
id: uint(i),
done: complete})
}
// Each client should write to the channel.
for i := 0; i < clients; i++ {
code := <-complete
if code == 1 {
t.Fatalf("early failure")
}
}
t.Logf("doooone")
}
func noninterferenceByDesign(t testing.TB, clients, iterations int) {
t.Helper()
c := ReportV7(0)
complete := make(chan int)
for i := 0; i < clients; i++ {
go runNoninterference(t, c, &runner{
iterations: iterations,
id: uint(i),
done: complete})
}
// Each client should write to the channel.
for i := 0; i < clients; i++ {
code := <-complete
if code == 1 {
t.Fatalf("early failure")
}
}
}
func BenchmarkReportGenerationInterference(b *testing.B) {
nonceAnonceB(b, 4, b.N)
}
func BenchmarkReportGenerationNoninterference(b *testing.B) {
noninterferenceByDesign(b, 20, b.N)
}
go-configfs-tsm-0.3.2/configfs/faketsm/faketsm.go 0000664 0000000 0000000 00000005764 14646004755 0021756 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package faketsm defines a configfsi.Client for faking TSM behavior.
// The provider attribute returns "fake" and the attestation report format
// is just the state of the attributes. The certificate blob is part of the
// Client definition.
package faketsm
import (
"fmt"
"os"
"github.com/google/go-configfs-tsm/configfs/configfsi"
)
// Client provides a "fake" provider for configfs to emulate the /sys/kernel/config/tsm behavior.
// Dispatches to specialized subsystem Client interfaces.
type Client struct {
Subsystems map[string]configfsi.Client
}
func (c *Client) getSubsystem(name string) (configfsi.Client, error) {
p, err := configfsi.ParseTsmPath(name)
if err != nil {
return nil, fmt.Errorf("getSubsystem: %v", err)
}
if p.Subsystem == "" {
return nil, fmt.Errorf("faketsm: expected tsm subsystem in %q", name)
}
sub, ok := c.Subsystems[p.Subsystem]
if !ok {
return nil, fmt.Errorf("faketsm: unsupported subsystem %q", p.Subsystem)
}
return sub, nil
}
// ReadDir reads the directory named by dir and returns a list of directory entries.
func (c *Client) ReadDir(dir string) ([]os.DirEntry, error) {
if dir == "" {
return nil, fmt.Errorf("faketsm doesn't implement empty directory behavior")
}
sub, err := c.getSubsystem(dir)
if err != nil {
return nil, err
}
return sub.ReadDir(dir)
}
// MkdirTemp creates a new temporary directory in the directory dir and returns the pathname
// of the new directory. Pattern semantics follow os.MkdirTemp.
func (c *Client) MkdirTemp(dir, pattern string) (string, error) {
if dir == "" {
return "", fmt.Errorf("faketsm doesn't implement empty directory behavior")
}
sub, err := c.getSubsystem(dir)
if err != nil {
return "", err
}
return sub.MkdirTemp(dir, pattern)
}
// ReadFile reads the named file and returns the contents.
func (c *Client) ReadFile(name string) ([]byte, error) {
sub, err := c.getSubsystem(name)
if err != nil {
return nil, err
}
return sub.ReadFile(name)
}
// WriteFile writes data to the named file, creating it if necessary. The permissions
// are implementation-defined.
func (c *Client) WriteFile(name string, contents []byte) error {
sub, err := c.getSubsystem(name)
if err != nil {
return err
}
return sub.WriteFile(name, contents)
}
// RemoveAll removes path and any children it contains.
func (c *Client) RemoveAll(name string) error {
sub, err := c.getSubsystem(name)
if err != nil {
return err
}
return sub.RemoveAll(name)
}
go-configfs-tsm-0.3.2/configfs/linuxtsm/ 0000775 0000000 0000000 00000000000 14646004755 0020212 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/configfs/linuxtsm/aliases.go 0000664 0000000 0000000 00000002222 14646004755 0022160 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package linuxtsm
// The aliases.go file is for "convenience" functions when folks only want to use the
// Linux client.
import (
"github.com/google/go-configfs-tsm/report"
"go.uber.org/multierr"
)
// GetReport returns a one-shot configfs-tsm report given a report request.
func GetReport(req *report.Request) (*report.Response, error) {
var err error
client, err := MakeClient()
if err != nil {
return nil, err
}
r, err := report.Create(client, req)
if err != nil {
return nil, err
}
response, err := r.Get()
return response, multierr.Combine(r.Destroy(), err)
}
go-configfs-tsm-0.3.2/configfs/linuxtsm/linuxtsm.go 0000664 0000000 0000000 00000004344 14646004755 0022431 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package linuxtsm defines a configfsi.Client for Linux OS operations on configfs.
package linuxtsm
import (
"fmt"
"os"
"path"
"github.com/google/go-configfs-tsm/configfs/configfsi"
)
// client provides configfsi.Client for /sys/kernel/config/tsm file operations in Linux.
type client struct{}
// MkdirTemp creates a new temporary directory in the directory dir and returns the pathname
// of the new directory. Pattern semantics follow os.MkdirTemp.
func (*client) MkdirTemp(dir, pattern string) (string, error) {
return os.MkdirTemp(dir, pattern)
}
// ReadFile reads the named file and returns the contents.
func (*client) ReadFile(name string) ([]byte, error) {
return os.ReadFile(name)
}
// WriteFile writes data to the named file, creating it if necessary. The permissions
// are implementation-defined.
func (*client) WriteFile(name string, contents []byte) error {
return os.WriteFile(name, contents, 0220)
}
// RemoveAll removes path and any children it contains.
func (*client) RemoveAll(path string) error {
return os.Remove(path)
}
// ReadDir reads the directory named by dirname and returns a list of directory
// entries sorted by filename.
func (*client) ReadDir(dirname string) ([]os.DirEntry, error) {
return os.ReadDir(dirname)
}
// MakeClient returns a "real" client for using configfs for TSM use.
func MakeClient() (configfsi.Client, error) {
// Linux client expects just the "report" subsystem for now.
checkPath := path.Join(configfsi.TsmPrefix, "report")
info, err := os.Stat(checkPath)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("expected %s to be a directory", checkPath)
}
return &client{}, nil
}
go-configfs-tsm-0.3.2/go.mod 0000664 0000000 0000000 00000000130 14646004755 0015631 0 ustar 00root root 0000000 0000000 module github.com/google/go-configfs-tsm
go 1.19
require go.uber.org/multierr v1.11.0
go-configfs-tsm-0.3.2/go.sum 0000664 0000000 0000000 00000000743 14646004755 0015670 0 ustar 00root root 0000000 0000000 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
go-configfs-tsm-0.3.2/report/ 0000775 0000000 0000000 00000000000 14646004755 0016044 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/report/report.go 0000664 0000000 0000000 00000016226 14646004755 0017715 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package report provides an API to the configfs/tsm/report subsystem for collecting
// attestation reports and associated certificates.
package report
import (
"errors"
"fmt"
"github.com/google/go-configfs-tsm/configfs/configfsi"
"go.uber.org/multierr"
)
const (
subsystem = "report"
subsystemPath = configfsi.TsmPrefix + "/" + subsystem
numberAttributeBase = 10
)
// Privilege represents the requested privilege information at which a report should
// be created.
type Privilege struct {
Level uint
}
// Request represents an open request for an attestation report.
type Request struct {
InBlob []byte
Privilege *Privilege
GetAuxBlob bool
}
// OpenReport represents a created tsm report subtree with internal expectations for the generation.
type OpenReport struct {
InBlob []byte
Privilege *Privilege
GetAuxBlob bool
entry *configfsi.TsmPath
expectedGeneration uint64
client configfsi.Client
}
// Response represents a common case response for getting at attestation report to avoid
// multiple attribute access calls.
type Response struct {
Provider string
OutBlob []byte
AuxBlob []byte
}
// GenerationErr is returned when an attribute's value is invalid due to mismatched expectations
// on the number of writes to a report entry.
type GenerationErr struct {
Got uint64
Want uint64
Attribute string
}
// Error returns the human-readable explanation for the error.
func (e *GenerationErr) Error() string {
return fmt.Sprintf("report generation was %d when expecting %d while reading property %q",
e.Got, e.Want, e.Attribute)
}
// GetGenerationErr returns the GenerationErr contained in an error with 0 or 1 wraps.
func GetGenerationErr(err error) *GenerationErr {
var result *GenerationErr
if err != nil && (errors.As(err, &result) || errors.As(errors.Unwrap(err), &result)) {
return result
}
return nil
}
func (r *OpenReport) attribute(subtree string) string {
a := *r.entry
a.Attribute = subtree
return a.String()
}
func readUint64File(client configfsi.Client, p string) (uint64, error) {
data, err := client.ReadFile(p)
if err != nil {
return 0, fmt.Errorf("could not read %q: %v", p, err)
}
return configfsi.Kstrtouint(data, numberAttributeBase, 64)
}
// CreateOpenReport returns a newly-created entry in the configfs-tsm report subtree with an initial
// expected generation value.
func CreateOpenReport(client configfsi.Client) (*OpenReport, error) {
entry, err := client.MkdirTemp(subsystemPath, "entry")
if err != nil {
return nil, fmt.Errorf("could not create report entry in configfs: %v", err)
}
return UnsafeWrap(client, entry)
}
// UnsafeWrap returns a new OpenReport for a given report entry.
func UnsafeWrap(client configfsi.Client, entryPath string) (r *OpenReport, err error) {
p, _ := configfsi.ParseTsmPath(entryPath)
r = &OpenReport{
client: client,
entry: &configfsi.TsmPath{Subsystem: subsystem, Entry: p.Entry},
}
r.expectedGeneration, err = readUint64File(client, r.attribute("generation"))
if err != nil {
// The report was created but couldn't be properly initialized.
return nil, multierr.Combine(r.Destroy(), err)
}
return r, nil
}
// Create returns a newly-created entry in the configfs-tsm report subtree with common inputs
// for the Get() method initialized from the request.
func Create(client configfsi.Client, req *Request) (*OpenReport, error) {
r, err := CreateOpenReport(client)
if err != nil {
return nil, err
}
r.InBlob = req.InBlob // InBlob is not a copy!
r.Privilege = req.Privilege
r.GetAuxBlob = req.GetAuxBlob
return r, nil
}
// Destroy returns an error if the configfs report subtree cannot be removed. Will not error for
// partially initialized or already-destroyed reports.
func (r *OpenReport) Destroy() error {
if r.entry != nil {
if err := r.client.RemoveAll(r.entry.String()); err != nil {
return err
}
r.entry = nil
}
return nil
}
// PrivilegeLevelFloor returns the privlevel_floor attribute interpreted as the uint type it is.
func (r *OpenReport) PrivilegeLevelFloor() (uint, error) {
data, err := r.ReadOption("privlevel_floor")
if err != nil {
return 0, err
}
i, err := configfsi.Kstrtouint(data, numberAttributeBase, 32)
if err != nil {
return 0, fmt.Errorf("could not parse privlevel_floor data %v as int: %v", data, err)
}
return uint(i), nil
}
// WriteOption sets a configfs report option to the provided data and internally tracks
// the generation that should be expected on the next ReadOption.
func (r *OpenReport) WriteOption(subtree string, data []byte) error {
if err := r.client.WriteFile(r.attribute(subtree), data); err != nil {
return fmt.Errorf("could not write report %s: %v", subtree, err)
}
r.expectedGeneration += 1
return nil
}
// ReadOption is a safe accessor to a readable attribute of a report. Returns an error if there is
// any detected tampering to the ongoing request.
func (r *OpenReport) ReadOption(subtree string) ([]byte, error) {
data, err := r.client.ReadFile(r.attribute(subtree))
if err != nil {
return nil, fmt.Errorf("could not read report property %q: %v", subtree, err)
}
gotGeneration, err := readUint64File(r.client, r.attribute("generation"))
if err != nil {
return nil, err
}
if gotGeneration != r.expectedGeneration {
return nil, &GenerationErr{Got: gotGeneration, Want: r.expectedGeneration, Attribute: subtree}
}
return data, nil
}
// Get returns the requested report data after initializing the context to the expected
// parameters. Returns an error if the kernel reports an error or there is a difference in expected
// generation value.
func (r *OpenReport) Get() (*Response, error) {
var err error
if err := r.WriteOption("inblob", r.InBlob); err != nil {
return nil, err
}
if r.Privilege != nil {
if err := r.WriteOption("privlevel", []byte(fmt.Sprintf("%d", r.Privilege.Level))); err != nil {
return nil, err
}
}
resp := &Response{}
if r.GetAuxBlob {
resp.AuxBlob, err = r.ReadOption("auxblob")
if err != nil {
return nil, fmt.Errorf("could not read report auxblob: %w", err)
}
}
resp.OutBlob, err = r.ReadOption("outblob")
if err != nil {
return nil, fmt.Errorf("could not read report outblob: %w", err)
}
providerData, err := r.ReadOption("provider")
if err != nil {
return nil, err
}
resp.Provider = string(providerData)
return resp, nil
}
// Get returns a one-shot configfs-tsm report given a report request.
func Get(client configfsi.Client, req *Request) (*Response, error) {
var err error
r, err := Create(client, req)
if err != nil {
return nil, err
}
response, err := r.Get()
return response, multierr.Combine(r.Destroy(), err)
}
go-configfs-tsm-0.3.2/report/report_test.go 0000664 0000000 0000000 00000005033 14646004755 0020746 0 ustar 00root root 0000000 0000000 // Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package report
import (
"bytes"
"strings"
"testing"
"github.com/google/go-configfs-tsm/configfs/configfsi"
"github.com/google/go-configfs-tsm/configfs/faketsm"
)
func TestGet(t *testing.T) {
c := &faketsm.Client{Subsystems: map[string]configfsi.Client{"report": faketsm.ReportV7(0)}}
req := &Request{
InBlob: []byte("lessthan64bytesok"),
GetAuxBlob: true,
}
resp, err := Get(c, req)
if err != nil {
t.Fatalf("Get(%+v) = %+v, %v, want nil", req, resp, err)
}
wantOut := "privlevel: 0\ninblob: 6c6573737468616e363462797465736f6b"
if !bytes.Equal(resp.OutBlob, []byte(wantOut)) {
t.Errorf("OutBlob %v is not %v", string(resp.OutBlob), wantOut)
}
wantProvider := "fake\n"
if resp.Provider != wantProvider {
t.Errorf("provider = %q, want %q", resp.Provider, wantProvider)
}
if !bytes.Equal(resp.AuxBlob, []byte(`auxblob`)) {
t.Errorf("auxblob = %v, want %v", resp.AuxBlob, []byte(`auxblob`))
}
}
func TestGetErr(t *testing.T) {
tcs := []struct {
name string
req *Request
floor uint
wantErr string
}{
{
name: "inblob too big",
req: &Request{
InBlob: make([]byte, 4096),
},
wantErr: "invalid argument",
},
{
name: "privlevel too high",
req: &Request{
InBlob: make([]byte, 64),
Privilege: &Privilege{Level: 300},
},
wantErr: "privlevel must be 0-3",
},
{
name: "missing inblob",
req: &Request{},
wantErr: "invalid argument",
},
{
name: "privlevel too low",
req: &Request{
InBlob: make([]byte, 64),
Privilege: &Privilege{Level: 0},
},
floor: 1,
wantErr: "privlevel 0 cannot be less than 1",
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
c := &faketsm.Client{Subsystems: map[string]configfsi.Client{"report": faketsm.ReportV7(tc.floor)}}
resp, err := Get(c, tc.req)
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("Get(%+v) = %+v, %v, want %q", tc.req, resp, err, tc.wantErr)
}
})
}
}
go-configfs-tsm-0.3.2/rtmr/ 0000775 0000000 0000000 00000000000 14646004755 0015515 5 ustar 00root root 0000000 0000000 go-configfs-tsm-0.3.2/rtmr/rtmr.go 0000664 0000000 0000000 00000013041 14646004755 0017027 0 ustar 00root root 0000000 0000000 // Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package rtmr provides an API to the configfs/tsm/rtmr subsystem for
// extending runtime measurements to RTMR registers.
package rtmr
import (
"crypto"
"fmt"
"strconv"
"github.com/google/go-configfs-tsm/configfs/configfsi"
)
const (
rtmrSubsystem = "rtmrs"
tsmRtmrPrefix = configfsi.TsmPrefix + "/" + rtmrSubsystem
// The digest of the rtmr register.
tsmRtmrDigest = "digest"
// A Runtime Measurement Register (RTMR) hardware index.
tsmPathIndex = "index"
// A representation of the architecturally defined mapping between this RTMR and one or more TCG TPM PCRs
tsmPathTcgMap = "tcg_map"
)
// Extend is a struct that represents a rtmr entry in the configfs.
type Extend struct {
RtmrIndex int
entry *configfsi.TsmPath
client configfsi.Client
}
// Response is a struct that represents the response of reading a rtmr entry in the configfs.
type Response struct {
RtmrIndex int
digest []byte
tcgMap []byte
}
func (r *Extend) attribute(subtree string) string {
a := *r.entry
a.Attribute = subtree
return a.String()
}
// extendDigest extends the measurement to the rtmr with the given hash.
func (r *Extend) extendDigest(hash []byte) error {
if err := r.client.WriteFile(r.attribute(tsmRtmrDigest), hash); err != nil {
return fmt.Errorf("could not write digest to rmtr%d: %v", r.RtmrIndex, err)
}
return nil
}
// getDigest returns the digest of the rtmr.
func (r *Extend) getDigest() ([]byte, error) {
return r.client.ReadFile(r.attribute(tsmRtmrDigest))
}
// getTcgMap returns the tcg map of the rtmr.
func (r *Extend) getTcgMap() ([]byte, error) {
return r.client.ReadFile(r.attribute(tsmPathTcgMap))
}
// validateIndex checks if the rtmr index matches the expected value.
func (r *Extend) validateIndex() bool {
if r == nil {
return false
}
indexBytes, err := r.client.ReadFile(r.attribute(tsmPathIndex))
if err != nil {
return false
}
index, err := configfsi.Kstrtouint(indexBytes, 10, 64)
if err != nil {
return false
}
if int(index) != r.RtmrIndex {
return false
}
return true
}
// setRtmrIndex sets a configfs rtmr entry to the given index.
// It reports an error if the index cannot be written.
func (r *Extend) setRtmrIndex() error {
indexBytes := []byte(strconv.Itoa(r.RtmrIndex)) // Convert index to []byte
indexPath := r.attribute(tsmPathIndex)
if err := r.client.WriteFile(indexPath, indexBytes); err != nil {
return fmt.Errorf("could not write index %s: %v", indexPath, err)
}
return nil
}
// searchRtmrInterface searches for an rtmr entry in the configfs.
func searchRtmrInterface(client configfsi.Client, index int) *Extend {
root := tsmRtmrPrefix
entries, err := client.ReadDir(root)
if err != nil {
return nil
}
for _, d := range entries {
if d.IsDir() {
r := &Extend{
RtmrIndex: index,
entry: &configfsi.TsmPath{Subsystem: rtmrSubsystem, Entry: d.Name()},
client: client,
}
if r.validateIndex() {
return r
}
}
}
return nil
}
// createRtmrInterface creates a new rtmr entry in the configfs.
func createRtmrInterface(client configfsi.Client, index int) (*Extend, error) {
entryPath, err := client.MkdirTemp(tsmRtmrPrefix, fmt.Sprintf("rtmr%d-", index))
if err != nil {
return nil, err
}
p, _ := configfsi.ParseTsmPath(entryPath)
r := &Extend{
RtmrIndex: index,
entry: &configfsi.TsmPath{Subsystem: rtmrSubsystem, Entry: p.Entry},
client: client,
}
if err := r.setRtmrIndex(); err != nil {
return nil, fmt.Errorf("could not set rtmr index %d: %v", index, err)
}
return r, nil
}
// getRtmrInterface returns the rtmr entry in the configfs.
func getRtmrInterface(client configfsi.Client, index int) (*Extend, error) {
// The configfs-tsm interface only allows one rtmr entry for a given index.
// If the rtmr entry already exists, we should extend the digest to it.
var err error
r := searchRtmrInterface(client, index)
if r == nil {
r, err = createRtmrInterface(client, index)
}
return r, err
}
// ExtendDigest extends the measurement to the rtmr with the given digest.
func ExtendDigest(client configfsi.Client, rtmr int, digest []byte) error {
if len(digest) != crypto.SHA384.Size() {
return fmt.Errorf("the length of the digest must be %d bytes, the input is %d bytes", crypto.SHA384.Size(), len(digest))
}
if rtmr < 0 {
return fmt.Errorf("invalid rtmr index %d. Index can only be a non-negative number", rtmr)
}
r, err := getRtmrInterface(client, rtmr)
if err != nil {
return err
}
return r.extendDigest(digest)
}
// GetDigest returns the digest and the tcg map of a given rtmr index.
func GetDigest(client configfsi.Client, rtmr int) (*Response, error) {
if rtmr < 0 {
return nil, fmt.Errorf("invalid rtmr index %d. Index can only be a non-negative number", rtmr)
}
r, err := getRtmrInterface(client, rtmr)
if err != nil {
return nil, err
}
digest, err := r.getDigest()
if err != nil {
return nil, err
}
tcgmap, err := r.getTcgMap()
if err != nil {
return nil, err
}
return &Response{
RtmrIndex: rtmr,
digest: digest,
tcgMap: tcgmap,
}, nil
}
go-configfs-tsm-0.3.2/rtmr/rtmr_test.go 0000664 0000000 0000000 00000010012 14646004755 0020061 0 ustar 00root root 0000000 0000000 // Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtmr
import (
"bytes"
"strings"
"testing"
"github.com/google/go-configfs-tsm/configfs/fakertmr"
)
func TestExtendDigestErr(t *testing.T) {
var sha384Hash [48]byte
tcsErr := []struct {
rtmr int
digest []byte
wantErr string
}{
{rtmr: 1, digest: sha384Hash[:], wantErr: "could not write digest to rmtr1"},
{rtmr: 3, digest: []byte("aaaaaaaa"), wantErr: "the length of the digest must be 48 bytes"},
{rtmr: -1, digest: sha384Hash[:], wantErr: "invalid rtmr index -1. Index can only be a non-negative number"},
}
client := fakertmr.CreateRtmrSubsystem(t.TempDir())
for _, tc := range tcsErr {
err := ExtendDigest(client, tc.rtmr, tc.digest)
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("ExtendtoRtmrClient(%d, %q) failed: %v, want %q", tc.rtmr, tc.digest, err, tc.wantErr)
}
}
}
func TestExtendDigestRtmrOk(t *testing.T) {
var sha384Hash [48]byte
tcsOk := []struct {
rtmr int
digest []byte
}{
{rtmr: 2, digest: sha384Hash[:]},
{rtmr: 3, digest: sha384Hash[:]},
// Test the same rtmr index with an existing entry.
{rtmr: 3, digest: sha384Hash[:]},
}
client := fakertmr.CreateRtmrSubsystem(t.TempDir())
for _, tc := range tcsOk {
err := ExtendDigest(client, tc.rtmr, tc.digest)
if err != nil {
t.Fatalf("ExtendtoRtmrClient (%d, %q) failed: %v", tc.rtmr, tc.digest, err)
}
}
}
func TestGetDigestErr(t *testing.T) {
tcsErr := []struct {
rtmr int
wantErr string
}{
{rtmr: -1, wantErr: "invalid rtmr index -1. Index can only be a non-negative number"},
}
client := fakertmr.CreateRtmrSubsystem(t.TempDir())
for _, tc := range tcsErr {
_, err := GetDigest(client, tc.rtmr)
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("GetDigestRtmr(%d) failed: %v, want %q", tc.rtmr, err, tc.wantErr)
}
}
}
func TestGetDigestOk(t *testing.T) {
var sha384Hash [48]byte
tcsOk := []struct {
rtmr int
digest []byte
tcgMap []byte
}{
{rtmr: 0, digest: sha384Hash[:], tcgMap: []byte("1,7\n")},
{rtmr: 1, digest: sha384Hash[:], tcgMap: []byte("2-6\n")},
{rtmr: 2, digest: sha384Hash[:], tcgMap: []byte("8-15\n")},
{rtmr: 3, digest: sha384Hash[:], tcgMap: []byte("\n")},
// Test the same rtmr index with an existing entry.
{rtmr: 2, digest: sha384Hash[:], tcgMap: []byte("8-15\n")},
}
client := fakertmr.CreateRtmrSubsystem(t.TempDir())
for _, tc := range tcsOk {
r, err := GetDigest(client, tc.rtmr)
if err != nil {
t.Fatalf("GetDigestRtmr(%d) failed: %v", tc.rtmr, err)
}
if r.RtmrIndex != tc.rtmr {
t.Fatalf("GetDigestRtmr(%d) failed: got %d, want %d", tc.rtmr, r.RtmrIndex, tc.rtmr)
}
if !bytes.Equal(r.tcgMap, tc.tcgMap) {
t.Fatalf("GetDigestRtmr(%d) failed: got %q, want %q", tc.rtmr, r.tcgMap, tc.tcgMap)
}
}
}
func TestGetRtmrDigestAndExtendDigest(t *testing.T) {
var sha384Hash [48]byte
sha384Hash[0] = 0x01
client := fakertmr.CreateRtmrSubsystem(t.TempDir())
rtmrIndex := 3
// GetDigest
digest1, err := GetDigest(client, rtmrIndex)
if err != nil {
t.Fatalf("GetDigest(%d) failed: %v", rtmrIndex, err)
}
// ExtendDigest
err = ExtendDigest(client, rtmrIndex, sha384Hash[:])
if err != nil {
t.Fatalf("ExtendDigest(%d) failed: %v", rtmrIndex, err)
}
// GetDigest
digest2, err := GetDigest(client, rtmrIndex)
if err != nil {
t.Fatalf("GetDigest(%d) failed: %v", rtmrIndex, err)
}
if bytes.Equal(digest1.digest, digest2.digest) {
t.Fatalf("rtmr%q does not change after an extend %q", rtmrIndex, digest2.digest)
}
}