tailscale/feature/tpm/attestation_test.go
Andrew Lytvynov f522b9dbb7
feature/tpm: protect all TPM handle operations with a mutex (#17708)
In particular on Windows, the `transport.TPMCloser` we get is not safe
for concurrent use. This is especially noticeable because
`tpm.attestationKey.Clone` uses the same open handle as the original
key. So wrap the operations on ak.tpm with a mutex and make a deep copy
with a new connection in Clone.

Updates #15830
Updates #17662
Updates #17644

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
2025-10-30 10:32:30 -07:00

165 lines
3.4 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tpm
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"runtime"
"sync"
"testing"
)
func TestAttestationKeySign(t *testing.T) {
skipWithoutTPM(t)
ak, err := newAttestationKey()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := ak.Close(); err != nil {
t.Errorf("ak.Close: %v", err)
}
})
data := []byte("secrets")
digest := sha256.Sum256(data)
// Check signature/validation round trip.
sig, err := ak.Sign(rand.Reader, digest[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
if !ecdsa.VerifyASN1(ak.Public().(*ecdsa.PublicKey), digest[:], sig) {
t.Errorf("ecdsa.VerifyASN1 failed")
}
// Create a different key.
ak2, err := newAttestationKey()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := ak2.Close(); err != nil {
t.Errorf("ak2.Close: %v", err)
}
})
// Make sure that the keys are distinct via their public keys and the
// signatures they produce.
if ak.Public().(*ecdsa.PublicKey).Equal(ak2.Public()) {
t.Errorf("public keys of distinct attestation keys are the same")
}
sig2, err := ak2.Sign(rand.Reader, digest[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
if bytes.Equal(sig, sig2) {
t.Errorf("signatures from distinct attestation keys are the same")
}
}
func TestAttestationKeySignConcurrent(t *testing.T) {
skipWithoutTPM(t)
ak, err := newAttestationKey()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := ak.Close(); err != nil {
t.Errorf("ak.Close: %v", err)
}
})
data := []byte("secrets")
digest := sha256.Sum256(data)
wg := sync.WaitGroup{}
for range runtime.GOMAXPROCS(-1) {
wg.Go(func() {
// Check signature/validation round trip.
sig, err := ak.Sign(rand.Reader, digest[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
if !ecdsa.VerifyASN1(ak.Public().(*ecdsa.PublicKey), digest[:], sig) {
t.Errorf("ecdsa.VerifyASN1 failed")
}
})
}
wg.Wait()
}
func TestAttestationKeyUnmarshal(t *testing.T) {
skipWithoutTPM(t)
ak, err := newAttestationKey()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := ak.Close(); err != nil {
t.Errorf("ak.Close: %v", err)
}
})
buf, err := ak.MarshalJSON()
if err != nil {
t.Fatal(err)
}
var ak2 attestationKey
if err := json.Unmarshal(buf, &ak2); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := ak2.Close(); err != nil {
t.Errorf("ak2.Close: %v", err)
}
})
if !ak2.loaded() {
t.Error("unmarshalled key is not loaded")
}
if !ak.Public().(*ecdsa.PublicKey).Equal(ak2.Public()) {
t.Error("unmarshalled public key is not the same as the original public key")
}
}
func TestAttestationKeyClone(t *testing.T) {
skipWithoutTPM(t)
ak, err := newAttestationKey()
if err != nil {
t.Fatal(err)
}
ak2 := ak.Clone()
if ak2 == nil {
t.Fatal("Clone failed")
}
t.Cleanup(func() {
if err := ak2.Close(); err != nil {
t.Errorf("ak2.Close: %v", err)
}
})
// Close the original key, ak2 should remain open and usable.
if err := ak.Close(); err != nil {
t.Fatal(err)
}
data := []byte("secrets")
digest := sha256.Sum256(data)
// Check signature/validation round trip using cloned key.
sig, err := ak2.Sign(rand.Reader, digest[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
if !ecdsa.VerifyASN1(ak2.Public().(*ecdsa.PublicKey), digest[:], sig) {
t.Errorf("ecdsa.VerifyASN1 failed")
}
}