Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TDX quote provider to attest and verify. #405

Merged
merged 2 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
101 changes: 86 additions & 15 deletions client/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
sg "github.com/google/go-sev-guest/client"
tg "github.com/google/go-tdx-guest/client"
tabi "github.com/google/go-tdx-guest/client/linuxabi"
tpb "github.com/google/go-tdx-guest/proto/tdx"
pb "github.com/google/go-tpm-tools/proto/attest"
)

Expand All @@ -19,9 +20,9 @@ const (
)

// TEEDevice is an interface to add an attestation report from a TEE technology's
// attestation driver.
// attestation driver or quote provider.
type TEEDevice interface {
jkl73 marked this conversation as resolved.
Show resolved Hide resolved
// AddAttestation uses the TEE device's attestation driver to collect an
// AddAttestation uses the TEE device's attestation driver or quote provider to collect an
// attestation report, then adds it to the correct field of `attestation`.
AddAttestation(attestation *pb.Attestation, options AttestOpts) error
// Close finalizes any resources in use by the TEEDevice.
Expand Down Expand Up @@ -144,10 +145,17 @@ type SevSnpDevice struct {

// TdxDevice encapsulates the TDX attestation device to add its attestation quote
// to a pb.Attestation.
// Deprecated: TdxDevice is deprecated. It is recommended to use TdxQuoteProvider.
jkl73 marked this conversation as resolved.
Show resolved Hide resolved
type TdxDevice struct {
Device tg.Device
}

// TdxQuoteProvider encapsulates the TDX attestation device to add its attestation quote
// to a pb.Attestation.
type TdxQuoteProvider struct {
QuoteProvider tg.QuoteProvider
}

// CreateSevSnpDevice opens the SEV-SNP attestation driver and wraps it with behavior
// that allows it to add an attestation report to pb.Attestation.
func CreateSevSnpDevice() (*SevSnpDevice, error) {
Expand Down Expand Up @@ -193,6 +201,8 @@ func (d *SevSnpDevice) Close() error {

// CreateTdxDevice opens the TDX attestation driver and wraps it with behavior
// that allows it to add an attestation quote to pb.Attestation.
// Deprecated: TdxDevice is deprecated, and use of CreateTdxQuoteProvider is
// recommended to create a TEEDevice.
func CreateTdxDevice() (*TdxDevice, error) {
d, err := tg.OpenDevice()
if err != nil {
Expand All @@ -206,21 +216,15 @@ func CreateTdxDevice() (*TdxDevice, error) {
// contents of opts.Nonce.
func (d *TdxDevice) AddAttestation(attestation *pb.Attestation, opts AttestOpts) error {
var tdxNonce [tabi.TdReportDataSize]byte
if len(opts.TEENonce) == 0 {
copy(tdxNonce[:], opts.Nonce)
} else if len(opts.TEENonce) != tabi.TdReportDataSize {
return fmt.Errorf("the TEENonce size is %d. Intel TDX device requires %d", len(opts.TEENonce), tabi.TdReportDataSize)
} else {
copy(tdxNonce[:], opts.TEENonce)
err := fillTdxNonce(opts, tdxNonce[:])
if err != nil {
return err
}
quote, err := tg.GetQuote(d.Device, tdxNonce)
if err != nil {
return err
}
attestation.TeeAttestation = &pb.Attestation_TdxAttestation{
TdxAttestation: quote,
}
return nil
return setTeeAttestationTdxQuote(quote, attestation)
}

// Close will free the device handle held by the TdxDevice. Calling more
Expand All @@ -234,6 +238,73 @@ func (d *TdxDevice) Close() error {
return nil
}

// CreateTdxQuoteProvider creates the TDX quote provider and wraps it with behavior
// that allows it to add an attestation quote to pb.Attestation.
func CreateTdxQuoteProvider() (*TdxQuoteProvider, error) {
jkl73 marked this conversation as resolved.
Show resolved Hide resolved
qp, err := tg.GetQuoteProvider()
if err != nil {
return nil, err
}
if qp.IsSupported() != nil {
// TDX quote provider has a fallback mechanism to fetch attestation quote
// via device driver in case ConfigFS is not supported, so checking for TDX
// device availability here. Once Device interface is fully removed from
// subsequent go-tdx-guest versions, then below OpenDevice call should be
// removed as well.
d, err2 := tg.OpenDevice()
if err2 != nil {
return nil, fmt.Errorf("neither TDX device, nor quote provider is supported")
}
d.Close()
}

return &TdxQuoteProvider{QuoteProvider: qp}, nil
}

// AddAttestation will get the TDX attestation quote given opts.TEENonce
// and add them to `attestation`. If opts.TEENonce is empty, then uses
// contents of opts.Nonce.
func (qp *TdxQuoteProvider) AddAttestation(attestation *pb.Attestation, opts AttestOpts) error {
var tdxNonce [tabi.TdReportDataSize]byte
err := fillTdxNonce(opts, tdxNonce[:])
if err != nil {
return err
}
quote, err := tg.GetQuote(qp.QuoteProvider, tdxNonce)
if err != nil {
return err
}
return setTeeAttestationTdxQuote(quote, attestation)
}

// Close will free resources held by QuoteProvider.
func (qp *TdxQuoteProvider) Close() error {
return nil
}

func fillTdxNonce(opts AttestOpts, tdxNonce []byte) error {
if len(opts.TEENonce) == 0 {
copy(tdxNonce[:], opts.Nonce)
} else if len(opts.TEENonce) != tabi.TdReportDataSize {
return fmt.Errorf("the TEENonce size is %d. Intel TDX device requires %d", len(opts.TEENonce), tabi.TdReportDataSize)
} else {
copy(tdxNonce[:], opts.TEENonce)
}
return nil
}

func setTeeAttestationTdxQuote(quote any, attestation *pb.Attestation) error {
switch q := quote.(type) {
case *tpb.QuoteV4:
attestation.TeeAttestation = &pb.Attestation_TdxAttestation{
TdxAttestation: q,
}
default:
return fmt.Errorf("unsupported quote type: %T", quote)
}
return nil
}

// Does best effort to get a TEE hardware rooted attestation, but won't fail fatally
// unless the user provided a TEEDevice object.
func getTEEAttestationReport(attestation *pb.Attestation, opts AttestOpts) error {
Expand All @@ -257,11 +328,11 @@ func getTEEAttestationReport(attestation *pb.Attestation, opts AttestOpts) error
}

// Try TDX.
if device, err := CreateTdxDevice(); err == nil {
if quoteProvider, err := CreateTdxQuoteProvider(); err == nil {
// Don't return errors if the attestation collection fails, since
// the user didn't specify a TEEDevice.
device.AddAttestation(attestation, opts)
device.Close()
quoteProvider.AddAttestation(attestation, opts)
quoteProvider.Close()
return nil
}
// Add more devices here.
Expand Down
74 changes: 74 additions & 0 deletions client/attest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,77 @@ func TestTdxDevice(t *testing.T) {
})
}
}

func TestTdxQuoteProvider(t *testing.T) {
rwc := test.GetTPM(t)
defer CheckedClose(t, rwc)

ak, err := AttestationKeyRSA(rwc)
if err != nil {
t.Fatalf("Failed to generate test AK: %v", err)
}

someNonce := []byte("some nonce")
var someNonce64 [64]byte
copy(someNonce64[:], someNonce)
var nonce64 [64]byte
copy(nonce64[:], []byte("noncey business"))
mockTdxQuoteProvider := tgtestclient.GetMockTdxQuoteProvider([]tgtest.TestCase{
{
Input: someNonce64,
Quote: tgtestdata.RawQuote,
},
{
Input: nonce64,
Quote: tgtestdata.RawQuote,
},
}, t)

testcases := []struct {
name string
opts AttestOpts
wantReportData [64]byte
wantErr string
}{
{
name: "Happy case no nonce",
opts: AttestOpts{
Nonce: someNonce,
TEEDevice: &TdxQuoteProvider{mockTdxQuoteProvider},
},
wantReportData: someNonce64,
},
{
name: "Happy case with nonce",
opts: AttestOpts{
Nonce: someNonce,
TEEDevice: &TdxQuoteProvider{mockTdxQuoteProvider},
TEENonce: nonce64[:],
},
wantReportData: nonce64,
},
{
name: "TEE nonce without TEE",
opts: AttestOpts{
Nonce: someNonce,
TEENonce: nonce64[:],
},
wantErr: "got non-nil TEENonce when TEEDevice is nil",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
att, err := ak.Attest(tc.opts)
if (err == nil && tc.wantErr != "") || (err != nil && !strings.Contains(err.Error(), tc.wantErr)) {
t.Fatalf("Attest(%v) = %v, want %q", tc.opts, err, tc.wantErr)
}
// Successful attestation should include a TDX attestation.
if err == nil {
_, ok := att.GetTeeAttestation().(*pb.Attestation_TdxAttestation)
if !ok {
t.Fatalf("Attestation missing TDX attestation: %v", att.GetTeeAttestation())
}
}
})
}
}
4 changes: 2 additions & 2 deletions cmd/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ hardware and guarantees a fresh quote.
}
attestOpts.TEENonce = teeNonce
case Tdx:
attestOpts.TEEDevice, err = client.CreateTdxDevice()
attestOpts.TEEDevice, err = client.CreateTdxQuoteProvider()
if err != nil {
return fmt.Errorf("failed to open %s device: %v", Tdx, err)
return fmt.Errorf("failed to create %s quote provider: %v", Tdx, err)
}
attestOpts.TEENonce = teeNonce
case "":
Expand Down
7 changes: 3 additions & 4 deletions cmd/attest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,11 @@ func TestTdxAttestTeeNonceFail(t *testing.T) {
}

// TEENonce with length less than 64 bytes.
tdxTestDevice := tgtestclient.GetTdxGuest([]tgtest.TestCase{
mockTdxQuoteProvider := tgtestclient.GetMockTdxQuoteProvider([]tgtest.TestCase{
{
Input: [64]byte{1, 2, 3, 4},
},
}, t)
defer tdxTestDevice.Close()

ak, err := client.AttestationKeyRSA(rwc)
if err != nil {
Expand All @@ -356,7 +355,7 @@ func TestTdxAttestTeeNonceFail(t *testing.T) {
attestopts := client.AttestOpts{
Nonce: []byte{1, 2, 3, 4},
TEENonce: []byte{1, 2, 3, 4},
TEEDevice: &client.TdxDevice{Device: tdxTestDevice},
TEEDevice: &client.TdxQuoteProvider{QuoteProvider: mockTdxQuoteProvider},
}
_, err = ak.Attest(attestopts)
if err == nil {
Expand All @@ -380,7 +379,7 @@ func TestHardwareAttestationPass(t *testing.T) {
teetech string
wanterr string
}{
{"TdxPass", "1234", "tdx", "failed to open tdx device"},
{"TdxPass", "1234", "tdx", "failed to create tdx quote provider"},
{"SevSnpPass", "1234", "sev-snp", "failed to open sev-snp device"},
}
for _, op := range tests {
Expand Down
2 changes: 1 addition & 1 deletion cmd/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func TestHwAttestationPass(t *testing.T) {
teetech string
wanterr string
}{
{"TdxPass", "1234", "tdx", "failed to open tdx device"},
{"TdxPass", "1234", "tdx", "failed to create tdx quote provider"},
{"SevSnpPass", "1234", "sev-snp", "failed to open sev-snp device"},
}
for _, op := range tests {
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/google/go-attestation v0.5.0
github.com/google/go-cmp v0.5.9
github.com/google/go-sev-guest v0.9.3
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33
github.com/google/go-tdx-guest v0.2.3-0.20231222042644-aeefcb0d0eb3
jkl73 marked this conversation as resolved.
Show resolved Hide resolved
github.com/google/go-tpm v0.9.0
github.com/google/logger v1.1.1
google.golang.org/protobuf v1.31.0
Expand All @@ -21,6 +21,6 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ github.com/google/go-sev-guest v0.9.3 h1:GOJ+EipURdeWFl/YYdgcCxyPeMgQUWlI056iFkB
github.com/google/go-sev-guest v0.9.3/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs=
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33 h1:lRlUusuieEuqljjihCXb+Mr73VNitOYPJYWXzJKtBWs=
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33/go.mod h1:84ut3oago/BqPXD4ppiGXdkZNW3WFPkcyAO4my2hXdY=
github.com/google/go-tdx-guest v0.2.3-0.20231222042644-aeefcb0d0eb3 h1:0EpD00z41G8EjJsHmO54BoUb3izF8/hgUzAzmlo6mCk=
github.com/google/go-tdx-guest v0.2.3-0.20231222042644-aeefcb0d0eb3/go.mod h1:Iut2YE9wI7DbuNY9hFcExiUH5oeayMkHNgh/JpByzHQ=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
Expand Down Expand Up @@ -765,6 +767,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -959,6 +962,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
14 changes: 9 additions & 5 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -193,21 +193,21 @@ cloud.google.com/go/webrisk v1.9.2/go.mod h1:pY9kfDgAqxUpDBOrG4w8deLfhvJmejKB0qd
cloud.google.com/go/websecurityscanner v1.6.2/go.mod h1:7YgjuU5tun7Eg2kpKgGnDuEOXWIrh8x8lWrJT4zfmas=
cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCwLM1P1tBRGHM=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
github.com/google/go-sev-guest v0.8.0/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand All @@ -218,6 +218,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
Expand All @@ -238,12 +239,15 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
Expand Down