// Copyright 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

//go:build !localtest && cgo && !gofuzz
// +build !localtest,cgo,!gofuzz

package attest

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"slices"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/google/go-tpm/legacy/tpm2"
)

func TestSimCertificationParametersRSA(t *testing.T) {
	sim, tpm := setupSimulatedTPM(t)
	defer sim.Close()
	testCertificationParameters(t, tpm, RSA)
}

func TestSimCertificationParametersECC(t *testing.T) {
	sim, tpm := setupSimulatedTPM(t)
	defer sim.Close()
	testCertificationParameters(t, tpm, ECDSA)
}

func TestCertificationParametersRSA(t *testing.T) {
	if !*testLocal {
		t.SkipNow()
	}
	tpm, err := OpenTPM(nil)
	if err != nil {
		t.Fatalf("OpenTPM() failed: %v", err)
	}
	defer tpm.Close()
	testCertificationParameters(t, tpm, RSA)
}

func TestCertificationParametersECC(t *testing.T) {
	if !*testLocal {
		t.SkipNow()
	}
	tpm, err := OpenTPM(nil)
	if err != nil {
		t.Fatalf("OpenTPM() failed: %v", err)
	}
	defer tpm.Close()
	testCertificationParameters(t, tpm, ECDSA)
}

func testCertificationParameters(t *testing.T, tpm *TPM, akAlg Algorithm) {
	ak, err := tpm.NewAK(&AKConfig{Algorithm: akAlg})
	if err != nil {
		t.Fatal(err)
	}
	akAttestParams := ak.AttestationParameters()
	pub, err := tpm2.DecodePublic(akAttestParams.Public)
	if err != nil {
		t.Fatal(err)
	}

	pk, err := pub.Key()
	if err != nil {
		t.Fatal(err)
	}
	hash, err := pub.NameAlg.Hash()
	if err != nil {
		t.Fatal(err)
	}
	correctOpts := VerifyOpts{
		Public: pk,
		Hash:   hash,
	}

	wrongKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatal(err)
	}
	wrongHash := crypto.SHA512_256
	if wrongHash == correctOpts.Hash {
		wrongHash = crypto.SHA256
	}

	sk, err := tpm.NewKey(ak, nil)
	if err != nil {
		t.Fatal(err)
	}
	skCertParams := sk.CertificationParameters()

	for _, test := range []struct {
		name string
		p    *CertificationParameters
		opts VerifyOpts
		err  error
	}{
		{
			name: "OK",
			p:    &skCertParams,
			opts: correctOpts,
			err:  nil,
		},
		{
			name: "wrong public key",
			p:    &skCertParams,
			opts: VerifyOpts{
				Public: wrongKey.Public,
				Hash:   correctOpts.Hash,
			},
			err: cmpopts.AnyError,
		},
		{
			name: "wrong hash function",
			p:    &skCertParams,
			opts: VerifyOpts{
				Public: correctOpts.Public,
				Hash:   wrongHash,
			},
			err: cmpopts.AnyError,
		},
		{
			name: "unavailable hash function",
			p:    &skCertParams,
			opts: VerifyOpts{
				Public: correctOpts.Public,
				Hash:   crypto.BLAKE2b_384,
			},
			err: cmpopts.AnyError,
		},
		{
			name: "modified Public",
			p: &CertificationParameters{
				Public:            akAttestParams.Public,
				CreateAttestation: skCertParams.CreateAttestation,
				CreateSignature:   skCertParams.CreateSignature,
			},
			opts: correctOpts,
			err:  cmpopts.AnyError,
		},
		{
			name: "modified CreateAttestation",
			p: &CertificationParameters{
				Public:            skCertParams.Public,
				CreateAttestation: akAttestParams.CreateAttestation,
				CreateSignature:   skCertParams.CreateSignature,
			},
			opts: correctOpts,
			err:  cmpopts.AnyError,
		},
		{
			name: "modified CreateSignature",
			p: &CertificationParameters{
				Public:            skCertParams.Public,
				CreateAttestation: skCertParams.CreateAttestation,
				CreateSignature:   akAttestParams.CreateSignature,
			},
			opts: correctOpts,
			err:  cmpopts.AnyError,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			err := test.p.Verify(test.opts)
			if test.err == nil && err == nil {
				return
			}
			if got, want := err, test.err; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
				t.Errorf("p.Verify() err = %v, want = %v", got, want)
			}
		})
	}
}

func TestSimKeyCertificationRSA(t *testing.T) {
	sim, tpm := setupSimulatedTPM(t)
	defer sim.Close()
	testKeyCertification(t, tpm, RSA)
}

func TestSimKeyCertificationECC(t *testing.T) {
	sim, tpm := setupSimulatedTPM(t)
	defer sim.Close()
	testKeyCertification(t, tpm, ECDSA)
}

func TestKeyCertificationRSA(t *testing.T) {
	if !*testLocal {
		t.SkipNow()
	}
	tpm, err := OpenTPM(nil)
	if err != nil {
		t.Fatalf("OpenTPM() failed: %v", err)
	}
	defer tpm.Close()
	testKeyCertification(t, tpm, RSA)
}

func TestKeyCertificationECC(t *testing.T) {
	if !*testLocal {
		t.SkipNow()
	}
	tpm, err := OpenTPM(nil)
	if err != nil {
		t.Fatalf("OpenTPM() failed: %v", err)
	}
	defer tpm.Close()
	testKeyCertification(t, tpm, ECDSA)
}

func extraData(t *testing.T, p CertificationParameters) []byte {
	t.Helper()
	ad, err := tpm2.DecodeAttestationData(p.CreateAttestation)
	if err != nil {
		t.Fatalf("failed to decode attestation data: %v", err)
	}
	return ad.ExtraData
}

func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) {
	ak, err := tpm.NewAK(&AKConfig{Algorithm: akAlg})
	if err != nil {
		t.Fatalf("NewAK() failed: %v", err)
	}
	akAttestParams := ak.AttestationParameters()
	pub, err := tpm2.DecodePublic(akAttestParams.Public)
	if err != nil {
		t.Fatalf("DecodePublic() failed: %v", err)
	}
	pk, err := pub.Key()
	if err != nil {
		t.Fatalf("pub.Key() failed: %v", err)
	}
	hash, err := pub.NameAlg.Hash()
	if err != nil {
		t.Fatalf("cannot access AK's hash function: %v", err)
	}
	verifyOpts := VerifyOpts{
		Public: pk,
		Hash:   hash,
	}
	for _, test := range []struct {
		name          string
		opts          *KeyConfig
		wantExtraData []byte
		err           error
	}{
		{
			name: "default",
			opts: nil,
			err:  nil,
		},
		{
			name: "ECDSAP256-SHA256",
			opts: &KeyConfig{
				Algorithm: ECDSA,
				Size:      256,
			},
			err: nil,
		},
		{
			name: "ECDSAP384-SHA384",
			opts: &KeyConfig{
				Algorithm: ECDSA,
				Size:      384,
			},
			err: nil,
		},
		{
			name: "ECDSAP521-SHA512",
			opts: &KeyConfig{
				Algorithm: ECDSA,
				Size:      521,
			},
			err: nil,
		},
		{
			name: "RSA-1024, key too short",
			opts: &KeyConfig{
				Algorithm: RSA,
				Size:      1024,
			},
			err: cmpopts.AnyError,
		},
		{
			name: "RSA-2048",
			opts: &KeyConfig{
				Algorithm: RSA,
				Size:      2048,
			},
			err: nil,
		},
		{
			name: "QualifyingData-RSA",
			opts: &KeyConfig{
				Algorithm:      RSA,
				Size:           2048,
				QualifyingData: []byte("qualifying data"),
			},
			wantExtraData: []byte("qualifying data"),
			err:           nil,
		},
		{
			name: "QualifyingData-ECDSA",
			opts: &KeyConfig{
				Algorithm:      ECDSA,
				Size:           384,
				QualifyingData: []byte("qualifying data"),
			},
			wantExtraData: []byte("qualifying data"),
			err:           nil,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			sk, err := tpm.NewKey(ak, test.opts)
			if err != nil {
				t.Fatalf("NewKey() failed: %v", err)
			}
			defer sk.Close()
			p := sk.CertificationParameters()
			if gotExtraData, wantExtraData := extraData(t, p), test.wantExtraData; !slices.Equal(gotExtraData, wantExtraData) {
				t.Errorf("ExtraData got = %v, want = %v", gotExtraData, wantExtraData)
			}
			err = p.Verify(verifyOpts)
			if test.err == nil && err == nil {
				return
			}
			if got, want := err, test.err; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
				t.Errorf("p.Verify() err = %v, want = %v", got, want)
			}
		})
	}
}

func TestKeyActivation(t *testing.T) {
	sim, tpm := setupSimulatedTPM(t)
	defer sim.Close()

	ak, err := tpm.NewAK(nil)
	if err != nil {
		t.Fatalf("error creating a new AK using simulated TPM: %v", err)
	}
	akAttestParams := ak.AttestationParameters()
	pub, err := tpm2.DecodePublic(akAttestParams.Public)
	if err != nil {
		t.Fatalf("unable to decode public struct from AK attestation params: %v", err)
	}
	if pub.Type != tpm2.AlgRSA {
		t.Fatal("non-RSA verifying key")
	}

	eks, err := tpm.EKs()
	if err != nil {
		t.Fatalf("unexpected error retrieving EK from tpm: %v", err)
	}

	if len(eks) == 0 {
		t.Fatal("expected at least one EK from the simulated TPM")
	}

	pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
	hash, err := pub.RSAParameters.Sign.Hash.Hash()
	if err != nil {
		t.Fatalf("unable to compute hash signature from verifying key's RSA parameters: %v", err)
	}
	verifyOpts := VerifyOpts{
		Public: pk,
		Hash:   hash,
	}

	sk, err := tpm.NewKey(ak, nil)
	if err != nil {
		t.Fatalf("unable to create a new TPM-backed key to certify: %v", err)
	}

	skCertParams := sk.CertificationParameters()
	activateOpts, err := NewActivateOpts(pub, eks[0].Public)
	if err != nil {
		t.Fatalf("unable to create new ActivateOpts: %v", err)
	}

	wrongPub, err := tpm2.DecodePublic(skCertParams.Public)
	if err != nil {
		t.Fatalf("unable to decode public struct from CertificationParameters: %v", err)
	}

	wrongActivateOpts, err := NewActivateOpts(wrongPub, eks[0].Public)
	if err != nil {
		t.Fatalf("unable to create wrong ActivateOpts: %v", err)
	}

	for _, test := range []struct {
		name         string
		p            *CertificationParameters
		verifyOpts   VerifyOpts
		activateOpts ActivateOpts
		generateErr  error
		activateErr  error
	}{
		{
			name:         "OK",
			p:            &skCertParams,
			verifyOpts:   verifyOpts,
			activateOpts: *activateOpts,
			generateErr:  nil,
			activateErr:  nil,
		},
		{
			name:         "invalid verify opts",
			p:            &skCertParams,
			verifyOpts:   VerifyOpts{},
			activateOpts: *activateOpts,
			generateErr:  cmpopts.AnyError,
			activateErr:  nil,
		},
		{
			name:         "invalid activate opts",
			p:            &skCertParams,
			verifyOpts:   verifyOpts,
			activateOpts: *wrongActivateOpts,
			generateErr:  nil,
			activateErr:  cmpopts.AnyError,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			expectedSecret, encryptedCredentials, err := test.p.Generate(rand.Reader, test.verifyOpts, test.activateOpts)
			if test.generateErr != nil {
				if got, want := err, test.generateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
					t.Errorf("p.Generate() err = %v, want = %v", got, want)
				}

				return
			}
			if err != nil {
				t.Errorf("unexpected p.Generate() error: %v", err)
				return
			}

			actualSecret, err := ak.ActivateCredential(tpm, *encryptedCredentials)
			if test.activateErr != nil {
				if got, want := err, test.activateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
					t.Errorf("p.ActivateCredential() err = %v, want = %v", got, want)
				}

				return
			}
			if err != nil {
				t.Errorf("unexpected p.ActivateCredential() error: %v", err)
				return
			}

			if !bytes.Equal(expectedSecret, actualSecret) {
				t.Fatalf("Unexpected bytes decoded, expected %x, but got %x", expectedSecret, actualSecret)
			}
		})
	}
}
