mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-19 04:44:42 -05:00
bb76a4e4c8
* refactor measurements to use consistent types and less byte pushing * refactor: only rely on a single multierr dependency * extend config creation with envar support * document changes Signed-off-by: Fabian Kammel <fk@edgeless.systems>
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package measurements
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
|
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// M are Platform Configuration Register (PCR) values that make up the Measurements.
|
|
type M map[uint32][]byte
|
|
|
|
// PCRWithAllBytes returns a PCR value where all 32 bytes are set to b.
|
|
func PCRWithAllBytes(b byte) []byte {
|
|
return bytes.Repeat([]byte{b}, 32)
|
|
}
|
|
|
|
// DefaultsFor provides the default measurements for given cloud provider.
|
|
func DefaultsFor(provider cloudprovider.Provider) M {
|
|
switch provider {
|
|
case cloudprovider.AWS:
|
|
return M{
|
|
11: PCRWithAllBytes(0x00),
|
|
12: PCRWithAllBytes(0x00),
|
|
13: PCRWithAllBytes(0x00),
|
|
uint32(vtpm.PCRIndexClusterID): PCRWithAllBytes(0x00),
|
|
}
|
|
case cloudprovider.Azure:
|
|
return M{
|
|
11: PCRWithAllBytes(0x00),
|
|
12: PCRWithAllBytes(0x00),
|
|
13: PCRWithAllBytes(0x00),
|
|
uint32(vtpm.PCRIndexClusterID): PCRWithAllBytes(0x00),
|
|
}
|
|
case cloudprovider.GCP:
|
|
return M{
|
|
0: {0x0F, 0x35, 0xC2, 0x14, 0x60, 0x8D, 0x93, 0xC7, 0xA6, 0xE6, 0x8A, 0xE7, 0x35, 0x9B, 0x4A, 0x8B, 0xE5, 0xA0, 0xE9, 0x9E, 0xEA, 0x91, 0x07, 0xEC, 0xE4, 0x27, 0xC4, 0xDE, 0xA4, 0xE4, 0x39, 0xCF},
|
|
11: PCRWithAllBytes(0x00),
|
|
12: PCRWithAllBytes(0x00),
|
|
13: PCRWithAllBytes(0x00),
|
|
uint32(vtpm.PCRIndexClusterID): PCRWithAllBytes(0x00),
|
|
}
|
|
case cloudprovider.QEMU:
|
|
return M{
|
|
11: PCRWithAllBytes(0x00),
|
|
12: PCRWithAllBytes(0x00),
|
|
13: PCRWithAllBytes(0x00),
|
|
uint32(vtpm.PCRIndexClusterID): PCRWithAllBytes(0x00),
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// FetchAndVerify fetches measurement and signature files via provided URLs,
|
|
// using client for download. The publicKey is used to verify the measurements.
|
|
// The hash of the fetched measurements is returned.
|
|
func (m *M) FetchAndVerify(ctx context.Context, client *http.Client, measurementsURL *url.URL, signatureURL *url.URL, publicKey []byte) (string, error) {
|
|
measurements, err := getFromURL(ctx, client, measurementsURL)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to fetch measurements: %w", err)
|
|
}
|
|
signature, err := getFromURL(ctx, client, signatureURL)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to fetch signature: %w", err)
|
|
}
|
|
if err := sigstore.VerifySignature(measurements, signature, publicKey); err != nil {
|
|
return "", err
|
|
}
|
|
if err := yaml.NewDecoder(bytes.NewReader(measurements)).Decode(&m); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
shaHash := sha256.Sum256(measurements)
|
|
|
|
return hex.EncodeToString(shaHash[:]), nil
|
|
}
|
|
|
|
// CopyFrom copies over all values from other. Overwriting existing values,
|
|
// but keeping not specified values untouched.
|
|
func (m M) CopyFrom(other M) {
|
|
for idx := range other {
|
|
m[idx] = other[idx]
|
|
}
|
|
}
|
|
|
|
// EqualTo tests whether the provided other Measurements are equal to these
|
|
// measurements.
|
|
func (m M) EqualTo(other M) bool {
|
|
if len(m) != len(other) {
|
|
return false
|
|
}
|
|
for k, v := range m {
|
|
if !bytes.Equal(v, other[k]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MarshalYAML overwrites the default behaviour of writing out []byte not as
|
|
// single bytes, but as a single base64 encoded string.
|
|
func (m M) MarshalYAML() (any, error) {
|
|
base64Map := make(map[uint32]string)
|
|
|
|
for key, value := range m {
|
|
base64Map[key] = base64.StdEncoding.EncodeToString(value[:])
|
|
}
|
|
|
|
return base64Map, nil
|
|
}
|
|
|
|
// UnmarshalYAML overwrites the default behaviour of reading []byte not as
|
|
// single bytes, but as a single base64 encoded string.
|
|
func (m *M) UnmarshalYAML(unmarshal func(any) error) error {
|
|
base64Map := make(map[uint32]string)
|
|
err := unmarshal(base64Map)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*m = make(M)
|
|
for key, value := range base64Map {
|
|
measurement, err := base64.StdEncoding.DecodeString(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
(*m)[key] = measurement
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getFromURL(ctx context.Context, client *http.Client, sourceURL *url.URL) ([]byte, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL.String(), http.NoBody)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return []byte{}, fmt.Errorf("http status code: %d", resp.StatusCode)
|
|
}
|
|
content, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
return content, nil
|
|
}
|