2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2023-01-19 09:57:50 -05:00
/ *
# Measurements
Defines default expected measurements for the current release , as well as functions for comparing , updating and marshalling measurements .
This package should not include TPM specific code .
* /
2022-11-15 09:40:49 -05:00
package measurements
2022-05-12 04:15:00 -04:00
2022-05-16 12:54:25 -04:00
import (
2022-08-01 03:37:05 -04:00
"bytes"
"context"
2022-10-11 07:57:52 -04:00
"crypto/sha256"
"encoding/hex"
2022-11-24 04:57:58 -05:00
"encoding/json"
2023-02-07 06:56:25 -05:00
"errors"
2022-08-01 03:37:05 -04:00
"fmt"
2022-08-05 09:30:23 -04:00
"io"
2022-08-01 03:37:05 -04:00
"net/http"
"net/url"
2022-11-28 05:09:39 -05:00
"sort"
"strconv"
2022-05-16 12:54:25 -04:00
2023-06-09 09:41:02 -04:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
2022-11-24 04:57:58 -05:00
"github.com/google/go-tpm/tpmutil"
2023-01-02 07:33:56 -05:00
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
2022-11-24 04:57:58 -05:00
"gopkg.in/yaml.v3"
2023-05-22 08:59:28 -04:00
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
2022-05-16 12:54:25 -04:00
)
2022-05-12 04:15:00 -04:00
2023-05-30 03:38:58 -04:00
//go:generate measurement-generator
2022-11-24 04:57:58 -05:00
const (
// PCRIndexClusterID is a PCR we extend to mark the node as initialized.
// The value used to extend is a random generated 32 Byte value.
PCRIndexClusterID = tpmutil . Handle ( 15 )
// PCRIndexOwnerID is a PCR we extend to mark the node as initialized.
// The value used to extend is derived from Constellation's master key.
2023-06-01 06:33:06 -04:00
// TODO(daniel-weisse): move to stable, non-debug PCR before use.
2022-11-24 04:57:58 -05:00
PCRIndexOwnerID = tpmutil . Handle ( 16 )
2023-03-08 08:13:57 -05:00
// TDXIndexClusterID is the measurement used to mark the node as initialized.
// The value is the index of the RTMR + 1, since index 0 of the TDX measurements is reserved for MRTD.
TDXIndexClusterID = RTMRIndexClusterID + 1
// RTMRIndexClusterID is the RTMR we extend to mark the node as initialized.
RTMRIndexClusterID = 2
2023-03-10 05:22:56 -05:00
// PCRMeasurementLength holds the length for valid PCR measurements (SHA256).
PCRMeasurementLength = 32
// TDXMeasurementLength holds the length for valid TDX measurements (SHA384).
TDXMeasurementLength = 48
2022-11-24 04:57:58 -05:00
)
2022-10-21 06:24:18 -04:00
2022-11-24 04:57:58 -05:00
// M are Platform Configuration Register (PCR) values that make up the Measurements.
type M map [ uint32 ] Measurement
2022-05-16 12:54:25 -04:00
2023-05-22 08:59:28 -04:00
// ImageMeasurementsV2 is a struct to hold measurements for a specific image.
// .List contains measurements for all variants of the image.
type ImageMeasurementsV2 struct {
Version string ` json:"version" yaml:"version" `
Ref string ` json:"ref" yaml:"ref" `
Stream string ` json:"stream" yaml:"stream" `
List [ ] ImageMeasurementsV2Entry ` json:"list" yaml:"list" `
}
// ImageMeasurementsV2Entry is a struct to hold measurements for one variant of a specific image.
type ImageMeasurementsV2Entry struct {
CSP cloudprovider . Provider ` json:"csp" yaml:"csp" `
AttestationVariant string ` json:"attestationVariant" yaml:"attestationVariant" `
Measurements M ` json:"measurements" yaml:"measurements" `
}
2023-05-23 05:13:52 -04:00
// MergeImageMeasurementsV2 combines the image measurement entries from multiple sources into a single
// ImageMeasurementsV2 object.
func MergeImageMeasurementsV2 ( measurements ... ImageMeasurementsV2 ) ( ImageMeasurementsV2 , error ) {
if len ( measurements ) == 0 {
return ImageMeasurementsV2 { } , errors . New ( "no measurement objects specified" )
}
if len ( measurements ) == 1 {
return measurements [ 0 ] , nil
}
out := ImageMeasurementsV2 {
Version : measurements [ 0 ] . Version ,
Ref : measurements [ 0 ] . Ref ,
Stream : measurements [ 0 ] . Stream ,
List : [ ] ImageMeasurementsV2Entry { } ,
}
for _ , m := range measurements {
if m . Version != out . Version {
return ImageMeasurementsV2 { } , errors . New ( "version mismatch" )
}
if m . Ref != out . Ref {
return ImageMeasurementsV2 { } , errors . New ( "ref mismatch" )
}
if m . Stream != out . Stream {
return ImageMeasurementsV2 { } , errors . New ( "stream mismatch" )
}
out . List = append ( out . List , m . List ... )
}
sort . SliceStable ( out . List , func ( i , j int ) bool {
if out . List [ i ] . CSP != out . List [ j ] . CSP {
return out . List [ i ] . CSP < out . List [ j ] . CSP
}
return out . List [ i ] . AttestationVariant < out . List [ j ] . AttestationVariant
} )
return out , nil
}
2022-11-28 05:09:39 -05:00
// MarshalYAML returns the YAML encoding of m.
func ( m M ) MarshalYAML ( ) ( any , error ) {
// cast to prevent infinite recursion
node , err := encoder . NewEncoder ( map [ uint32 ] Measurement ( m ) ) . Marshal ( )
if err != nil {
return nil , err
}
// sort keys numerically
sort . Sort ( mYamlContent ( node . Content ) )
return node , nil
}
2022-08-01 03:37:05 -04:00
// FetchAndVerify fetches measurement and signature files via provided URLs,
2023-05-26 11:49:46 -04:00
// using client for download.
2022-10-11 07:57:52 -04:00
// The hash of the fetched measurements is returned.
2022-11-28 04:27:33 -05:00
func ( m * M ) FetchAndVerify (
2023-05-26 11:49:46 -04:00
ctx context . Context , client * http . Client , verifier cosignVerifier ,
measurementsURL , signatureURL * url . URL ,
version versionsapi . Version , csp cloudprovider . Provider , attestationVariant variant . Variant ,
) ( string , error ) {
publicKey , err := sigstore . CosignPublicKeyForVersion ( version )
if err != nil {
return "" , fmt . Errorf ( "getting public key: %w" , err )
}
return m . fetchAndVerify (
ctx , client , verifier ,
measurementsURL , signatureURL ,
publicKey , version , csp , attestationVariant ,
)
}
// 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 , verifier cosignVerifier ,
measurementsURL , signatureURL * url . URL , publicKey [ ] byte ,
version versionsapi . Version , csp cloudprovider . Provider , attestationVariant variant . Variant ,
2022-11-28 04:27:33 -05:00
) ( string , error ) {
2023-05-22 08:59:28 -04:00
measurementsRaw , err := getFromURL ( ctx , client , measurementsURL )
2022-08-01 03:37:05 -04:00
if err != nil {
2022-10-11 07:57:52 -04:00
return "" , fmt . Errorf ( "failed to fetch measurements: %w" , err )
2022-08-01 03:37:05 -04:00
}
signature , err := getFromURL ( ctx , client , signatureURL )
if err != nil {
2022-10-11 07:57:52 -04:00
return "" , fmt . Errorf ( "failed to fetch signature: %w" , err )
2022-08-01 03:37:05 -04:00
}
2023-05-26 11:49:46 -04:00
if err := verifier . VerifySignature ( measurementsRaw , signature , publicKey ) ; err != nil {
2022-10-11 07:57:52 -04:00
return "" , err
2022-08-01 03:37:05 -04:00
}
2022-11-24 04:57:58 -05:00
2023-05-22 08:59:28 -04:00
var measurements ImageMeasurementsV2
if err := json . Unmarshal ( measurementsRaw , & measurements ) ; err != nil {
return "" , err
2022-11-28 04:27:33 -05:00
}
2023-05-22 08:59:28 -04:00
if err := m . fromImageMeasurementsV2 ( measurements , version , csp , attestationVariant ) ; err != nil {
return "" , err
2022-11-28 04:27:33 -05:00
}
2023-05-22 08:59:28 -04:00
shaHash := sha256 . Sum256 ( measurementsRaw )
return hex . EncodeToString ( shaHash [ : ] ) , nil
}
2022-11-28 04:27:33 -05:00
2023-05-22 08:59:28 -04:00
// FetchNoVerify fetches measurement via provided URLs,
// using client for download. Measurements are not verified.
func ( m * M ) FetchNoVerify ( ctx context . Context , client * http . Client , measurementsURL * url . URL ,
version versionsapi . Version , csp cloudprovider . Provider , attestationVariant variant . Variant ,
) error {
measurementsRaw , err := getFromURL ( ctx , client , measurementsURL )
if err != nil {
2023-06-26 04:13:28 -04:00
return fmt . Errorf ( "failed to fetch measurements from %s: %w" , measurementsURL . String ( ) , err )
2023-05-22 08:59:28 -04:00
}
2022-10-11 07:57:52 -04:00
2023-05-22 08:59:28 -04:00
var measurements ImageMeasurementsV2
if err := json . Unmarshal ( measurementsRaw , & measurements ) ; err != nil {
return err
}
return m . fromImageMeasurementsV2 ( measurements , version , csp , attestationVariant )
2022-08-01 03:37:05 -04:00
}
// CopyFrom copies over all values from other. Overwriting existing values,
// but keeping not specified values untouched.
2022-11-24 04:57:58 -05:00
func ( m * M ) CopyFrom ( other M ) {
2022-08-01 03:37:05 -04:00
for idx := range other {
2022-11-24 04:57:58 -05:00
( * m ) [ idx ] = other [ idx ]
2022-08-01 03:37:05 -04:00
}
}
2023-05-04 04:51:28 -04:00
// Copy creates a new map with the same values as the original.
func ( m * M ) Copy ( ) M {
newM := make ( M , len ( * m ) )
for idx := range * m {
newM [ idx ] = ( * m ) [ idx ]
}
return newM
}
2022-11-15 09:40:49 -05:00
// EqualTo tests whether the provided other Measurements are equal to these
// measurements.
2022-11-24 04:57:58 -05:00
func ( m * M ) EqualTo ( other M ) bool {
if len ( * m ) != len ( other ) {
2022-11-15 09:40:49 -05:00
return false
}
2022-11-24 04:57:58 -05:00
for k , v := range * m {
otherExpected := other [ k ] . Expected
2023-03-08 08:13:57 -05:00
if ! bytes . Equal ( v . Expected , otherExpected ) {
2022-11-24 04:57:58 -05:00
return false
}
2023-03-22 06:47:39 -04:00
if v . ValidationOpt != other [ k ] . ValidationOpt {
2022-11-15 09:40:49 -05:00
return false
}
}
return true
}
2022-11-24 04:57:58 -05:00
// GetEnforced returns a list of all enforced Measurements,
// i.e. all Measurements that are not marked as WarnOnly.
func ( m * M ) GetEnforced ( ) [ ] uint32 {
var enforced [ ] uint32
for idx , measurement := range * m {
2023-03-29 04:52:57 -04:00
if measurement . ValidationOpt == Enforce {
2022-11-24 04:57:58 -05:00
enforced = append ( enforced , idx )
}
}
return enforced
}
// SetEnforced sets the WarnOnly flag to true for all Measurements
// that are NOT included in the provided list of enforced measurements.
func ( m * M ) SetEnforced ( enforced [ ] uint32 ) error {
newM := make ( M )
// set all measurements to warn only
for idx , measurement := range * m {
newM [ idx ] = Measurement {
2023-03-22 06:47:39 -04:00
Expected : measurement . Expected ,
ValidationOpt : WarnOnly ,
2022-11-24 04:57:58 -05:00
}
}
2022-05-12 04:15:00 -04:00
2022-11-24 04:57:58 -05:00
// set enforced measurements from list
for _ , idx := range enforced {
measurement , ok := newM [ idx ]
if ! ok {
return fmt . Errorf ( "measurement %d not in list, but set to enforced" , idx )
}
2023-03-22 06:47:39 -04:00
measurement . ValidationOpt = Enforce
2022-11-24 04:57:58 -05:00
newM [ idx ] = measurement
2022-05-12 04:15:00 -04:00
}
2022-11-24 04:57:58 -05:00
* m = newM
return nil
2022-05-12 04:15:00 -04:00
}
2023-03-08 08:13:57 -05:00
// UnmarshalJSON unmarshals measurements from json.
// This function enforces all measurements to be of equal length.
func ( m * M ) UnmarshalJSON ( b [ ] byte ) error {
newM := make ( map [ uint32 ] Measurement )
if err := json . Unmarshal ( b , & newM ) ; err != nil {
return err
}
// check if all measurements are of equal length
if err := checkLength ( newM ) ; err != nil {
return err
}
* m = newM
return nil
}
// UnmarshalYAML unmarshals measurements from yaml.
// This function enforces all measurements to be of equal length.
func ( m * M ) UnmarshalYAML ( unmarshal func ( any ) error ) error {
newM := make ( map [ uint32 ] Measurement )
if err := unmarshal ( & newM ) ; err != nil {
return err
}
// check if all measurements are of equal length
if err := checkLength ( newM ) ; err != nil {
return err
}
* m = newM
return nil
}
2023-05-22 08:59:28 -04:00
func ( m * M ) fromImageMeasurementsV2 (
measurements ImageMeasurementsV2 , wantVersion versionsapi . Version ,
csp cloudprovider . Provider , attestationVariant variant . Variant ,
) error {
gotVersion := versionsapi . Version {
Ref : measurements . Ref ,
Stream : measurements . Stream ,
Version : measurements . Version ,
Kind : versionsapi . VersionKindImage ,
}
if ! wantVersion . Equal ( gotVersion ) {
return fmt . Errorf ( "invalid measurement metadata: version mismatch: expected %s, got %s" , wantVersion . ShortPath ( ) , gotVersion . ShortPath ( ) )
}
// find measurements for requested image in list
var measurementsEntry ImageMeasurementsV2Entry
var found bool
for _ , entry := range measurements . List {
gotCSP := entry . CSP
if gotCSP != csp {
continue
}
gotAttestationVariant , err := variant . FromString ( entry . AttestationVariant )
if err != nil {
continue
}
if gotAttestationVariant == nil || attestationVariant == nil {
continue
}
if ! gotAttestationVariant . Equal ( attestationVariant ) {
continue
}
measurementsEntry = entry
found = true
break
}
if ! found {
return fmt . Errorf ( "invalid measurement metadata: no measurements found for csp %s, attestationVariant %s and image %s" , csp . String ( ) , attestationVariant , wantVersion . ShortPath ( ) )
}
* m = measurementsEntry . Measurements
return nil
}
2022-11-24 04:57:58 -05:00
// Measurement wraps expected PCR value and whether it is enforced.
type Measurement struct {
// Expected measurement value.
2023-03-08 08:13:57 -05:00
// 32 bytes for vTPM attestation, 48 for TDX.
Expected [ ] byte ` json:"expected" yaml:"expected" `
2023-03-22 06:47:39 -04:00
// ValidationOpt indicates how measurement mismatches should be handled.
ValidationOpt MeasurementValidationOption ` json:"warnOnly" yaml:"warnOnly" `
2022-11-24 04:57:58 -05:00
}
2023-03-22 06:47:39 -04:00
// MeasurementValidationOption indicates how measurement mismatches should be handled.
type MeasurementValidationOption bool
const (
// WarnOnly will only result in a warning in case of a mismatching measurement.
WarnOnly MeasurementValidationOption = true
// Enforce will result in an error in case of a mismatching measurement, and operation will be aborted.
Enforce MeasurementValidationOption = false
)
2022-11-24 04:57:58 -05:00
// UnmarshalJSON reads a Measurement either as json object,
// or as a simple hex or base64 encoded string.
func ( m * Measurement ) UnmarshalJSON ( b [ ] byte ) error {
var eM encodedMeasurement
if err := json . Unmarshal ( b , & eM ) ; err != nil {
2022-11-25 06:08:24 -05:00
// Unmarshalling failed, Measurement might be a simple string instead of Measurement struct.
// These values will always be enforced.
2022-11-24 04:57:58 -05:00
if legacyErr := json . Unmarshal ( b , & eM . Expected ) ; legacyErr != nil {
2023-02-07 06:56:25 -05:00
return errors . Join (
2022-11-24 04:57:58 -05:00
err ,
fmt . Errorf ( "trying legacy format: %w" , legacyErr ) ,
)
}
}
if err := m . unmarshal ( eM ) ; err != nil {
return fmt . Errorf ( "unmarshalling json: %w" , err )
}
return nil
}
// MarshalJSON writes out a Measurement with Expected encoded as a hex string.
func ( m Measurement ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( encodedMeasurement {
Expected : hex . EncodeToString ( m . Expected [ : ] ) ,
2023-03-22 06:47:39 -04:00
WarnOnly : m . ValidationOpt ,
2022-11-24 04:57:58 -05:00
} )
}
// UnmarshalYAML reads a Measurement either as yaml object,
// or as a simple hex or base64 encoded string.
func ( m * Measurement ) UnmarshalYAML ( unmarshal func ( any ) error ) error {
var eM encodedMeasurement
if err := unmarshal ( & eM ) ; err != nil {
2022-11-25 06:08:24 -05:00
// Unmarshalling failed, Measurement might be a simple string instead of Measurement struct.
// These values will always be enforced.
2022-11-24 04:57:58 -05:00
if legacyErr := unmarshal ( & eM . Expected ) ; legacyErr != nil {
2023-02-07 06:56:25 -05:00
return errors . Join (
2022-11-24 04:57:58 -05:00
err ,
fmt . Errorf ( "trying legacy format: %w" , legacyErr ) ,
)
}
2022-05-12 04:15:00 -04:00
}
2022-11-24 04:57:58 -05:00
if err := m . unmarshal ( eM ) ; err != nil {
return fmt . Errorf ( "unmarshalling yaml: %w" , err )
}
return nil
}
// MarshalYAML writes out a Measurement with Expected encoded as a hex string.
func ( m Measurement ) MarshalYAML ( ) ( any , error ) {
return encodedMeasurement {
Expected : hex . EncodeToString ( m . Expected [ : ] ) ,
2023-03-22 06:47:39 -04:00
WarnOnly : m . ValidationOpt ,
2022-11-24 04:57:58 -05:00
} , nil
}
// unmarshal parses a hex or base64 encoded Measurement.
func ( m * Measurement ) unmarshal ( eM encodedMeasurement ) error {
expected , err := hex . DecodeString ( eM . Expected )
if err != nil {
2023-06-01 06:33:06 -04:00
return fmt . Errorf ( "decoding measurement: %w" , err )
2022-05-12 04:15:00 -04:00
}
2022-11-24 04:57:58 -05:00
2023-03-08 08:13:57 -05:00
if len ( expected ) != 32 && len ( expected ) != 48 {
2022-11-24 04:57:58 -05:00
return fmt . Errorf ( "invalid measurement: invalid length: %d" , len ( expected ) )
}
2023-03-08 08:13:57 -05:00
m . Expected = expected
2023-03-22 06:47:39 -04:00
m . ValidationOpt = eM . WarnOnly
2022-11-24 04:57:58 -05:00
2022-05-12 04:15:00 -04:00
return nil
}
2022-08-01 03:37:05 -04:00
2023-03-10 05:33:06 -05:00
// WithAllBytes returns a measurement value where all bytes are set to b. Takes a dynamic length as input.
// Expected are either 32 bytes (PCRMeasurementLength) or 48 bytes (TDXMeasurementLength).
// Over inputs are possible in this function, but potentially rejected elsewhere.
func WithAllBytes ( b byte , validationOpt MeasurementValidationOption , len int ) Measurement {
2022-11-24 04:57:58 -05:00
return Measurement {
2023-03-10 05:33:06 -05:00
Expected : bytes . Repeat ( [ ] byte { b } , len ) ,
2023-03-22 06:47:39 -04:00
ValidationOpt : validationOpt ,
2022-11-24 04:57:58 -05:00
}
}
// PlaceHolderMeasurement returns a measurement with placeholder values for Expected.
2023-05-04 04:51:28 -04:00
func PlaceHolderMeasurement ( len int ) Measurement {
2022-11-24 04:57:58 -05:00
return Measurement {
2023-05-04 04:51:28 -04:00
Expected : bytes . Repeat ( [ ] byte { 0x12 , 0x34 } , len / 2 ) ,
2023-03-22 06:47:39 -04:00
ValidationOpt : Enforce ,
2022-11-24 04:57:58 -05:00
}
}
2023-05-04 04:51:28 -04:00
// DefaultsFor provides the default measurements for given cloud provider.
func DefaultsFor ( provider cloudprovider . Provider , attestationVariant variant . Variant ) M {
switch {
case provider == cloudprovider . AWS && attestationVariant == variant . AWSNitroTPM { } :
return aws_AWSNitroTPM . Copy ( )
2023-06-09 09:41:02 -04:00
case provider == cloudprovider . AWS && attestationVariant == variant . AWSSEVSNP { } :
return aws_AWSSEVSNP . Copy ( )
2023-05-04 04:51:28 -04:00
case provider == cloudprovider . Azure && attestationVariant == variant . AzureSEVSNP { } :
return azure_AzureSEVSNP . Copy ( )
case provider == cloudprovider . Azure && attestationVariant == variant . AzureTrustedLaunch { } :
return azure_AzureTrustedLaunch . Copy ( )
case provider == cloudprovider . GCP && attestationVariant == variant . GCPSEVES { } :
return gcp_GCPSEVES . Copy ( )
case provider == cloudprovider . QEMU && attestationVariant == variant . QEMUTDX { } :
return qemu_QEMUTDX . Copy ( )
case provider == cloudprovider . QEMU && attestationVariant == variant . QEMUVTPM { } :
return qemu_QEMUVTPM . Copy ( )
default :
return nil
}
}
2023-03-08 08:13:57 -05:00
func checkLength ( m map [ uint32 ] Measurement ) error {
var length int
for idx , measurement := range m {
if length == 0 {
length = len ( measurement . Expected )
} else if len ( measurement . Expected ) != length {
return fmt . Errorf ( "inconsistent measurement length: index %d: expected %d, got %d" , idx , length , len ( measurement . Expected ) )
}
}
return nil
}
2022-11-24 04:57:58 -05:00
type encodedMeasurement struct {
2023-03-22 06:47:39 -04:00
Expected string ` json:"expected" yaml:"expected" `
WarnOnly MeasurementValidationOption ` json:"warnOnly" yaml:"warnOnly" `
2022-11-24 04:57:58 -05:00
}
2022-11-28 05:09:39 -05:00
// mYamlContent is the Content of a yaml.Node encoding of an M. It implements sort.Interface.
// The slice is filled like {key1, value1, key2, value2, ...}.
type mYamlContent [ ] * yaml . Node
func ( c mYamlContent ) Len ( ) int {
return len ( c ) / 2
}
func ( c mYamlContent ) Less ( i , j int ) bool {
lhs , err := strconv . Atoi ( c [ 2 * i ] . Value )
if err != nil {
panic ( err )
}
rhs , err := strconv . Atoi ( c [ 2 * j ] . Value )
if err != nil {
panic ( err )
}
return lhs < rhs
}
func ( c mYamlContent ) Swap ( i , j int ) {
// The slice is filled like {key1, value1, key2, value2, ...}.
// We need to swap both key and value.
c [ 2 * i ] , c [ 2 * j ] = c [ 2 * j ] , c [ 2 * i ]
c [ 2 * i + 1 ] , c [ 2 * j + 1 ] = c [ 2 * j + 1 ] , c [ 2 * i + 1 ]
}
2023-05-26 11:49:46 -04:00
2023-06-01 07:55:46 -04:00
// getFromURL fetches the content from the given URL and returns the content as a byte slice.
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 nil , err
}
resp , err := client . Do ( req )
if err != nil {
return nil , err
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "http status code: %d" , resp . StatusCode )
}
return io . ReadAll ( resp . Body )
}
2023-05-26 11:49:46 -04:00
type cosignVerifier interface {
VerifySignature ( content , signature , publicKey [ ] byte ) error
}