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

Revert "[launcher] Merge upstream/tdx_rtmr (#513)" #516

Merged
merged 1 commit into from
Dec 17, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@ on:
- main
pull_request:


jobs:
build:
strategy:
matrix:
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
Expand Down Expand Up @@ -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:
Expand Down
83 changes: 79 additions & 4 deletions client/attest.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
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 @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down
5 changes: 3 additions & 2 deletions client/attest_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Expand Down
113 changes: 110 additions & 3 deletions client/attest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package client

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"math/big"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions cmd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
Loading
Loading