mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-23 13:51:06 -05:00
AB#1770 (semi)automatic PCR updates (#7)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
752571bbf8
commit
1f843d4593
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,6 +18,7 @@ build
|
|||||||
admin.conf
|
admin.conf
|
||||||
coordinatorConfig.json
|
coordinatorConfig.json
|
||||||
coordinator-*
|
coordinator-*
|
||||||
|
util/pcr-reader/pcrs/
|
||||||
|
|
||||||
# VS Code configuration folder
|
# VS Code configuration folder
|
||||||
.vscode
|
.vscode
|
||||||
|
89
util/pcr-reader/README.md
Normal file
89
util/pcr-reader/README.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# PCR-updater
|
||||||
|
|
||||||
|
New images result in different PCR values for the image.
|
||||||
|
This utility program makes it simple to update the expected PCR values of the CLI.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Script
|
||||||
|
|
||||||
|
Run `fetch_pcrs.sh` to create Constellations on all supported cloud providers and read their PCR states.
|
||||||
|
```shell
|
||||||
|
./fetch_pcrs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is printed to screen and written as Go code to files in `./pcrs`.
|
||||||
|
```bash
|
||||||
|
+ main
|
||||||
|
+ command -v constellation
|
||||||
|
+ command -v go
|
||||||
|
+ mkdir -p ./pcrs
|
||||||
|
+ constellation create azure 2 Standard_D4s_v3 --name pcr-fetch -y
|
||||||
|
Your Constellation was created successfully.
|
||||||
|
++ jq '.azurecoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json
|
||||||
|
+ coord_ip=192.0.2.1
|
||||||
|
+ go run ../main.go -coord-ip 192.0.2.1 -o ./pcrs/azure_pcrs.go
|
||||||
|
connecting to Coordinator at 192.0.2.1:9000
|
||||||
|
PCRs:
|
||||||
|
{
|
||||||
|
"0": "q27iAZeXGAiCPdu1bqRA2gAoyMO2KrXWY4YkTCQowc4=",
|
||||||
|
...
|
||||||
|
"9": "dEGJtQe3h+SI0z42yO7TklzwPixtM3iMCUeJPGRozvg="
|
||||||
|
}
|
||||||
|
+ constellation terminate
|
||||||
|
Your Constellation was terminated successfully.
|
||||||
|
+ constellation create gcp 2 n2d-standard-2 --name pcr-fetch -y
|
||||||
|
Your Constellation was created successfully.
|
||||||
|
++ jq '.gcpcoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json
|
||||||
|
+ coord_ip=192.0.2.2
|
||||||
|
+ go run ../main.go -coord-ip 192.0.2.2 -o ./pcrs/gcp_pcrs.go
|
||||||
|
connecting to Coordinator at 192.0.2.2:9000
|
||||||
|
PCRs:
|
||||||
|
{
|
||||||
|
"0": "DzXCFGCNk8em5ornNZtKi+Wg6Z7qkQfs5CfE3qTkOc8=",
|
||||||
|
...
|
||||||
|
"9": "gse53SjsqREEdOpImJH4KAb0b8PqIgwI+Ps/XSiFnN4="
|
||||||
|
}
|
||||||
|
+ constellation terminate
|
||||||
|
Your Constellation was terminated successfully.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
To read the PCR state of any running Constellation node, run the following:
|
||||||
|
```shell
|
||||||
|
go run main.go -coord-ip <NODE_IP> -coord-port <COORDINATOR_PORT>
|
||||||
|
```
|
||||||
|
|
||||||
|
The output is similar to the following:
|
||||||
|
```shell
|
||||||
|
$ go run main.go -coord-ip 192.0.2.3 -coord-port 12345
|
||||||
|
connecting to Coordinator at 192.0.2.3:12345
|
||||||
|
PCRs:
|
||||||
|
{
|
||||||
|
"0": "DzXCFGCNk8em5ornNZtKi+Wg6Z7qkQfs5CfE3qTkOc8=",
|
||||||
|
"1": "XBoRlWuQx6nIDr5vgUL0DlJHy6H6u1dPU3qK2NyToc8=",
|
||||||
|
"10": "WLmYFRmDft/ajZJ056CAhpheU6Vbt73aR8eIQpLRGq0=",
|
||||||
|
"11": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"12": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"13": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"14": "4tPyJd6A5g09KduV3+nWZQCiEzHAiRT5DulmAqlvpZU=",
|
||||||
|
"15": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"16": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"17": "//////////////////////////////////////////8=",
|
||||||
|
"18": "//////////////////////////////////////////8=",
|
||||||
|
"19": "//////////////////////////////////////////8=",
|
||||||
|
"2": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||||
|
"20": "//////////////////////////////////////////8=",
|
||||||
|
"21": "//////////////////////////////////////////8=",
|
||||||
|
"22": "//////////////////////////////////////////8=",
|
||||||
|
"23": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"3": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||||
|
"4": "MmkueFj1rP2seH+bjeIRsO4dUnLnMdl7QgtGoAtQH7M=",
|
||||||
|
"5": "ExaiapuIfo0KMBo8wj6kPDORLocgnH1C0G/KY8DcV3A=",
|
||||||
|
"6": "PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=",
|
||||||
|
"7": "UZcW+fhFRMpFkgU+EfKG2s3KdmgEA+TD2quLmthQHbo=",
|
||||||
|
"8": "KLSMootYaHBjysWKq9CAYXkXpeYx9PUBimlSEZGJqUM=",
|
||||||
|
"9": "gse53SjsqREEdOpImJH4KAb0b8PqIgwI+Ps/XSiFnN4="
|
||||||
|
}
|
||||||
|
```
|
41
util/pcr-reader/fetch_pcrs.sh
Executable file
41
util/pcr-reader/fetch_pcrs.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o xtrace
|
||||||
|
trap 'terminate $?' ERR
|
||||||
|
|
||||||
|
terminate() {
|
||||||
|
echo "error: $1"
|
||||||
|
constellation terminate
|
||||||
|
popd || exit 1
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
if ! command -v constellation &> /dev/null
|
||||||
|
then
|
||||||
|
echo "constellation is not in path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v go &> /dev/null
|
||||||
|
then
|
||||||
|
echo "go is not in path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ./pcrs
|
||||||
|
|
||||||
|
# Fetch Azure PCRs
|
||||||
|
# TODO: Switch to confidential VMs
|
||||||
|
constellation create azure 2 Standard_D4s_v3 --name pcr-fetch -y
|
||||||
|
coord_ip=$(jq '.azurecoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json)
|
||||||
|
go run main.go -coord-ip "${coord_ip}" -o ./pcrs/azure_pcrs.go
|
||||||
|
constellation terminate
|
||||||
|
|
||||||
|
# Fetch GCP PCRs
|
||||||
|
constellation create gcp 2 n2d-standard-2 --name pcr-fetch -y
|
||||||
|
coord_ip=$(jq '.gcpcoordinators | to_entries[] | select(.key|startswith("")) | .value.PublicIP' -rcM constellation-state.json)
|
||||||
|
go run main.go -coord-ip "${coord_ip}" -o ./pcrs/gcp_pcrs.go
|
||||||
|
constellation terminate
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
167
util/pcr-reader/main.go
Normal file
167
util/pcr-reader/main.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/cli/status"
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/atls"
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/oid"
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||||
|
coordinatorstate "github.com/edgelesssys/constellation/coordinator/state"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
coordIP = flag.String("coord-ip", "", "IP of the VM the Coordinator is running on")
|
||||||
|
coordinatorPort = flag.String("coord-port", "9000", "Port of the Coordinator's pub API")
|
||||||
|
export = flag.String("o", "", "Write PCRs, formatted as Go code, to file")
|
||||||
|
quiet = flag.Bool("q", false, "Set to disable output")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
fmt.Printf("connecting to Coordinator at %s:%s\n", *coordIP, *coordinatorPort)
|
||||||
|
addr := net.JoinHostPort(*coordIP, *coordinatorPort)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// wait for coordinator to come online
|
||||||
|
waiter := status.NewWaiter(map[uint32][]byte{})
|
||||||
|
if err := waiter.WaitFor(ctx, coordinatorstate.AcceptingInit, addr); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attDocRaw := &[]byte{}
|
||||||
|
tlsConfig, err := atls.CreateUnverifiedClientTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tlsConfig.VerifyPeerCertificate = getVerifyPeerCertificateFunc(attDocRaw)
|
||||||
|
if err := connectToCoordinator(ctx, addr, tlsConfig); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pcrs, err := validatePCRAttDoc(*attDocRaw)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*quiet {
|
||||||
|
if err := printPCRs(os.Stdout, pcrs); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *export != "" {
|
||||||
|
if err := exportToFile(*export, pcrs, &afero.Afero{Fs: afero.NewOsFs()}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// connectToCoordinator connects to the Constellation Coordinator and returns its attestation document.
|
||||||
|
func connectToCoordinator(ctx context.Context, addr string, tlsConfig *tls.Config) error {
|
||||||
|
conn, err := grpc.DialContext(
|
||||||
|
ctx, addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := pubproto.NewAPIClient(conn)
|
||||||
|
_, err = client.GetState(ctx, &pubproto.GetStateRequest{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVerifyPeerCertificateFunc returns a VerifyPeerCertificate function, which writes the attestation document extension to the given byte slice pointer.
|
||||||
|
func getVerifyPeerCertificateFunc(attDoc *[]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
if len(rawCerts) == 0 {
|
||||||
|
return errors.New("rawCerts is empty")
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ex := range cert.Extensions {
|
||||||
|
if ex.Id.Equal(oid.Azure{}.OID()) || ex.Id.Equal(oid.GCP{}.OID()) || ex.Id.Equal(oid.GCPNonCVM{}.OID()) {
|
||||||
|
if err := json.Unmarshal(ex.Value, attDoc); err != nil {
|
||||||
|
*attDoc = ex.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*attDoc) == 0 {
|
||||||
|
return errors.New("did not receive attestation document in certificate extension")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatePCRAttDoc parses and validates PCRs of an attestation document.
|
||||||
|
func validatePCRAttDoc(attDocRaw []byte) (map[uint32][]byte, error) {
|
||||||
|
attDoc := vtpm.AttestationDocument{}
|
||||||
|
if err := json.Unmarshal(attDocRaw, &attDoc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if attDoc.Attestation == nil {
|
||||||
|
return nil, errors.New("empty attestation")
|
||||||
|
}
|
||||||
|
qIdx, err := vtpm.GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for idx, pcr := range attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs {
|
||||||
|
if len(pcr) != 32 {
|
||||||
|
return nil, fmt.Errorf("incomplete PCR at index: %d", idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printPCRs formates and prints PCRs to the given writer.
|
||||||
|
func printPCRs(w io.Writer, pcrs map[uint32][]byte) error {
|
||||||
|
pcrJSON, err := json.MarshalIndent(pcrs, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "PCRs:\n%s\n", string(pcrJSON))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// exportToFile writes pcrs to a file, formatted to be valid Go code.
|
||||||
|
// Validity of the PCR map is not checked, and should be handled by the caller.
|
||||||
|
func exportToFile(path string, pcrs map[uint32][]byte, fs *afero.Afero) error {
|
||||||
|
goCode := `package pcrs
|
||||||
|
|
||||||
|
var pcrs = map[uint32][]byte{%s
|
||||||
|
}
|
||||||
|
`
|
||||||
|
pcrsFormatted := ""
|
||||||
|
for i := 0; i < len(pcrs); i++ {
|
||||||
|
pcrHex := fmt.Sprintf("%#02X", pcrs[uint32(i)][0])
|
||||||
|
for j := 1; j < len(pcrs[uint32(i)]); j++ {
|
||||||
|
pcrHex = fmt.Sprintf("%s, %#02X", pcrHex, pcrs[uint32(i)][j])
|
||||||
|
}
|
||||||
|
|
||||||
|
pcrsFormatted = pcrsFormatted + fmt.Sprintf("\n\t%d: {%s},", i, pcrHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.WriteFile(path, []byte(fmt.Sprintf(goCode, pcrsFormatted)), 0o644)
|
||||||
|
}
|
246
util/pcr-reader/main_test.go
Normal file
246
util/pcr-reader/main_test.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/coordinator/oid"
|
||||||
|
"github.com/google/go-tpm-tools/proto/attest"
|
||||||
|
"github.com/google/go-tpm-tools/proto/tpm"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetVerifyPeerCertificateFunc(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
rawCerts [][]byte
|
||||||
|
errExpected bool
|
||||||
|
}{
|
||||||
|
"no certificates": {
|
||||||
|
rawCerts: nil,
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"invalid certificate": {
|
||||||
|
rawCerts: [][]byte{
|
||||||
|
{0x1, 0x2, 0x3},
|
||||||
|
},
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"no extension": {
|
||||||
|
rawCerts: [][]byte{
|
||||||
|
mustGenerateTestCert(t, &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(123),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"certificate with attestation": {
|
||||||
|
rawCerts: [][]byte{
|
||||||
|
mustGenerateTestCert(t, &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(123),
|
||||||
|
ExtraExtensions: []pkix.Extension{
|
||||||
|
{
|
||||||
|
Id: oid.GCP{}.OID(),
|
||||||
|
Value: []byte{0x1, 0x2, 0x3},
|
||||||
|
Critical: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
errExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
attDoc := &[]byte{}
|
||||||
|
verify := getVerifyPeerCertificateFunc(attDoc)
|
||||||
|
|
||||||
|
err := verify(tc.rawCerts, nil)
|
||||||
|
if tc.errExpected {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
assert.NotNil(attDoc)
|
||||||
|
cert, err := x509.ParseCertificate(tc.rawCerts[0])
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(cert.Extensions[0].Value, *attDoc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustGenerateTestCert(t *testing.T, template *x509.Certificate) []byte {
|
||||||
|
require := require.New(t)
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.NoError(err)
|
||||||
|
cert, err := x509.CreateCertificate(rand.Reader, template, template, priv.Public(), priv)
|
||||||
|
require.NoError(err)
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportToFile(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
pcrs map[uint32][]byte
|
||||||
|
fs *afero.Afero
|
||||||
|
errExpected bool
|
||||||
|
}{
|
||||||
|
"file not writeable": {
|
||||||
|
pcrs: map[uint32][]byte{
|
||||||
|
0: {0x1, 0x2, 0x3},
|
||||||
|
1: {0x1, 0x2, 0x3},
|
||||||
|
2: {0x1, 0x2, 0x3},
|
||||||
|
},
|
||||||
|
fs: &afero.Afero{Fs: afero.NewReadOnlyFs(afero.NewMemMapFs())},
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"file writeable": {
|
||||||
|
pcrs: map[uint32][]byte{
|
||||||
|
0: {0x1, 0x2, 0x3},
|
||||||
|
1: {0x1, 0x2, 0x3},
|
||||||
|
2: {0x1, 0x2, 0x3},
|
||||||
|
},
|
||||||
|
fs: &afero.Afero{Fs: afero.NewMemMapFs()},
|
||||||
|
errExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
path := "test-file"
|
||||||
|
err := exportToFile(path, tc.pcrs, tc.fs)
|
||||||
|
if tc.errExpected {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
content, err := tc.fs.ReadFile(path)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
for _, pcr := range tc.pcrs {
|
||||||
|
for _, register := range pcr {
|
||||||
|
assert.Contains(string(content), fmt.Sprintf("%#02X", register))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatePCRAttDoc(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
attDocRaw []byte
|
||||||
|
errExpected bool
|
||||||
|
}{
|
||||||
|
"invalid attestation document": {
|
||||||
|
attDocRaw: []byte{0x1, 0x2, 0x3},
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"nil attestation": {
|
||||||
|
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{}),
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"nil quotes": {
|
||||||
|
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||||
|
Attestation: &attest.Attestation{},
|
||||||
|
}),
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"invalid PCRs": {
|
||||||
|
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||||
|
Attestation: &attest.Attestation{
|
||||||
|
Quotes: []*tpm.Quote{
|
||||||
|
{
|
||||||
|
Pcrs: &tpm.PCRs{
|
||||||
|
Hash: tpm.HashAlgo_SHA256,
|
||||||
|
Pcrs: map[uint32][]byte{
|
||||||
|
0: {0x1, 0x2, 0x3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
errExpected: true,
|
||||||
|
},
|
||||||
|
"valid PCRs": {
|
||||||
|
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
|
||||||
|
Attestation: &attest.Attestation{
|
||||||
|
Quotes: []*tpm.Quote{
|
||||||
|
{
|
||||||
|
Pcrs: &tpm.PCRs{
|
||||||
|
Hash: tpm.HashAlgo_SHA256,
|
||||||
|
Pcrs: map[uint32][]byte{
|
||||||
|
0: []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
errExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
pcrs, err := validatePCRAttDoc(tc.attDocRaw)
|
||||||
|
if tc.errExpected {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
attDoc := vtpm.AttestationDocument{}
|
||||||
|
require.NoError(json.Unmarshal(tc.attDocRaw, &attDoc))
|
||||||
|
qIdx, err := vtpm.GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
|
||||||
|
require.NoError(err)
|
||||||
|
assert.EqualValues(attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, pcrs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustMarshalAttDoc(t *testing.T, attDoc vtpm.AttestationDocument) []byte {
|
||||||
|
attDocRaw, err := json.Marshal(attDoc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return attDocRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintPCRs(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
pcrs := map[uint32][]byte{
|
||||||
|
0: {0x1, 0x2, 0x3},
|
||||||
|
1: {0x1, 0x2, 0x3},
|
||||||
|
2: {0x1, 0x2, 0x3},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
err := printPCRs(&out, pcrs)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
for idx, pcr := range pcrs {
|
||||||
|
assert.Contains(out.String(), fmt.Sprintf("\"%d\": ", idx))
|
||||||
|
assert.Contains(out.String(), fmt.Sprintf(": \"%s\"", base64.StdEncoding.EncodeToString(pcr)))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user