Create hack folder with independent modules (#131)

This commit is contained in:
Paul Meyer 2022-05-17 11:14:23 +02:00 committed by GitHub
parent cfad36720b
commit 8e0f9491af
12 changed files with 1810 additions and 11 deletions

32
hack/clidocgen/main.go Normal file
View file

@ -0,0 +1,32 @@
// Clidocgen generates a Markdown page describing all CLI commands.
package main
import (
"bytes"
"fmt"
"regexp"
"github.com/edgelesssys/constellation/cli/cmd"
"github.com/spf13/cobra/doc"
)
func main() {
rootCmd := cmd.NewRootCmd()
rootCmd.DisableAutoGenTag = true
// Generate Markdown for all commands.
cmdList := &bytes.Buffer{}
body := &bytes.Buffer{}
for _, c := range rootCmd.Commands() {
name := c.Name()
fmt.Fprintf(cmdList, "* [%v](#constellation-%v): %v\n", name, name, c.Short)
if err := doc.GenMarkdown(c, body); err != nil {
panic(err)
}
}
// Remove "see also" sections. They list parent and child commands, which is not interesting for us.
cleanedBody := regexp.MustCompile(`(?s)### SEE ALSO\n.+?\n\n`).ReplaceAll(body.Bytes(), nil)
fmt.Printf("Commands:\n\n%s\n%s", cmdList, cleanedBody)
}

146
hack/go.mod Normal file
View file

@ -0,0 +1,146 @@
module github.com/edgelesssys/constellation/hack
go 1.18
replace (
k8s.io/api => k8s.io/api v0.24.0
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.24.0
k8s.io/apimachinery => k8s.io/apimachinery v0.24.0
k8s.io/apiserver => k8s.io/apiserver v0.24.0
k8s.io/cli-runtime => k8s.io/cli-runtime v0.24.0
k8s.io/client-go => k8s.io/client-go v0.24.0
k8s.io/cloud-provider => k8s.io/cloud-provider v0.24.0
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.24.0
k8s.io/code-generator => k8s.io/code-generator v0.24.0
k8s.io/component-base => k8s.io/component-base v0.24.0
k8s.io/component-helpers => k8s.io/component-helpers v0.24.0
k8s.io/controller-manager => k8s.io/controller-manager v0.24.0
k8s.io/cri-api => k8s.io/cri-api v0.24.0
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.24.0
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.24.0
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.24.0
k8s.io/kube-proxy => k8s.io/kube-proxy v0.24.0
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.24.0
k8s.io/kubectl => k8s.io/kubectl v0.24.0
k8s.io/kubelet => k8s.io/kubelet v0.24.0
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.24.0
k8s.io/metrics => k8s.io/metrics v0.24.0
k8s.io/mount-utils => k8s.io/mount-utils v0.24.0
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.24.0
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.24.0
k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.24.0
k8s.io/sample-controller => k8s.io/sample-controller v0.24.0
)
replace (
github.com/edgelesssys/constellation => ./..
github.com/nmiculinic/wg-quick-go v0.1.3 => github.com/katexochen/wg-quick-go v0.1.3-beta.1
)
require (
github.com/edgelesssys/constellation v1.0.0
github.com/google/go-tpm-tools v0.3.8
github.com/spf13/afero v1.8.2
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.1
google.golang.org/grpc v1.46.2
)
require (
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.5.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/kms v1.4.0 // indirect
cloud.google.com/go/resourcemanager v1.2.0 // indirect
cloud.google.com/go/storage v1.21.0 // indirect
github.com/Azure/azure-sdk-for-go v62.2.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v0.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v0.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.16.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.11.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.32.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.16.0 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 // indirect
github.com/aws/smithy-go v1.11.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/certificate-transparency-go v1.1.2 // indirect
github.com/google/go-attestation v0.4.4-0.20220404204839-8820d49b18d9 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/go-tpm v0.3.3 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/tink/go v1.6.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.2.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/nmiculinic/wg-quick-go v0.1.3 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68 // indirect
google.golang.org/api v0.73.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

1649
hack/go.sum Normal file

File diff suppressed because it is too large Load diff

119
hack/pcr-reader/README.md Normal file
View file

@ -0,0 +1,119 @@
# 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="
}
```
## Meaning of PCR values
An overview about what data is measured into the different registers can be found [in the TPM spec](https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf#%5B%7B%22num%22%3A157%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C33%2C400%2C0%5D).
We use the TPM and its PCRs to verify all nodes of a Constellation run with the same firmware and OS software.
### Azure trusted launch
PCR[0] measures the firmware volume (FV). Changes to FV also change PCR[0], making it unreliable for attestation.
PCR[6] measures the VM ID. This is unusable for cluster attestation for two reasons:
1. The Coordinator does not know the VM ID of nodes wanting to join the cluster, so it can not compute the expected PCR[6] for the joining VM
2. A user may attest any node of the cluster without knowing the VM ID
PCR[10] is used by Linux Integrity Measurement Architecture (IMA).
IMA creates runtime measurements based on a measurement policy (which is obsolete for Constellation, since we use dm-verity).
The first entry of the runtime measurements is the `boot_aggregate`. It is a SHA1 hash over PCRs 0 to 7.
As detailed earlier, PCR[6] is different for every VM in Azure, therefore PCR[10] will also be different since it includes PCR[6], meaning we can not use it for attestation.
IMA writing its measurements into PCR[10] can not be disabled without rebuilding the kernel.
### Azure flexible deployment and attestation (FDA)
With FDA CVMs measuring all of the firmware, it should be possible to use all PCRs for attestation since we know, and can choose, what firmware is running.
### GCP confidential VM
GCP uses confidential VMs based on AMD SEV-ES with a vTPM interface.
PCR[0] contains the measurement of a string marking the VM as using ADM SEV-ES.
All firmware measurements seem to be constant.

33
hack/pcr-reader/fetch_pcrs.sh Executable file
View file

@ -0,0 +1,33 @@
#!/bin/bash
set -o xtrace
trap 'terminate $?' ERR
terminate() {
echo "error: $1"
constellation terminate
exit 1
}
main() {
command -v constellation > /dev/null
command -v go > /dev/null
command -v jq > /dev/null
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

176
hack/pcr-reader/main.go Normal file
View file

@ -0,0 +1,176 @@
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/azure"
"github.com/edgelesssys/constellation/coordinator/attestation/gcp"
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
"github.com/edgelesssys/constellation/coordinator/oid"
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
"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")
timeout = flag.Duration("timeout", 2*time.Minute, "Wait this duration for the Coordinator to become available")
)
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(), *timeout)
defer cancel()
// wait for coordinator to come online
waiter := status.NewWaiter()
if err := waiter.InitializeValidators([]atls.Validator{
azure.NewValidator(map[uint32][]byte{}),
gcp.NewValidator(map[uint32][]byte{}),
}); err != nil {
log.Fatal(err)
}
if err := waiter.WaitFor(ctx, addr, state.AcceptingInit, state.ActivatingNodes, state.IsNode, state.NodeWaitingForClusterJoin); 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()) {
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)
}

View 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
wantErr bool
}{
"no certificates": {
rawCerts: nil,
wantErr: true,
},
"invalid certificate": {
rawCerts: [][]byte{
{0x1, 0x2, 0x3},
},
wantErr: true,
},
"no extension": {
rawCerts: [][]byte{
mustGenerateTestCert(t, &x509.Certificate{
SerialNumber: big.NewInt(123),
}),
},
wantErr: 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,
},
},
}),
},
wantErr: 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.wantErr {
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
wantErr 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())},
wantErr: 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()},
wantErr: 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.wantErr {
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
wantErr bool
}{
"invalid attestation document": {
attDocRaw: []byte{0x1, 0x2, 0x3},
wantErr: true,
},
"nil attestation": {
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{}),
wantErr: true,
},
"nil quotes": {
attDocRaw: mustMarshalAttDoc(t, vtpm.AttestationDocument{
Attestation: &attest.Attestation{},
}),
wantErr: 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},
},
},
},
},
},
}),
wantErr: 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"),
},
},
},
},
},
}),
wantErr: 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.wantErr {
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)))
}
}