From c77b94e52d729d9b2a2461094563cfeadb66410e Mon Sep 17 00:00:00 2001 From: Josh Krstic Date: Tue, 26 Jul 2022 16:40:14 -0700 Subject: [PATCH] Add fake client for launcher/verifier/client interface Fix test with new Fake Client --- launcher/agent/agent.go | 2 +- launcher/agent/agent_test.go | 64 +++++----- launcher/verifier/fake/fakeverifier.go | 66 +++++++++++ .../service/fake_attestationverifier.go | 81 ------------- .../service/fake_attestationverifier_test.go | 111 ------------------ .../service/fake_tokens/fake_rsa_token.txt | 1 - 6 files changed, 99 insertions(+), 226 deletions(-) create mode 100644 launcher/verifier/fake/fakeverifier.go delete mode 100644 launcher/verifier/grpcclient/service/fake_attestationverifier.go delete mode 100644 launcher/verifier/grpcclient/service/fake_attestationverifier_test.go delete mode 100644 launcher/verifier/grpcclient/service/fake_tokens/fake_rsa_token.txt diff --git a/launcher/agent/agent.go b/launcher/agent/agent.go index 7b799fb0e..fa8b9a977 100644 --- a/launcher/agent/agent.go +++ b/launcher/agent/agent.go @@ -65,7 +65,7 @@ func (a *agent) MeasureEvent(event cel.Content) error { // Attest fetches the nonce and connection ID from the Attestation Service, // creates an attestation message, and returns the resultant -// principalIDTokens are Metadata Server-generated ID tokens for the instance. +// principalIDTokens and Metadata Server-generated ID tokens for the instance. func (a *agent) Attest(ctx context.Context) ([]byte, error) { challenge, err := a.client.CreateChallenge(ctx) if err != nil { diff --git a/launcher/agent/agent_test.go b/launcher/agent/agent_test.go index ea3756234..b502c5788 100644 --- a/launcher/agent/agent_test.go +++ b/launcher/agent/agent_test.go @@ -1,58 +1,58 @@ package agent import ( - "bytes" "context" - "log" - "net" + "crypto/rand" + "crypto/rsa" + "fmt" "testing" + "github.com/golang-jwt/jwt/v4" "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/internal/test" - "github.com/google/go-tpm-tools/launcher/verifier/grpcclient" - "github.com/google/go-tpm-tools/launcher/verifier/grpcclient/service" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" - - servgrpc "github.com/google/go-tpm-tools/launcher/verifier/grpcclient/proto/attestation_verifier/v0" + "github.com/google/go-tpm-tools/launcher/verifier/fake" ) func TestAttest(t *testing.T) { tpm := test.GetTPM(t) defer client.CheckedClose(t, tpm) - server := grpc.NewServer() - - fakeServer := service.New() - servgrpc.RegisterAttestationVerifierServer(server, &fakeServer) - - lis := bufconn.Listen(1024 * 1024) - go func() { - if err := server.Serve(lis); err != nil { - log.Fatalf("Server exited with error: %v", err) - } - }() - bufDialer := func(context.Context, string) (net.Conn, error) { - return lis.Dial() - } - - conn, err := grpc.DialContext(context.Background(), "bufconn", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials())) + fakeSigner, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - t.Fatalf("failed to connect to attestation service: %v", err) + t.Errorf("Failed to generate signing key %v", err) } - verifierClient := grpcclient.NewClient(conn, log.Default()) - // Cannot test a GCE key on the simulator. + verifierClient := fake.NewClient(fakeSigner) agent := CreateAttestationAgent(tpm, client.AttestationKeyECC, verifierClient, placeholderFetcher) - token, err := agent.Attest(context.Background()) + tokenBytes, err := agent.Attest(context.Background()) if err != nil { t.Errorf("failed to attest to Attestation Service: %v", err) } - if !bytes.Equal(token, service.FakeToken) { - t.Errorf("received unexpected token: %v, expected: %v", token, service.FakeToken) + registeredClaims := &jwt.RegisteredClaims{} + keyFunc := func(token *jwt.Token) (interface{}, error) { return fakeSigner.Public(), nil } + token, err := jwt.ParseWithClaims(string(tokenBytes), registeredClaims, keyFunc) + if err != nil { + t.Errorf("Failed to parse token %s", err) + } + + if err = registeredClaims.Valid(); err != nil { + t.Errorf("Invalid exp, iat, or nbf: %s", err) } + + if !registeredClaims.VerifyAudience("https://sts.googleapis.com/", true) { + t.Errorf("Invalid aud") + } + + if !registeredClaims.VerifyIssuer("https://confidentialcomputing.googleapis.com/", true) { + t.Errorf("Invalid iss") + } + + if registeredClaims.Subject != "https://www.googleapis.com/compute/v1/projects/fakeProject/zones/fakeZone/instances/fakeInstance" { + t.Errorf("Invalid sub") + } + + fmt.Printf("token.Claims: %v\n", token.Claims) } func placeholderFetcher(audience string) ([][]byte, error) { diff --git a/launcher/verifier/fake/fakeverifier.go b/launcher/verifier/fake/fakeverifier.go new file mode 100644 index 000000000..c6a60a4d1 --- /dev/null +++ b/launcher/verifier/fake/fakeverifier.go @@ -0,0 +1,66 @@ +// Package fake is a fake implementation of the Client interface for testing. +package fake + +import ( + "context" + "crypto" + "encoding/binary" + "time" + + "github.com/golang-jwt/jwt/v4" + "github.com/google/go-tpm-tools/launcher/verifier" +) + +type fakeClient struct { + signer crypto.Signer +} + +// NewClient contructs a new fake client given a crypto.Signer. +func NewClient(signer crypto.Signer) verifier.Client { + return &fakeClient{signer} +} + +// CreateChallenge returns a hard coded, basic challenge. +// +// If you have found this method is insufficient for your tests, this class must be updated to +// allow for better testing. +func (fc *fakeClient) CreateChallenge(ctx context.Context) (*verifier.Challenge, error) { + bs := make([]byte, 2) + binary.LittleEndian.PutUint16(bs, 15) + return &verifier.Challenge{ + Name: "projects/fakeProject/locations/fakeRegion/challenges/d882c62f-452f-4709-9335-0cccaf64eee1", + Nonce: bs, + }, nil +} + +// VerifyAttestation does basic checks and returns a hard coded attestation response. +// +// If you have found this method is insufficient for your tests, this class must be updated to +// allow for better testing. +func (fc *fakeClient) VerifyAttestation(ctx context.Context, request verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) { + // Determine signing algorithm. + signingMethod := jwt.SigningMethodRS256 + now := jwt.TimeFunc() + claims := jwt.RegisteredClaims{ + IssuedAt: &jwt.NumericDate{Time: now}, + NotBefore: &jwt.NumericDate{Time: now}, + ExpiresAt: &jwt.NumericDate{Time: now.Add(time.Hour)}, + Audience: []string{"https://sts.googleapis.com/"}, + Issuer: "https://confidentialcomputing.googleapis.com/", + Subject: "https://www.googleapis.com/compute/v1/projects/fakeProject/zones/fakeZone/instances/fakeInstance", + } + + token := jwt.NewWithClaims(signingMethod, claims) + + // Instead of a private key, provide the signer. + signed, err := token.SignedString(fc.signer) + if err != nil { + return nil, err + } + + response := verifier.VerifyAttestationResponse{ + ClaimsToken: []byte(signed), + } + + return &response, nil +} diff --git a/launcher/verifier/grpcclient/service/fake_attestationverifier.go b/launcher/verifier/grpcclient/service/fake_attestationverifier.go deleted file mode 100644 index 8e6ae8c98..000000000 --- a/launcher/verifier/grpcclient/service/fake_attestationverifier.go +++ /dev/null @@ -1,81 +0,0 @@ -// Package service is a fake implementation of the AttestationVerifier for testing. -package service - -import ( - "context" - "crypto/rand" - "fmt" - - servpb "github.com/google/go-tpm-tools/launcher/verifier/grpcclient/proto/attestation_verifier/v0" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - _ "embed" -) - -// FakeToken is generated by fake_tokens/fake_rsa_token.txt. -//go:embed fake_tokens/fake_rsa_token.txt -var FakeToken []byte - -// FakeServer implements the AttestationVerifier service methods. The initial -// connection ID produced by the server will be "0", incrementing with every -// subsequent request to GetParams. -type FakeServer struct { - // conns maps connection IDs to nonces. - conns map[string][]byte - - // nextConnID represents the next connection ID the server will produce. - nextConnID int -} - -// Check that FakeServer implements servpb.AttestationVerifierServer. -var _ servpb.AttestationVerifierServer = &FakeServer{} - -// New constructs a new FakeServer. -func New() FakeServer { - fs := FakeServer{} - fs.conns = make(map[string][]byte) - fs.nextConnID = 0 - return fs -} - -// GetParams requests attestation parameters (including nonce and audience). -func (s *FakeServer) GetParams(ctx context.Context, req *servpb.GetParamsRequest) (*servpb.GetParamsResponse, error) { - nonce := make([]byte, 32) - rand.Read(nonce) - - connID := fmt.Sprint(s.nextConnID) - s.nextConnID++ - - s.conns[connID] = nonce - - resp := &servpb.GetParamsResponse{ - ConnId: connID, - Nonce: nonce, - Audience: "https://fake_attestation_verifier/v0/conn_id/" + connID, - } - - return resp, nil -} - -// Verify verifies the attestation and return an OIDC/JWT token. -func (s *FakeServer) Verify(ctx context.Context, req *servpb.VerifyRequest) (*servpb.VerifyResponse, error) { - if req.GetConnId() == "" { - return nil, status.Error(codes.InvalidArgument, "VerifyRequest is missing conn_id") - } - - if _, ok := s.conns[req.GetConnId()]; !ok { - return nil, status.Error(codes.InvalidArgument, "conn_id was not found") - } - - if req.GetAttestation() == nil { - return nil, status.Error(codes.InvalidArgument, "VerifyRequest is missing attestation") - } - - // TODO(b/210015375): Return a more realistic fake OIDC token with fake signing key and claims. - resp := &servpb.VerifyResponse{ - ClaimsToken: FakeToken, - } - - return resp, nil -} diff --git a/launcher/verifier/grpcclient/service/fake_attestationverifier_test.go b/launcher/verifier/grpcclient/service/fake_attestationverifier_test.go deleted file mode 100644 index c93c01e38..000000000 --- a/launcher/verifier/grpcclient/service/fake_attestationverifier_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package service - -import ( - "context" - "testing" - - servpb "github.com/google/go-tpm-tools/launcher/verifier/grpcclient/proto/attestation_verifier/v0" - pb "github.com/google/go-tpm-tools/proto/attest" -) - -func TestGetParamsSuccess(t *testing.T) { - s := New() - ctx := context.Background() - - resp, err := s.GetParams(ctx, &servpb.GetParamsRequest{}) - if err != nil { - t.Errorf("Want no error from GetParams, got %v", err) - } - - if resp.GetConnId() == "" { - t.Errorf("Want non-empty connection ID, got %v", resp.GetConnId()) - } - - if len(resp.GetNonce()) == 0 { - t.Errorf("Want non-empty nonce, got: %v", resp.GetNonce()) - } - - if resp.GetAudience() == "" { - t.Errorf("Want non-empty audience, got %v", resp.GetAudience()) - } -} - -func TestGetParamsNoRepeatedConnIDs(t *testing.T) { - s := New() - ctx := context.Background() - - seen := make(map[string]bool) - - for i := 0; i < 100; i++ { - resp, err := s.GetParams(ctx, &servpb.GetParamsRequest{}) - if err != nil { - t.Errorf("Want no error from GetParams, got %v", err) - } - - if seen[resp.GetConnId()] { - t.Errorf("Found duplicate connection ID: %v:", resp.GetConnId()) - } - - seen[resp.GetConnId()] = true - } -} - -func TestVerifyEmptyConnID(t *testing.T) { - s := New() - ctx := context.Background() - - if _, err := s.Verify(ctx, &servpb.VerifyRequest{}); err == nil { - t.Errorf("Want error after providing no connection ID, got none") - } -} - -func TestVerifyInvalidConnID(t *testing.T) { - s := New() - ctx := context.Background() - - if _, err := s.Verify(ctx, &servpb.VerifyRequest{ConnId: "bad"}); err == nil { - t.Errorf("Want error after providing bad connection ID, got none") - } -} - -func TestVerifyNoAttestation(t *testing.T) { - s := New() - ctx := context.Background() - - // Get valid connection ID from a call to GetParams. - resp, err := s.GetParams(ctx, &servpb.GetParamsRequest{}) - if err != nil { - t.Errorf("Want no error from GetParams, got %v", err) - } - - if _, err := s.Verify(ctx, &servpb.VerifyRequest{ConnId: resp.GetConnId()}); err == nil { - t.Errorf("Want error after providing no attestation, got none") - } -} - -func TestVerifySuccess(t *testing.T) { - s := New() - ctx := context.Background() - - // Get valid connection ID from a call to GetParams. - resp, err := s.GetParams(ctx, &servpb.GetParamsRequest{}) - if err != nil { - t.Errorf("Want no error from GetParams, got %v", err) - } - - req := &servpb.VerifyRequest{ - ConnId: resp.GetConnId(), - Attestation: &pb.Attestation{EventLog: []byte("I am an event log")}, - } - - verifyResp, err := s.Verify(ctx, req) - if err != nil { - t.Errorf("Want no error from Verify, got: %v", err) - } - // TODO(b/206146397): Verify attestation, checking nonce. - - // TODO(b/206146397): Check signing key and claims in fake OIDC token response. - if len(verifyResp.GetClaimsToken()) == 0 { - t.Errorf("Want non-empty claims token, got: %v", verifyResp.GetClaimsToken()) - } -} diff --git a/launcher/verifier/grpcclient/service/fake_tokens/fake_rsa_token.txt b/launcher/verifier/grpcclient/service/fake_tokens/fake_rsa_token.txt deleted file mode 100644 index 054550dac..000000000 --- a/launcher/verifier/grpcclient/service/fake_tokens/fake_rsa_token.txt +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSUzI1NiIsImtpZCI6InNPeEhJdzNVa2djVEs1RW4xKy8zeFBMUUJRRCtSVnNVdnRrTTBLdzh5U1E9IiwidHlwIjoiSldUIn0.eyJhdWQiOiJodHRwczovL3N0cy5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTY0MDk4MTg0OSwiaWF0IjoxNjQwODk1NDQ5LCJpc3MiOiJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRpb24tdmVyaWZpZXItZGV2IiwibmJmIjoxNjQwODk1NDQ5LCJzdWIiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9jb21wdXRlL3YxL3Byb2plY3RzL2JpdGxvY2tlci1kYW5jZS1kZW1vL3pvbmVzL3VzLXdlc3QyLWMvaW5zdGFuY2VzL3VidTIwIiwidGVlIjp7InZlcnNpb24iOnsibWFqb3IiOjAsIm1pbm9yIjoxfSwicGxhdGZvcm0iOnsiaGFyZHdhcmVfdGVjaG5vbG9neSI6IkFNRF9TRVYifSwiY29udGFpbmVyIjp7ImltYWdlX3JlZmVyZW5jZSI6ImxhdW5jaGVyLmdjci5pby9nb29nbGUvZGViaWFuMTA6bGF0ZXN0IiwiaW1hZ2VfZGlnZXN0Ijoic2hhMjU2OmZmMTY0YTY3NjZiZDhiZDJkN2Q1ZTk0ZGI0MTg2NDY0MjZlNTg3MmM2NjRjNmEyNzBjNjg5ZjBiMWQyOGY4NmYiLCJyZXN0YXJ0X3BvbGljeSI6Ik5ldmVyIiwiaW1hZ2VfaWQiOiJzaGEyNTY6MzBlMzgwNTI0MmU1MDIyMzIzYTk3ZjhiNmRlZWM4NGZlNTgzMjBjM2NjMWU3MTc0MjlkZjdmNDdiNWIzMTdiZSIsImVudl9vdmVycmlkZSI6ZmFsc2UsImNtZF9vdmVycmlkZSI6ZmFsc2UsImVudHJ5cG9pbnRfb3ZlcnJpZGUiOmZhbHNlLCJlbnYiOnsiRk9PIjoiQkFSIiwiUEFUSCI6Ii91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL2JpbiIsIlNPTUVfTkFNRSI6IlNPTUVfVkFMVUUifSwiY21kIjpbIi0tZm9vIiwiYmFyIiwiYmF6Il19LCJnY2UiOnsiem9uZSI6InVzLXdlc3QyLWMiLCJwcm9qZWN0X2lkIjoiYml0bG9ja2VyLWRhbmNlLWRlbW8iLCJwcm9qZWN0X251bWJlciI6NTY3MDQ1MDUzODc4LCJpbnN0YW5jZV9uYW1lIjoidWJ1MjAiLCJpbnN0YW5jZV9pZCI6Mjk5NDAxNTUzNzY4MjU1NDcwOX0sImVtYWlscyI6WyJjeWhhbmlzaEBnb29nbGUuY29tIiwiamVzc2llcWxpdUBnb29nbGUuY29tIl19fQ.LQDMX2Q3cAQn6ATQIdWyzK-w6PfjSJ9Xb2VbTpRgHtqgQMr4tmU6N27pnmPnkeVkUubhOvf5g3c2FOxNcZvU7pROJNo_2JJaJK2q70PskwoMcZyQLtY0NUV3FgSAZHQfi6HxCwDIDQGGN1PZ9CfQi6hP8xZBfso0__M-eTuMOcy8Zq3_w0o7JIkgL7NZcKr61dFDTGLwzNPGpPnJsoAVXWbMRz3oUxfXPHXwfOiLeSEEh4R7Gh6bshkms4Trxy0H20yMsVq69YZ4vNba3Vv147PlJXvFqcTTXKlzZTWYdLswtZaeDTremh-B6koEoQYyIJ14Jdz9fIDtErSE6okQ-XNH7PesbbaJFZBvULJwIP0izy3JHucNqdXwzr0PCCIY_8e_mTo9PCdXgbtaIsiFRlBweUq2jkWPxVs63diFNULaBZRN8He45jl6evCAl15bEEnt9dm-U__3DigXjh6D9N6qtg5B0aNg28544RhPbhr2shu_l2vTxQlaltSrlFJS