Skip to content

Commit

Permalink
merge tdx_rtmr to main (google#543)
Browse files Browse the repository at this point in the history
* [launcher] Add TDX/RTMR attestation in launcher (google#478)

Allow a TDX machine to create a TD quote and request a hardware
rooted attestation from the attestation verifier.

./launcher ci will now only run in linux.

Upgrade go-sev-guest.

Signed-off-by: Jiankun Lu <[email protected]>

* implement AttestationEvidence for TDX

* Revert "implement AttestationEvidence for TDX"

This reverts commit d150246.

* [launcher] Allow multiple Root of Trust in Agent (google#517)

Launcher Attestation Agent now can support multiple Root of Trust.
Extend operation will extend measurements to all RoTs.
Attest will generate quote/report using a technology specific
RoT (like TDX RTMR), and fallback to use TPM if no such technology
specific RoT can be found.

Signed-off-by: Jiankun Lu <[email protected]>

* Add ContainerImageSignature type to verifier client (google#521)

---------

Signed-off-by: Jiankun Lu <[email protected]>
Co-authored-by: Jessie Liu <[email protected]>
Co-authored-by: Jessie Liu <[email protected]>
  • Loading branch information
3 people authored Feb 26, 2025
1 parent d56b654 commit 775a60e
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 302 deletions.
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,21 @@ jobs:
- name: Install Windows packages
run: choco install openssl
if: runner.os == 'Windows'
- name: Build all modules
run: go build -v ./... ./cmd/... ./launcher/... ./verifier/...
- 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: 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' || runner.os == 'macOS') && matrix.architecture == 'x64'
- name: Test all modules
run: go test -v ./... ./cmd/... ./launcher/... ./verifier/... -skip='TestCacheConcurrentSetGet|TestHwAttestationPass|TestHardwareAttestationPass'
if: (runner.os == 'Linux') && matrix.architecture == 'x64'
- name: Test all modules except launcher
run: go test -v ./... ./cmd/... ./verifier/... -skip='TestCacheConcurrentSetGet|TestHwAttestationPass|TestHardwareAttestationPass'

lint:
strategy:
Expand Down
83 changes: 4 additions & 79 deletions client/attest.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
package client

import (
"crypto/x509"
"fmt"
"io"
"net/http"

sabi "github.com/google/go-sev-guest/abi"
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"
"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 {
Expand Down Expand Up @@ -49,6 +43,7 @@ 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
Expand All @@ -66,77 +61,6 @@ 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 {
Expand Down Expand Up @@ -383,12 +307,13 @@ 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 = k.getCertificateChain(opts.CertChainFetcher)
attestation.IntermediateCerts, err = internal.GetCertificateChain(k.cert, 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)
}
Expand Down
5 changes: 2 additions & 3 deletions client/attest_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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"
Expand All @@ -24,9 +25,7 @@ func TestNetworkFetchIssuingCertificate(t *testing.T) {
t.Fatalf("Error parsing AK Cert: %v", err)
}

key := &Key{cert: akCert}

certChain, err := key.getCertificateChain(externalClient)
certChain, err := internal.GetCertificateChain(akCert, externalClient)
if err != nil {
t.Error(err)
}
Expand Down
113 changes: 3 additions & 110 deletions client/attest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package client

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"math/big"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -23,112 +20,8 @@ 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 := getTestCert(t, nil, nil, nil)
testCA, caKey := test.GetTestCert(t, nil, nil, nil)

caServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusOK)
Expand All @@ -137,7 +30,7 @@ func TestKeyAttestSucceedsWithCertChainRetrieval(t *testing.T) {

defer caServer.Close()

leafCert, _ := getTestCert(t, []string{caServer.URL}, testCA, caKey)
leafCert, _ := test.GetTestCert(t, []string{caServer.URL}, testCA, caKey)

rwc := test.GetTPM(t)
defer CheckedClose(t, rwc)
Expand Down Expand Up @@ -173,7 +66,7 @@ func TestKeyAttestGetCertificateChainConditions(t *testing.T) {
t.Fatalf("Failed to generate test AK: %v", err)
}

akCert, _ := getTestCert(t, nil, nil, nil)
akCert, _ := test.GetTestCert(t, nil, nil, nil)

testcases := []struct {
name string
Expand Down
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJ
github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI=
github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
Expand Down
Loading

0 comments on commit 775a60e

Please sign in to comment.