diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e69d3feb..7853bfc32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ on: - main pull_request: - jobs: build: strategy: @@ -32,11 +31,6 @@ jobs: go-version: [1.21.x] # TODO: Get this working on windows-latest os: [ubuntu-latest] - # ci will build and run tests based on the following matrix - # mac-os linux windows - # x32 [no builds] . ./cmd ./verifier ./launcher [no builds] - # x64 . ./cmd ./verifier . ./cmd ./verifier ./launcher [no builds] - # arm64 . ./cmd ./verifier [no builds] [no builds] architecture: [x32, x64] include: - os: macos-latest @@ -77,21 +71,17 @@ jobs: - name: Install Windows packages run: choco install openssl if: runner.os == 'Windows' - - name: Build all modules except launcher - run: go build -v ./... ./cmd/... ./verifier/... - - name: Build launcher module - run: go build -v ./launcher/... - if: runner.os == 'Linux' + - name: Build all modules + run: go build -v ./... ./cmd/... ./launcher/... ./verifier/... - name: Run specific tests under root permission run: | GO_EXECUTABLE_PATH=$(which go) sudo $GO_EXECUTABLE_PATH test -v -run "TestFetchImageSignaturesDockerPublic" ./launcher - if: runner.os == 'Linux' - name: Run all tests in launcher to capture potential data race run: go test -v -race ./launcher/... - if: (runner.os == 'Linux') && matrix.architecture == 'x64' - - name: Test all modules except launcher - run: go test -v ./... ./cmd/... ./verifier/... -skip='TestCacheConcurrentSetGet|TestHwAttestationPass|TestHardwareAttestationPass' + if: (runner.os == 'Linux' || runner.os == 'macOS') && matrix.architecture == 'x64' + - name: Test all modules + run: go test -v ./... ./cmd/... ./launcher/... ./verifier/... -skip='TestCacheConcurrentSetGet|TestHwAttestationPass|TestHardwareAttestationPass' lint: strategy: diff --git a/client/attest.go b/client/attest.go index 8e7be72a3..3ea022917 100644 --- a/client/attest.go +++ b/client/attest.go @@ -1,7 +1,9 @@ package client import ( + "crypto/x509" "fmt" + "io" "net/http" sabi "github.com/google/go-sev-guest/abi" @@ -9,10 +11,14 @@ import ( 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" - "github.com/google/go-tpm-tools/internal" pb "github.com/google/go-tpm-tools/proto/attest" ) +const ( + maxIssuingCertificateURLs = 3 + maxCertChainLength = 4 +) + // TEEDevice is an interface to add an attestation report from a TEE technology's // attestation driver or quote provider. type TEEDevice interface { @@ -43,7 +49,6 @@ type AttestOpts struct { // Currently, we only support PCR replay for PCRs orthogonal to those in the // firmware event log, where PCRs 0-9 and 14 are often measured. If the two // logs overlap, server-side verification using this library may fail. - // Deprecated: Manually populate the pb.Attestation instead. CanonicalEventLog []byte // If non-nil, will be used to fetch the AK certificate chain for validation. // Key.Attest() will construct the certificate chain by making GET requests to @@ -61,6 +66,77 @@ type AttestOpts struct { TEENonce []byte } +// Given a certificate, iterates through its IssuingCertificateURLs and returns +// the certificate that signed it. If the certificate lacks an +// IssuingCertificateURL, return nil. If fetching the certificates fails or the +// cert chain is malformed, return an error. +func fetchIssuingCertificate(client *http.Client, cert *x509.Certificate) (*x509.Certificate, error) { + // Check if we should event attempt fetching. + if cert == nil || len(cert.IssuingCertificateURL) == 0 { + return nil, nil + } + // For each URL, fetch and parse the certificate, then verify whether it signed cert. + // If successful, return the parsed certificate. If any step in this process fails, try the next url. + // If all the URLs fail, return the last error we got. + // TODO(Issue #169): Return a multi-error here + var lastErr error + for i, url := range cert.IssuingCertificateURL { + // Limit the number of attempts. + if i >= maxIssuingCertificateURLs { + break + } + resp, err := client.Get(url) + if err != nil { + lastErr = fmt.Errorf("failed to retrieve certificate at %v: %w", url, err) + continue + } + + if resp.StatusCode != http.StatusOK { + lastErr = fmt.Errorf("certificate retrieval from %s returned non-OK status: %v", url, resp.StatusCode) + continue + } + certBytes, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + lastErr = fmt.Errorf("failed to read response body from %s: %w", url, err) + continue + } + + parsedCert, err := x509.ParseCertificate(certBytes) + if err != nil { + lastErr = fmt.Errorf("failed to parse response from %s into a certificate: %w", url, err) + continue + } + + // Check if the parsed certificate signed the current one. + if err = cert.CheckSignatureFrom(parsedCert); err != nil { + lastErr = fmt.Errorf("parent certificate from %s did not sign child: %w", url, err) + continue + } + return parsedCert, nil + } + return nil, lastErr +} + +// Constructs the certificate chain for the key's certificate. +// If an error is encountered in the process, return what has been constructed so far. +func (k *Key) getCertificateChain(client *http.Client) ([][]byte, error) { + var certs [][]byte + currentCert := k.cert + for len(certs) <= maxCertChainLength { + issuingCert, err := fetchIssuingCertificate(client, currentCert) + if err != nil { + return nil, err + } + if issuingCert == nil { + return certs, nil + } + certs = append(certs, issuingCert.Raw) + currentCert = issuingCert + } + return nil, fmt.Errorf("max certificate chain length (%v) exceeded", maxCertChainLength) +} + // SevSnpQuoteProvider encapsulates the SEV-SNP attestation device to add its attestation report // to a pb.Attestation. type SevSnpQuoteProvider struct { @@ -307,13 +383,12 @@ func (k *Key) Attest(opts AttestOpts) (*pb.Attestation, error) { // Attempt to construct certificate chain. fetchIssuingCertificate checks if // AK cert is present and contains intermediate cert URLs. if opts.CertChainFetcher != nil { - attestation.IntermediateCerts, err = internal.GetCertificateChain(k.cert, opts.CertChainFetcher) + attestation.IntermediateCerts, err = k.getCertificateChain(opts.CertChainFetcher) if err != nil { return nil, fmt.Errorf("fetching certificate chain: %w", err) } } - // TODO: issues/504 this should be outside of this function, not related to TPM attestation if err := getTEEAttestationReport(&attestation, opts); err != nil { return nil, fmt.Errorf("collecting TEE attestation report: %w", err) } diff --git a/client/attest_network_test.go b/client/attest_network_test.go index 2f225c2e7..83a461c19 100644 --- a/client/attest_network_test.go +++ b/client/attest_network_test.go @@ -5,7 +5,6 @@ import ( "net/http" "testing" - "github.com/google/go-tpm-tools/internal" "github.com/google/go-tpm-tools/internal/test" pb "github.com/google/go-tpm-tools/proto/attest" "google.golang.org/protobuf/proto" @@ -25,7 +24,9 @@ func TestNetworkFetchIssuingCertificate(t *testing.T) { t.Fatalf("Error parsing AK Cert: %v", err) } - certChain, err := internal.GetCertificateChain(akCert, externalClient) + key := &Key{cert: akCert} + + certChain, err := key.getCertificateChain(externalClient) if err != nil { t.Error(err) } diff --git a/client/attest_test.go b/client/attest_test.go index 5da979080..84b98ef2c 100644 --- a/client/attest_test.go +++ b/client/attest_test.go @@ -2,7 +2,10 @@ package client import ( "bytes" + "crypto/rand" + "crypto/rsa" "crypto/x509" + "math/big" "net/http" "net/http/httptest" "strings" @@ -20,8 +23,112 @@ import ( var localClient = http.DefaultClient +// Returns an x509 Certificate with the provided issuingURL and signed with the provided parent certificate and key. +// If parentCert and parentKey are nil, the certificate will be self-signed. +func getTestCert(t *testing.T, issuingURL []string, parentCert *x509.Certificate, parentKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey) { + t.Helper() + + certKey, _ := rsa.GenerateKey(rand.Reader, 2048) + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLenZero: true, + IssuingCertificateURL: issuingURL, + } + + if parentCert == nil && parentKey == nil { + parentCert = template + parentKey = certKey + } + + certBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, certKey.Public(), parentKey) + if err != nil { + t.Fatalf("Unable to create test certificate: %v", err) + } + + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + t.Fatalf("Unable to parse test certificate: %v", err) + } + + return cert, certKey +} + +func TestFetchIssuingCertificateSucceeds(t *testing.T) { + testCA, caKey := getTestCert(t, nil, nil, nil) + + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + rw.Write(testCA.Raw) + })) + defer ts.Close() + + leafCert, _ := getTestCert(t, []string{"invalid.URL", ts.URL}, testCA, caKey) + + cert, err := fetchIssuingCertificate(localClient, leafCert) + if err != nil || cert == nil { + t.Errorf("fetchIssuingCertificate() did not find valid intermediate cert: %v", err) + } +} + +func TestFetchIssuingCertificateReturnsErrorIfMalformedCertificateFound(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + rw.Write([]byte("these are some random bytes")) + })) + defer ts.Close() + + testCA, caKey := getTestCert(t, nil, nil, nil) + leafCert, _ := getTestCert(t, []string{ts.URL}, testCA, caKey) + + _, err := fetchIssuingCertificate(localClient, leafCert) + if err == nil { + t.Fatal("expected fetchIssuingCertificate to fail with malformed cert") + } +} + +func TestGetCertificateChainSucceeds(t *testing.T) { + // Create CA and corresponding server. + testCA, caKey := getTestCert(t, nil, nil, nil) + + caServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + rw.Write(testCA.Raw) + })) + + defer caServer.Close() + + // Create intermediate cert and corresponding server. + intermediateCert, intermediateKey := getTestCert(t, []string{caServer.URL}, testCA, caKey) + + intermediateServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(http.StatusOK) + rw.Write(intermediateCert.Raw) + })) + defer intermediateServer.Close() + + // Create leaf cert. + leafCert, _ := getTestCert(t, []string{intermediateServer.URL}, intermediateCert, intermediateKey) + + key := &Key{cert: leafCert} + + certChain, err := key.getCertificateChain(localClient) + if err != nil { + t.Fatal(err) + } + if len(certChain) != 2 { + t.Fatalf("getCertificateChain did not return the expected number of certificates: got %v, want 2", len(certChain)) + } +} + func TestKeyAttestSucceedsWithCertChainRetrieval(t *testing.T) { - testCA, caKey := test.GetTestCert(t, nil, nil, nil) + testCA, caKey := getTestCert(t, nil, nil, nil) caServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.WriteHeader(http.StatusOK) @@ -30,7 +137,7 @@ func TestKeyAttestSucceedsWithCertChainRetrieval(t *testing.T) { defer caServer.Close() - leafCert, _ := test.GetTestCert(t, []string{caServer.URL}, testCA, caKey) + leafCert, _ := getTestCert(t, []string{caServer.URL}, testCA, caKey) rwc := test.GetTPM(t) defer CheckedClose(t, rwc) @@ -66,7 +173,7 @@ func TestKeyAttestGetCertificateChainConditions(t *testing.T) { t.Fatalf("Failed to generate test AK: %v", err) } - akCert, _ := test.GetTestCert(t, nil, nil, nil) + akCert, _ := getTestCert(t, nil, nil, nil) testcases := []struct { name string diff --git a/cmd/go.sum b/cmd/go.sum index f17dac2eb..e081e63b1 100644 --- a/cmd/go.sum +++ b/cmd/go.sum @@ -348,6 +348,7 @@ github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwG github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958 h1:GfnkFZNr80qFGLR/EY75zwk8puz8+frGj4iwPwnJbSU= github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958/go.mod h1:8+UOtSaqVIZjJJ9DDmgRko3J/kNc6jI5KLHxoeao7cA= github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A= +github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= 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= @@ -585,6 +586,8 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= diff --git a/go.sum b/go.sum index db4234626..dda4f43aa 100644 --- a/go.sum +++ b/go.sum @@ -307,6 +307,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU= +github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba h1:05m5+kgZjxYUZrx3bZfkKHl6wkch+Khao6N21rFHInk= github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= @@ -317,6 +318,7 @@ github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwG github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958 h1:GfnkFZNr80qFGLR/EY75zwk8puz8+frGj4iwPwnJbSU= github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958/go.mod h1:8+UOtSaqVIZjJJ9DDmgRko3J/kNc6jI5KLHxoeao7cA= github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A= +github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= 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= @@ -865,6 +867,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1169,6 +1172,7 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1202,6 +1206,7 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/go.work.sum b/go.work.sum index 4f65dc826..4a0330c88 100644 --- a/go.work.sum +++ b/go.work.sum @@ -696,12 +696,12 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-configfs-tsm v0.3.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= github.com/google/go-eventlog v0.0.1/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ= 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/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= github.com/google/go-tpm-tools v0.4.2/go.mod h1:fGUDZu4tw3V4hUVuFHmiYgRd0c58/IXivn9v3Ea/ck4= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= @@ -888,6 +888,7 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -958,6 +959,7 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -969,6 +971,7 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -976,6 +979,7 @@ 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/internal/cert.go b/internal/cert.go deleted file mode 100644 index a4a2cbb7c..000000000 --- a/internal/cert.go +++ /dev/null @@ -1,84 +0,0 @@ -package internal - -import ( - "crypto/x509" - "fmt" - "io" - "net/http" -) - -const ( - maxIssuingCertificateURLs = 3 - maxCertChainLength = 4 -) - -// GetCertificateChain constructs the certificate chain for the key's certificate. -// If an error is encountered in the process, return what has been constructed so far. -func GetCertificateChain(cert *x509.Certificate, client *http.Client) ([][]byte, error) { - var certs [][]byte - currentCert := cert - for len(certs) <= maxCertChainLength { - issuingCert, err := fetchIssuingCertificate(client, currentCert) - if err != nil { - return nil, err - } - if issuingCert == nil { - return certs, nil - } - certs = append(certs, issuingCert.Raw) - currentCert = issuingCert - } - return nil, fmt.Errorf("max certificate chain length (%v) exceeded", maxCertChainLength) -} - -// Given a certificate, iterates through its IssuingCertificateURLs and returns -// the certificate that signed it. If the certificate lacks an -// IssuingCertificateURL, return nil. If fetching the certificates fails or the -// cert chain is malformed, return an error. -func fetchIssuingCertificate(client *http.Client, cert *x509.Certificate) (*x509.Certificate, error) { - // Check if we should event attempt fetching. - if cert == nil || len(cert.IssuingCertificateURL) == 0 { - return nil, nil - } - // For each URL, fetch and parse the certificate, then verify whether it signed cert. - // If successful, return the parsed certificate. If any step in this process fails, try the next url. - // If all the URLs fail, return the last error we got. - // TODO(Issue #169): Return a multi-error here - var lastErr error - for i, url := range cert.IssuingCertificateURL { - // Limit the number of attempts. - if i >= maxIssuingCertificateURLs { - break - } - resp, err := client.Get(url) - if err != nil { - lastErr = fmt.Errorf("failed to retrieve certificate at %v: %w", url, err) - continue - } - - if resp.StatusCode != http.StatusOK { - lastErr = fmt.Errorf("certificate retrieval from %s returned non-OK status: %v", url, resp.StatusCode) - continue - } - certBytes, err := io.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - lastErr = fmt.Errorf("failed to read response body from %s: %w", url, err) - continue - } - - parsedCert, err := x509.ParseCertificate(certBytes) - if err != nil { - lastErr = fmt.Errorf("failed to parse response from %s into a certificate: %w", url, err) - continue - } - - // Check if the parsed certificate signed the current one. - if err = cert.CheckSignatureFrom(parsedCert); err != nil { - lastErr = fmt.Errorf("parent certificate from %s did not sign child: %w", url, err) - continue - } - return parsedCert, nil - } - return nil, lastErr -} diff --git a/internal/cert_test.go b/internal/cert_test.go deleted file mode 100644 index 9a502b36e..000000000 --- a/internal/cert_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package internal - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/google/go-tpm-tools/internal/test" -) - -var localClient = http.DefaultClient - -func TestFetchIssuingCertificateSucceeds(t *testing.T) { - testCA, caKey := test.GetTestCert(t, nil, nil, nil) - - ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write(testCA.Raw) - })) - defer ts.Close() - - leafCert, _ := test.GetTestCert(t, []string{"invalid.URL", ts.URL}, testCA, caKey) - - cert, err := fetchIssuingCertificate(localClient, leafCert) - if err != nil || cert == nil { - t.Errorf("fetchIssuingCertificate() did not find valid intermediate cert: %v", err) - } -} - -func TestFetchIssuingCertificateReturnsErrorIfMalformedCertificateFound(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write([]byte("these are some random bytes")) - })) - defer ts.Close() - - testCA, caKey := test.GetTestCert(t, nil, nil, nil) - leafCert, _ := test.GetTestCert(t, []string{ts.URL}, testCA, caKey) - - _, err := fetchIssuingCertificate(localClient, leafCert) - if err == nil { - t.Fatal("expected fetchIssuingCertificate to fail with malformed cert") - } -} - -func TestGetCertificateChainSucceeds(t *testing.T) { - // Create CA and corresponding server. - testCA, caKey := test.GetTestCert(t, nil, nil, nil) - - caServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write(testCA.Raw) - })) - - defer caServer.Close() - - // Create intermediate cert and corresponding server. - intermediateCert, intermediateKey := test.GetTestCert(t, []string{caServer.URL}, testCA, caKey) - - intermediateServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { - rw.WriteHeader(http.StatusOK) - rw.Write(intermediateCert.Raw) - })) - defer intermediateServer.Close() - - // Create leaf cert. - leafCert, _ := test.GetTestCert(t, []string{intermediateServer.URL}, intermediateCert, intermediateKey) - - certChain, err := GetCertificateChain(leafCert, localClient) - if err != nil { - t.Fatal(err) - } - if len(certChain) != 2 { - t.Fatalf("GetCertificateChain did not return the expected number of certificates: got %v, want 2", len(certChain)) - } -} diff --git a/internal/test/test_cert.go b/internal/test/test_cert.go deleted file mode 100644 index 0f45a277a..000000000 --- a/internal/test/test_cert.go +++ /dev/null @@ -1,47 +0,0 @@ -package test - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "math/big" - "testing" - "time" -) - -// GetTestCert returns an x509 Certificate with the provided issuingURL and signed with the provided parent certificate and key. -// If parentCert and parentKey are nil, the certificate will be self-signed. -func GetTestCert(t *testing.T, issuingURL []string, parentCert *x509.Certificate, parentKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey) { - t.Helper() - - certKey, _ := rsa.GenerateKey(rand.Reader, 2048) - - template := &x509.Certificate{ - SerialNumber: big.NewInt(1), - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - IsCA: true, - MaxPathLenZero: true, - IssuingCertificateURL: issuingURL, - } - - if parentCert == nil && parentKey == nil { - parentCert = template - parentKey = certKey - } - - certBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, certKey.Public(), parentKey) - if err != nil { - t.Fatalf("Unable to create test certificate: %v", err) - } - - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - t.Fatalf("Unable to parse test certificate: %v", err) - } - - return cert, certKey -} diff --git a/launcher/agent/agent.go b/launcher/agent/agent.go index 23c9b196b..9edc11d8d 100644 --- a/launcher/agent/agent.go +++ b/launcher/agent/agent.go @@ -12,20 +12,12 @@ import ( "fmt" "io" "net/http" - "os" "sync" "time" "github.com/cenkalti/backoff/v4" - "github.com/google/go-configfs-tsm/configfs/configfsi" - - "github.com/google/go-configfs-tsm/configfs/linuxtsm" - tg "github.com/google/go-tdx-guest/client" - tlabi "github.com/google/go-tdx-guest/client/linuxabi" - "github.com/google/go-tpm-tools/cel" "github.com/google/go-tpm-tools/client" - "github.com/google/go-tpm-tools/internal" "github.com/google/go-tpm-tools/launcher/internal/logging" "github.com/google/go-tpm-tools/launcher/internal/signaturediscovery" "github.com/google/go-tpm-tools/launcher/spec" @@ -49,13 +41,6 @@ type AttestationAgent interface { Close() error } -type attestRoot interface { - // Extend measures the cel content into a measurement register and appends to the CEL. - Extend(cel.Content, *cel.CEL) error - // Attest fetches a technology-specific quote from the root of trust. - Attest(nonce []byte) (any, error) -} - // AttestAgentOpts contains user generated options when calling the // VerifyAttestation API type AttestAgentOpts struct { @@ -65,25 +50,20 @@ type AttestAgentOpts struct { } type agent struct { - ar attestRoot - cosCel cel.CEL + tpm io.ReadWriteCloser + tpmMu sync.Mutex fetchedAK *client.Key client verifier.Client principalFetcher principalIDTokenFetcher sigsFetcher signaturediscovery.Fetcher + cosCel cel.CEL launchSpec spec.LaunchSpec logger logging.Logger sigsCache *sigsCache } // CreateAttestationAgent returns an agent capable of performing remote -// attestation using the machine's (v)TPM or TDX quote. -// -// For now, "tpm" and "akFetcher" is still needed even on a TDX quote, -// because we need to extract GCE instance info from the TPM's AK. -// TODO(jiankun): remove "tpm" and "akFetcher" from TDX attestAgent once -// it no longer needs TPM AK for GCE info. -// +// attestation using the machine's (v)TPM to GCE's Attestation Service. // - tpm is a handle to the TPM on the instance // - akFetcher is a func to fetch an attestation key: see go-tpm-tools/client. // - principalFetcher is a func to fetch GCE principal tokens for a given audience. @@ -95,8 +75,8 @@ func CreateAttestationAgent(tpm io.ReadWriteCloser, akFetcher util.TpmKeyFetcher if err != nil { return nil, fmt.Errorf("failed to create an Attestation Agent: %w", err) } - - attestAgent := &agent{ + return &agent{ + tpm: tpm, client: verifierClient, fetchedAK: ak, principalFetcher: principalFetcher, @@ -104,37 +84,7 @@ func CreateAttestationAgent(tpm io.ReadWriteCloser, akFetcher util.TpmKeyFetcher launchSpec: launchSpec, logger: logger, sigsCache: &sigsCache{}, - } - - qp, err := tg.GetQuoteProvider() - if err != nil { - return nil, fmt.Errorf("GetQuoteProvider should always return nil error, but got %v", err) - } - - if err := qp.IsSupported(); err != nil { - // check if the quote provider is backed by TDX - logger.Info(fmt.Sprintf("Cannot create the TDX quote provider (this is expected on a non-TDX machine): %v", err)) - logger.Info("Using TPM PCRs for measurement") - // if cannot get a TDX Quote Provider, by default using TPM as the attest root - attestAgent.ar = &tpmAttestRoot{ - fetchedAK: ak, - tpm: tpm, - } - } else { - logger.Info("Using TDX RTMRs for measurement") - // try to create tsm client for tdx rtmr - tsm, err := linuxtsm.MakeClient() - if err != nil { - return nil, fmt.Errorf("failed to create TSM for TDX: %v", err) - } - - attestAgent.ar = &tdxAttestRoot{ - qp: qp, - tsmClient: tsm, - } - } - - return attestAgent, nil + }, nil } // Close cleans up the agent @@ -146,7 +96,9 @@ func (a *agent) Close() error { // MeasureEvent takes in a cel.Content and appends it to the CEL eventlog // under the attestation agent. func (a *agent) MeasureEvent(event cel.Content) error { - return a.ar.Extend(event, &a.cosCel) + a.tpmMu.Lock() + defer a.tpmMu.Unlock() + return a.cosCel.AppendEvent(a.tpm, cel.CosEventPCR, defaultCELHashAlgo, event) } // Attest fetches the nonce and connection ID from the Attestation Service, @@ -163,9 +115,20 @@ func (a *agent) Attest(ctx context.Context, opts AttestAgentOpts) ([]byte, error return nil, fmt.Errorf("failed to get principal tokens: %w", err) } + var buf bytes.Buffer + if err := a.cosCel.EncodeCEL(&buf); err != nil { + return nil, err + } + + attestation, err := a.attest(challenge.Nonce, buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to attest: %v", err) + } + req := verifier.VerifyAttestationRequest{ Challenge: challenge, GcpCredentials: principalTokens, + Attestation: attestation, TokenOptions: verifier.TokenOptions{ CustomAudience: opts.Aud, CustomNonce: opts.Nonces, @@ -173,38 +136,6 @@ func (a *agent) Attest(ctx context.Context, opts AttestAgentOpts) ([]byte, error }, } - attResult, err := a.ar.Attest(challenge.Nonce) - if err != nil { - return nil, fmt.Errorf("failed to attest: %v", err) - } - - var cosCel bytes.Buffer - if err := a.cosCel.EncodeCEL(&cosCel); err != nil { - return nil, err - } - - switch v := attResult.(type) { - case *pb.Attestation: - a.logger.Info("Attest using TPM quote") - - v.CanonicalEventLog = cosCel.Bytes() - req.Attestation = v - case *verifier.TDCCELAttestation: - a.logger.Info("Attest using TDX quote (Public Preview)") - - certChain, err := internal.GetCertificateChain(a.fetchedAK.Cert(), http.DefaultClient) - if err != nil { - return nil, fmt.Errorf("failed when fetching certificate chain: %w", err) - } - - v.CanonicalEventLog = cosCel.Bytes() - v.IntermediateCerts = certChain - v.AkCert = a.fetchedAK.CertDERBytes() - req.TDCCELAttestation = v - default: - return nil, fmt.Errorf("received an unsupported attestation type! %v", v) - } - signatures := a.sigsCache.get() if len(signatures) > 0 { req.ContainerImageSignatures = signatures @@ -221,62 +152,10 @@ func (a *agent) Attest(ctx context.Context, opts AttestAgentOpts) ([]byte, error return resp.ClaimsToken, nil } -type tpmAttestRoot struct { - tpmMu sync.Mutex - fetchedAK *client.Key - tpm io.ReadWriteCloser -} - -func (t *tpmAttestRoot) Extend(c cel.Content, l *cel.CEL) error { - return l.AppendEventPCR(t.tpm, cel.CosEventPCR, defaultCELHashAlgo, c) -} - -func (t *tpmAttestRoot) Attest(nonce []byte) (any, error) { - t.tpmMu.Lock() - defer t.tpmMu.Unlock() - - return t.fetchedAK.Attest(client.AttestOpts{ - Nonce: nonce, - CertChainFetcher: http.DefaultClient, - }) -} - -type tdxAttestRoot struct { - tdxMu sync.Mutex - qp *tg.LinuxConfigFsQuoteProvider - tsmClient configfsi.Client -} - -func (t *tdxAttestRoot) Extend(c cel.Content, l *cel.CEL) error { - return l.AppendEventRTMR(t.tsmClient, cel.CosRTMR, c) -} - -func (t *tdxAttestRoot) Attest(nonce []byte) (any, error) { - t.tdxMu.Lock() - defer t.tdxMu.Unlock() - - var tdxNonce [tlabi.TdReportDataSize]byte - copy(tdxNonce[:], nonce) - - rawQuote, err := tg.GetRawQuote(t.qp, tdxNonce) - if err != nil { - return nil, err - } - - ccelData, err := os.ReadFile("/sys/firmware/acpi/tables/data/CCEL") - if err != nil { - return nil, err - } - ccelTable, err := os.ReadFile("/sys/firmware/acpi/tables/CCEL") - if err != nil { - return nil, err - } - - return &verifier.TDCCELAttestation{ - CcelAcpiTable: ccelTable, - CcelData: ccelData, - TdQuote: rawQuote, - }, nil +func (a *agent) attest(nonce []byte, cel []byte) (*pb.Attestation, error) { + a.tpmMu.Lock() + defer a.tpmMu.Unlock() + return a.fetchedAK.Attest(client.AttestOpts{Nonce: nonce, CanonicalEventLog: cel, CertChainFetcher: http.DefaultClient}) } // Refresh refreshes the internal state of the attestation agent. diff --git a/launcher/cloudbuild.yaml b/launcher/cloudbuild.yaml index 7c56dbb43..ed9b2378d 100644 --- a/launcher/cloudbuild.yaml +++ b/launcher/cloudbuild.yaml @@ -1,10 +1,9 @@ substitutions: '_BASE_IMAGE': '' # an empty base image means the build will use the latest image in '_BASE_IMAGE_FAMILY' - '_BASE_IMAGE_FAMILY': 'cos-tdx-113-lts' # base image family - '_BASE_IMAGE_PROJECT': 'confidential-vm-images' - '_OUTPUT_IMAGE_PREFIX': 'confidential-space' - '_OUTPUT_IMAGE_SUFFIX': '' # should defined by the caller - '_OUTPUT_IMAGE_FAMILY': '' # should defined by the caller + '_BASE_IMAGE_FAMILY': 'cos-113-lts' # base image family + '_OUTPUT_IMAGE_PREFIX': 'confidential-space' + '_OUTPUT_IMAGE_SUFFIX': '' + '_OUTPUT_IMAGE_FAMILY': '' '_BUCKET_NAME': '${PROJECT_ID}_cloudbuild' steps: @@ -12,9 +11,8 @@ steps: - name: 'gcr.io/cloud-builders/gcloud' id: BaseImageIdent env: - - 'BASE_IMAGE=${_BASE_IMAGE}' - - 'BASE_IMAGE_FAMILY=${_BASE_IMAGE_FAMILY}' - - 'BASE_IMAGE_PROJECT=${_BASE_IMAGE_PROJECT}' + - 'BASE_IMAGE=$_BASE_IMAGE' + - 'BASE_IMAGE_FAMILY=$_BASE_IMAGE_FAMILY' script: | #!/usr/bin/env bash @@ -23,7 +21,7 @@ steps: if [ -z ${base_image} ] then echo "getting the latest COS image" - base_image=$(gcloud compute images describe-from-family ${BASE_IMAGE_FAMILY} --project ${BASE_IMAGE_PROJECT} | grep name | cut -d ' ' -f 2) + base_image=$(gcloud compute images describe-from-family ${BASE_IMAGE_FAMILY} --project cos-cloud | grep name | cut -d ' ' -f 2) fi echo ${base_image} > /workspace/base_image.txt @@ -32,12 +30,11 @@ steps: id: DebugImageBuild waitFor: ['BaseImageIdent'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' - - 'OUTPUT_IMAGE_FAMILY=${_OUTPUT_IMAGE_FAMILY}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' + - 'OUTPUT_IMAGE_FAMILY=$_OUTPUT_IMAGE_FAMILY' - 'BUCKET_NAME=$_BUCKET_NAME' - 'SHORT_SHA=${SHORT_SHA}' - - 'BASE_IMAGE_PROJECT=${_BASE_IMAGE_PROJECT}' script: | #!/usr/bin/env bash set -exuo pipefail @@ -45,25 +42,18 @@ steps: base_image=$(cat /workspace/base_image.txt) echo "building the debug image: ${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX} with the base image: ${base_image}" gcloud builds submit --config=launcher/image/cloudbuild.yaml --region us-west1 \ - --substitutions _SHORT_SHA=${SHORT_SHA},_BASE_IMAGE=${base_image},\ - _OUTPUT_IMAGE_FAMILY=${OUTPUT_IMAGE_FAMILY}-debug,\ - _OUTPUT_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX},\ - _IMAGE_ENV=debug,\ - _CS_LICENSE=projects/confidential-space-images/global/licenses/confidential-space-debug,\ - _BUCKET_NAME=${BUCKET_NAME},\ - _BASE_IMAGE_PROJECT=${BASE_IMAGE_PROJECT} + --substitutions _SHORT_SHA=${SHORT_SHA},_BASE_IMAGE=${base_image},_OUTPUT_IMAGE_FAMILY=${OUTPUT_IMAGE_FAMILY}-debug,_OUTPUT_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-debug-${OUTPUT_IMAGE_SUFFIX},_IMAGE_ENV=debug,_CS_LICENSE=projects/confidential-space-images/global/licenses/confidential-space-debug,_BUCKET_NAME=${BUCKET_NAME} exit - name: 'gcr.io/cloud-builders/gcloud' id: HardenedImageBuild waitFor: ['BaseImageIdent'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' - - 'OUTPUT_IMAGE_FAMILY=${_OUTPUT_IMAGE_FAMILY}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' + - 'OUTPUT_IMAGE_FAMILY=$_OUTPUT_IMAGE_FAMILY' - 'BUCKET_NAME=$_BUCKET_NAME' - 'SHORT_SHA=${SHORT_SHA}' - - 'BASE_IMAGE_PROJECT=${_BASE_IMAGE_PROJECT}' script: | #!/usr/bin/env bash set -exuo pipefail @@ -71,21 +61,15 @@ steps: base_image=$(cat /workspace/base_image.txt) echo "building the hardened image: ${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX} with the base image: ${base_image}" gcloud builds submit --config=launcher/image/cloudbuild.yaml --region us-west1 \ - --substitutions _SHORT_SHA=${SHORT_SHA},_BASE_IMAGE=${base_image},\ - _OUTPUT_IMAGE_FAMILY=${OUTPUT_IMAGE_FAMILY},\ - _OUTPUT_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX},\ - _IMAGE_ENV=hardened,\ - _CS_LICENSE=projects/confidential-space-images/global/licenses/confidential-space,\ - _BUCKET_NAME=${BUCKET_NAME},\ - _BASE_IMAGE_PROJECT=${BASE_IMAGE_PROJECT} + --substitutions _SHORT_SHA=${SHORT_SHA},_BASE_IMAGE=${base_image},_OUTPUT_IMAGE_FAMILY=${OUTPUT_IMAGE_FAMILY},_OUTPUT_IMAGE_NAME=${OUTPUT_IMAGE_PREFIX}-hardened-${OUTPUT_IMAGE_SUFFIX},_IMAGE_ENV=hardened,_CS_LICENSE=projects/confidential-space-images/global/licenses/confidential-space,_BUCKET_NAME=${BUCKET_NAME} exit - name: 'gcr.io/cloud-builders/gcloud' id: ExperimentsTests waitFor: ['DebugImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -100,8 +84,8 @@ steps: id: HttpServerTests waitFor: ['DebugImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -116,8 +100,8 @@ steps: id: DebugImageTests waitFor: ['DebugImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -132,8 +116,8 @@ steps: id: HardenedImageTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -147,8 +131,8 @@ steps: id: LaunchPolicyTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -163,8 +147,8 @@ steps: id: HardenedNetworkIngressTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -177,8 +161,8 @@ steps: id: DebugNetworkIngressTests waitFor: ['DebugImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -191,8 +175,8 @@ steps: id: LogRedirectionTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -206,8 +190,8 @@ steps: id: HardenedDiscoverContainerSignatureTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -220,8 +204,8 @@ steps: id: DebugDiscoverContainerSignatureTests waitFor: ['DebugImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -234,8 +218,8 @@ steps: id: MemoryMonitoringTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -248,8 +232,8 @@ steps: id: ODAWithSignedContainerTest waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash @@ -262,8 +246,8 @@ steps: id: MountTests waitFor: ['HardenedImageBuild'] env: - - 'OUTPUT_IMAGE_PREFIX=${_OUTPUT_IMAGE_PREFIX}' - - 'OUTPUT_IMAGE_SUFFIX=${_OUTPUT_IMAGE_SUFFIX}' + - 'OUTPUT_IMAGE_PREFIX=$_OUTPUT_IMAGE_PREFIX' + - 'OUTPUT_IMAGE_SUFFIX=$_OUTPUT_IMAGE_SUFFIX' - 'PROJECT_ID=$PROJECT_ID' script: | #!/usr/bin/env bash diff --git a/launcher/container_runner.go b/launcher/container_runner.go index c05df038e..15ebc5fd1 100644 --- a/launcher/container_runner.go +++ b/launcher/container_runner.go @@ -428,12 +428,7 @@ func (r *ContainerRunner) refreshToken(ctx context.Context) (time.Duration, erro return 0, fmt.Errorf("failed to parse token: %w", err) } - claimsString, err := json.MarshalIndent(mapClaims, "", " ") - if err != nil { - return 0, fmt.Errorf("failed to format claims: %w", err) - } - - r.logger.Info("Successfully refreshed attestation token", "claims", string(claimsString)) + r.logger.Info("successfully refreshed attestation token", "token", mapClaims) return getNextRefreshFromExpiration(time.Until(claims.ExpiresAt.Time), rand.Float64()), nil } diff --git a/launcher/go.mod b/launcher/go.mod index b05c65be7..4f08edd5c 100644 --- a/launcher/go.mod +++ b/launcher/go.mod @@ -10,8 +10,6 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/google/go-cmp v0.6.0 - github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc - github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 github.com/google/go-tpm v0.9.0 github.com/google/go-tpm-tools v0.4.4 github.com/google/go-tpm-tools/verifier v0.4.4 @@ -51,8 +49,10 @@ require ( github.com/google/certificate-transparency-go v1.1.2 // indirect github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 // indirect github.com/google/go-attestation v0.5.1 // indirect + github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba // indirect github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958 // indirect + github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/logger v1.1.1 // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/launcher/go.sum b/launcher/go.sum index 2d68dc512..d13c261b5 100644 --- a/launcher/go.sum +++ b/launcher/go.sum @@ -632,6 +632,8 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= diff --git a/launcher/image/cloudbuild.yaml b/launcher/image/cloudbuild.yaml index 8a82b5383..1ac42a381 100644 --- a/launcher/image/cloudbuild.yaml +++ b/launcher/image/cloudbuild.yaml @@ -2,9 +2,9 @@ substitutions: '_BASE_IMAGE': '' '_OUTPUT_IMAGE_NAME': '' '_OUTPUT_IMAGE_FAMILY': '' - '_BASE_IMAGE_PROJECT': '' + '_BASE_IMAGE_PROJECT': 'cos-cloud' '_IMAGE_ENV': '' - '_BUCKET_NAME': '' + '_BUCKET_NAME': '${PROJECT_ID}_cloudbuild' '_CS_LICENSE': '' '_SHORT_SHA': '' diff --git a/verifier/client.go b/verifier/client.go index 7b8783391..0af28ae1a 100644 --- a/verifier/client.go +++ b/verifier/client.go @@ -37,24 +37,11 @@ type TokenOptions struct { // Challenge from CreateChallenge, optional GcpCredentials linked to the // attestation, the Attestation generated from the TPM, and optional container image signatures associated with the workload. type VerifyAttestationRequest struct { - Challenge *Challenge - GcpCredentials [][]byte - // Attestation is for TPM attestation + Challenge *Challenge + GcpCredentials [][]byte Attestation *attestpb.Attestation ContainerImageSignatures []oci.Signature TokenOptions TokenOptions - // TDCCELAttestation is for TDX CCEL RTMR attestation - TDCCELAttestation *TDCCELAttestation -} - -type TDCCELAttestation struct { - CcelAcpiTable []byte - CcelData []byte - CanonicalEventLog []byte - TdQuote []byte - // still needs following two for GCE info - AkCert []byte - IntermediateCerts [][]byte } // VerifyAttestationResponse is the response from a successful diff --git a/verifier/go.mod b/verifier/go.mod index 89d7080fa..0744c56b8 100644 --- a/verifier/go.mod +++ b/verifier/go.mod @@ -13,9 +13,9 @@ require ( github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 github.com/google/go-tpm v0.9.0 github.com/google/go-tpm-tools v0.4.4 - github.com/google/uuid v1.6.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 + github.com/pborman/uuid v1.2.1 go.uber.org/multierr v1.11.0 golang.org/x/net v0.27.0 golang.org/x/oauth2 v0.21.0 @@ -41,6 +41,7 @@ require ( github.com/google/go-tspi v0.3.0 // indirect github.com/google/logger v1.1.1 // indirect github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/verifier/go.sum b/verifier/go.sum index 84ffbb1e0..b9143c34b 100644 --- a/verifier/go.sum +++ b/verifier/go.sum @@ -335,6 +335,7 @@ github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwG github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958 h1:GfnkFZNr80qFGLR/EY75zwk8puz8+frGj4iwPwnJbSU= github.com/google/go-sev-guest v0.11.2-0.20241009005433-de2ac900e958/go.mod h1:8+UOtSaqVIZjJJ9DDmgRko3J/kNc6jI5KLHxoeao7cA= github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A= +github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= 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= @@ -576,6 +577,8 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= diff --git a/verifier/rest/rest.go b/verifier/rest/rest.go index f92665bb4..2a32d37bb 100644 --- a/verifier/rest/rest.go +++ b/verifier/rest/rest.go @@ -109,19 +109,11 @@ func (c *restClient) CreateChallenge(ctx context.Context) (*verifier.Challenge, // VerifyAttestation implements verifier.Client func (c *restClient) VerifyAttestation(ctx context.Context, request verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) { - if request.Challenge == nil { + if request.Challenge == nil || request.Attestation == nil { return nil, fmt.Errorf("nil value provided in challenge") } - - if request.Attestation == nil && request.TDCCELAttestation == nil { - return nil, fmt.Errorf("neither TPM nor TDX attestation is present") - } else if request.Attestation != nil && request.TDCCELAttestation != nil { - return nil, fmt.Errorf("both TPM and TDX attestation are present, only one should be present in the attestation request") - } - req := convertRequestToREST(request) req.Challenge = request.Challenge.Name - response, err := c.v1Client.VerifyAttestation(ctx, req) if err != nil { return nil, fmt.Errorf("calling v1.VerifyAttestation in %v: %w", c.location.LocationId, err) @@ -148,16 +140,24 @@ func convertRequestToREST(request verifier.VerifyAttestationRequest) *confidenti idTokens[i] = string(token) } - var tokenType confidentialcomputingpb.TokenType - switch request.TokenOptions.TokenType { - case "OIDC": - tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_OIDC - case "PKI": - tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_PKI - case "LIMITED_AWS": - tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_LIMITED_AWS - default: - tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_UNSPECIFIED + quotes := make([]*confidentialcomputingpb.TpmAttestation_Quote, len(request.Attestation.GetQuotes())) + for i, quote := range request.Attestation.GetQuotes() { + pcrVals := map[int32][]byte{} + for idx, val := range quote.GetPcrs().GetPcrs() { + pcrVals[int32(idx)] = val + } + + quotes[i] = &confidentialcomputingpb.TpmAttestation_Quote{ + RawQuote: quote.GetQuote(), + RawSignature: quote.GetRawSig(), + HashAlgo: int32(quote.GetPcrs().GetHash()), + PcrValues: pcrVals, + } + } + + certs := make([][]byte, len(request.Attestation.GetIntermediateCerts())) + for i, cert := range request.Attestation.GetIntermediateCerts() { + certs[i] = cert } signatures := make([]*confidentialcomputingpb.ContainerImageSignature, len(request.ContainerImageSignatures)) @@ -170,10 +170,29 @@ func convertRequestToREST(request verifier.VerifyAttestationRequest) *confidenti signatures[i] = signature } + var tokenType confidentialcomputingpb.TokenType + switch request.TokenOptions.TokenType { + case "OIDC": + tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_OIDC + case "PKI": + tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_PKI + case "LIMITED_AWS": + tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_LIMITED_AWS + default: + tokenType = confidentialcomputingpb.TokenType_TOKEN_TYPE_UNSPECIFIED + } + verifyReq := &confidentialcomputingpb.VerifyAttestationRequest{ GcpCredentials: &confidentialcomputingpb.GcpCredentials{ ServiceAccountIdTokens: idTokens, }, + TpmAttestation: &confidentialcomputingpb.TpmAttestation{ + Quotes: quotes, + TcgEventLog: request.Attestation.GetEventLog(), + CanonicalEventLog: request.Attestation.GetCanonicalEventLog(), + AkCert: request.Attestation.GetAkCert(), + CertChain: certs, + }, ConfidentialSpaceInfo: &confidentialcomputingpb.ConfidentialSpaceInfo{ SignedEntities: []*confidentialcomputingpb.SignedEntity{{ContainerImageSignatures: signatures}}, }, @@ -184,67 +203,20 @@ func convertRequestToREST(request verifier.VerifyAttestationRequest) *confidenti }, } - if request.Attestation != nil { - // TPM attestation route - quotes := make([]*confidentialcomputingpb.TpmAttestation_Quote, len(request.Attestation.GetQuotes())) - for i, quote := range request.Attestation.GetQuotes() { - pcrVals := map[int32][]byte{} - for idx, val := range quote.GetPcrs().GetPcrs() { - pcrVals[int32(idx)] = val - } - - quotes[i] = &confidentialcomputingpb.TpmAttestation_Quote{ - RawQuote: quote.GetQuote(), - RawSignature: quote.GetRawSig(), - HashAlgo: int32(quote.GetPcrs().GetHash()), - PcrValues: pcrVals, - } - } - - certs := make([][]byte, len(request.Attestation.GetIntermediateCerts())) - for i, cert := range request.Attestation.GetIntermediateCerts() { - certs[i] = cert - } - - verifyReq.TpmAttestation = &confidentialcomputingpb.TpmAttestation{ - Quotes: quotes, - TcgEventLog: request.Attestation.GetEventLog(), - CanonicalEventLog: request.Attestation.GetCanonicalEventLog(), - AkCert: request.Attestation.GetAkCert(), - CertChain: certs, - } - - if request.Attestation.GetSevSnpAttestation() != nil { - sevsnp, err := convertSEVSNPProtoToREST(request.Attestation.GetSevSnpAttestation()) - if err != nil { - log.Fatalf("Failed to convert SEVSNP proto to API proto: %v", err) - } - verifyReq.TeeAttestation = sevsnp - } - - if request.Attestation.GetTdxAttestation() != nil { - tdx, err := convertTDXProtoToREST(request.Attestation.GetTdxAttestation()) - if err != nil { - log.Fatalf("Failed to convert TD quote proto to API proto: %v", err) - } - verifyReq.TeeAttestation = tdx - } - } else if request.TDCCELAttestation != nil { - // TDX attestation route - // still need AK for GCE info! - verifyReq.TpmAttestation = &confidentialcomputingpb.TpmAttestation{ - AkCert: request.TDCCELAttestation.AkCert, - CertChain: request.TDCCELAttestation.IntermediateCerts, + if request.Attestation.GetSevSnpAttestation() != nil { + sevsnp, err := convertSEVSNPProtoToREST(request.Attestation.GetSevSnpAttestation()) + if err != nil { + log.Fatalf("Failed to convert SEVSNP proto to API proto: %v", err) } + verifyReq.TeeAttestation = sevsnp + } - verifyReq.TeeAttestation = &confidentialcomputingpb.VerifyAttestationRequest_TdCcel{ - TdCcel: &confidentialcomputingpb.TdxCcelAttestation{ - TdQuote: request.TDCCELAttestation.TdQuote, - CcelAcpiTable: request.TDCCELAttestation.CcelAcpiTable, - CcelData: request.TDCCELAttestation.CcelData, - CanonicalEventLog: request.TDCCELAttestation.CanonicalEventLog, - }, + if request.Attestation.GetTdxAttestation() != nil { + tdx, err := convertTDXProtoToREST(request.Attestation.GetTdxAttestation()) + if err != nil { + log.Fatalf("Failed to convert TD quote proto to API proto: %v", err) } + verifyReq.TeeAttestation = tdx } return verifyReq