mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-24 06:11:02 -05:00
attestation: add snp package
The package holds code shared between SNP-based attestation implementations on AWS and Azure .
This commit is contained in:
parent
635a5d2c0a
commit
5ce55e3449
@ -15,11 +15,11 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//internal/attestation",
|
"//internal/attestation",
|
||||||
"//internal/attestation/idkeydigest",
|
"//internal/attestation/idkeydigest",
|
||||||
|
"//internal/attestation/snp",
|
||||||
"//internal/attestation/variant",
|
"//internal/attestation/variant",
|
||||||
"//internal/attestation/vtpm",
|
"//internal/attestation/vtpm",
|
||||||
"//internal/cloud/azure",
|
"//internal/cloud/azure",
|
||||||
"//internal/config",
|
"//internal/config",
|
||||||
"//internal/constants",
|
|
||||||
"@com_github_edgelesssys_go_azguestattestation//maa",
|
"@com_github_edgelesssys_go_azguestattestation//maa",
|
||||||
"@com_github_google_go_sev_guest//abi",
|
"@com_github_google_go_sev_guest//abi",
|
||||||
"@com_github_google_go_sev_guest//kds",
|
"@com_github_google_go_sev_guest//kds",
|
||||||
@ -48,9 +48,10 @@ go_test(
|
|||||||
}),
|
}),
|
||||||
deps = [
|
deps = [
|
||||||
"//internal/attestation",
|
"//internal/attestation",
|
||||||
"//internal/attestation/azure/snp/testdata",
|
|
||||||
"//internal/attestation/idkeydigest",
|
"//internal/attestation/idkeydigest",
|
||||||
"//internal/attestation/simulator",
|
"//internal/attestation/simulator",
|
||||||
|
"//internal/attestation/snp",
|
||||||
|
"//internal/attestation/snp/testdata",
|
||||||
"//internal/attestation/vtpm",
|
"//internal/attestation/vtpm",
|
||||||
"//internal/config",
|
"//internal/config",
|
||||||
"//internal/logger",
|
"//internal/logger",
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||||
@ -65,7 +66,7 @@ func (i *Issuer) getInstanceInfo(ctx context.Context, tpm io.ReadWriteCloser, us
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceInfo := azureInstanceInfo{
|
instanceInfo := snp.InstanceInfo{
|
||||||
VCEK: params.VcekCert,
|
VCEK: params.VcekCert,
|
||||||
CertChain: params.VcekChain,
|
CertChain: params.VcekChain,
|
||||||
AttestationReport: params.SNPReport,
|
AttestationReport: params.SNPReport,
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||||
tpmclient "github.com/google/go-tpm-tools/client"
|
tpmclient "github.com/google/go-tpm-tools/client"
|
||||||
"github.com/google/go-tpm/legacy/tpm2"
|
"github.com/google/go-tpm/legacy/tpm2"
|
||||||
@ -99,7 +100,7 @@ func TestGetSNPAttestation(t *testing.T) {
|
|||||||
assert.Equal(data, maa.gotTokenData)
|
assert.Equal(data, maa.gotTokenData)
|
||||||
}
|
}
|
||||||
|
|
||||||
var instanceInfo azureInstanceInfo
|
var instanceInfo snp.InstanceInfo
|
||||||
err = json.Unmarshal(attestationJSON, &instanceInfo)
|
err = json.Unmarshal(attestationJSON, &instanceInfo)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
|
@ -15,16 +15,15 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
|
||||||
"github.com/google/go-sev-guest/abi"
|
"github.com/google/go-sev-guest/abi"
|
||||||
"github.com/google/go-sev-guest/kds"
|
"github.com/google/go-sev-guest/kds"
|
||||||
spb "github.com/google/go-sev-guest/proto/sevsnp"
|
spb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||||
@ -79,7 +78,7 @@ func NewValidator(cfg *config.AzureSEVSNP, log attestation.Logger) *Validator {
|
|||||||
log = nopAttestationLogger{}
|
log = nopAttestationLogger{}
|
||||||
}
|
}
|
||||||
v := &Validator{
|
v := &Validator{
|
||||||
hclValidator: &azureInstanceInfo{},
|
hclValidator: &attestationKey{},
|
||||||
maa: newMAAClient(),
|
maa: newMAAClient(),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
log: log,
|
log: log,
|
||||||
@ -106,18 +105,15 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
|||||||
trustedArk := (*x509.Certificate)(&v.config.AMDRootKey) // ARK, specified in the Constellation config
|
trustedArk := (*x509.Certificate)(&v.config.AMDRootKey) // ARK, specified in the Constellation config
|
||||||
|
|
||||||
// fallback certificates, used if not present in THIM response.
|
// fallback certificates, used if not present in THIM response.
|
||||||
cachedCerts := sevSnpCerts{
|
cachedCerts := snp.NewCertificateChain(trustedAsk, trustedArk)
|
||||||
ask: trustedAsk,
|
|
||||||
ark: trustedArk,
|
|
||||||
}
|
|
||||||
|
|
||||||
// transform the instanceInfo received from Microsoft into a verifiable attestation report format.
|
// transform the instanceInfo received from Microsoft into a verifiable attestation report format.
|
||||||
var instanceInfo azureInstanceInfo
|
var instanceInfo snp.InstanceInfo
|
||||||
if err := json.Unmarshal(attDoc.InstanceInfo, &instanceInfo); err != nil {
|
if err := json.Unmarshal(attDoc.InstanceInfo, &instanceInfo); err != nil {
|
||||||
return nil, fmt.Errorf("unmarshalling instanceInfo: %w", err)
|
return nil, fmt.Errorf("unmarshalling instanceInfo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
att, err := instanceInfo.attestationWithCerts(v.log, v.getter, cachedCerts)
|
att, err := instanceInfo.AttestationWithCerts(v.log, v.getter, cachedCerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing attestation report: %w", err)
|
return nil, fmt.Errorf("parsing attestation report: %w", err)
|
||||||
}
|
}
|
||||||
@ -192,7 +188,7 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = v.hclValidator.validateAk(instanceInfo.RuntimeData, att.Report.ReportData, pubArea.RSAParameters); err != nil {
|
if err = v.hclValidator.validate(instanceInfo.RuntimeData, att.Report.ReportData, pubArea.RSAParameters); err != nil {
|
||||||
return nil, fmt.Errorf("validating HCLAkPub: %w", err)
|
return nil, fmt.Errorf("validating HCLAkPub: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,201 +235,17 @@ func (v *Validator) checkIDKeyDigest(ctx context.Context, report *spb.Attestatio
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// azureInstanceInfo contains the necessary information to establish trust in
|
type attestationKey struct {
|
||||||
// an Azure CVM.
|
PublicPart []akPub `json:"keys"`
|
||||||
type azureInstanceInfo struct {
|
|
||||||
// VCEK is the PEM-encoded VCEK certificate for the attestation report.
|
|
||||||
VCEK []byte
|
|
||||||
// CertChain is the PEM-encoded certificate chain for the attestation report.
|
|
||||||
CertChain []byte
|
|
||||||
// AttestationReport is the attestation report from the vTPM (NVRAM) of the CVM.
|
|
||||||
AttestationReport []byte
|
|
||||||
// RuntimeData is the Azure runtime data from the vTPM (NVRAM) of the CVM.
|
|
||||||
RuntimeData []byte
|
|
||||||
// MAAToken is the token of the MAA for the attestation report, used as a fallback
|
|
||||||
// if the IDKeyDigest cannot be verified.
|
|
||||||
MAAToken string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// attestationWithCerts returns a formatted version of the attestation report and its certificates from the instanceInfo.
|
// validate validates that the attestation key from the TPM is trustworthy. The steps are:
|
||||||
// Certificates are retrieved in the following precedence:
|
|
||||||
// 1. ASK or ARK from THIM
|
|
||||||
// 2. ASK or ARK from fallbackCerts
|
|
||||||
// 3. ASK or ARK from AMD KDS.
|
|
||||||
func (a *azureInstanceInfo) attestationWithCerts(logger attestation.Logger, getter trust.HTTPSGetter,
|
|
||||||
fallbackCerts sevSnpCerts,
|
|
||||||
) (*spb.Attestation, error) {
|
|
||||||
report, err := abi.ReportToProto(a.AttestationReport)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("converting report to proto: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Product info as reported through CPUID[EAX=1]
|
|
||||||
sevProduct := &spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 0} // Milan-B0
|
|
||||||
productName := kds.ProductString(sevProduct)
|
|
||||||
|
|
||||||
att := &spb.Attestation{
|
|
||||||
Report: report,
|
|
||||||
CertificateChain: &spb.CertificateChain{},
|
|
||||||
Product: sevProduct,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the VCEK certificate is present, parse it and format it.
|
|
||||||
vcek, err := a.parseVCEK()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("Error parsing VCEK: %v", err)
|
|
||||||
}
|
|
||||||
if vcek != nil {
|
|
||||||
att.CertificateChain.VcekCert = vcek.Raw
|
|
||||||
} else {
|
|
||||||
// Otherwise, retrieve it from AMD KDS.
|
|
||||||
logger.Infof("VCEK certificate not present, falling back to retrieving it from AMD KDS")
|
|
||||||
vcekURL := kds.VCEKCertURL(productName, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb()))
|
|
||||||
vcek, err := getter.Get(vcekURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("retrieving VCEK certificate from AMD KDS: %w", err)
|
|
||||||
}
|
|
||||||
att.CertificateChain.VcekCert = vcek
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the certificate chain from THIM is present, parse it and format it.
|
|
||||||
ask, ark, err := a.parseCertChain()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("Error parsing certificate chain: %v", err)
|
|
||||||
}
|
|
||||||
if ask != nil {
|
|
||||||
logger.Infof("Using ASK certificate from Azure THIM")
|
|
||||||
att.CertificateChain.AskCert = ask.Raw
|
|
||||||
}
|
|
||||||
if ark != nil {
|
|
||||||
logger.Infof("Using ARK certificate from Azure THIM")
|
|
||||||
att.CertificateChain.ArkCert = ark.Raw
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a cached ASK or an ARK from the Constellation config is present, use it.
|
|
||||||
if att.CertificateChain.AskCert == nil && fallbackCerts.ask != nil {
|
|
||||||
logger.Infof("Using cached ASK certificate")
|
|
||||||
att.CertificateChain.AskCert = fallbackCerts.ask.Raw
|
|
||||||
}
|
|
||||||
if att.CertificateChain.ArkCert == nil && fallbackCerts.ark != nil {
|
|
||||||
logger.Infof("Using ARK certificate from %s", constants.ConfigFilename)
|
|
||||||
att.CertificateChain.ArkCert = fallbackCerts.ark.Raw
|
|
||||||
}
|
|
||||||
// Otherwise, retrieve it from AMD KDS.
|
|
||||||
if att.CertificateChain.AskCert == nil || att.CertificateChain.ArkCert == nil {
|
|
||||||
logger.Infof(
|
|
||||||
"Certificate chain not fully present (ARK present: %t, ASK present: %t), falling back to retrieving it from AMD KDS",
|
|
||||||
(att.CertificateChain.ArkCert != nil),
|
|
||||||
(att.CertificateChain.AskCert != nil),
|
|
||||||
)
|
|
||||||
kdsCertChain, err := trust.GetProductChain(productName, abi.VcekReportSigner, getter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("retrieving certificate chain from AMD KDS: %w", err)
|
|
||||||
}
|
|
||||||
if att.CertificateChain.AskCert == nil && kdsCertChain.Ask != nil {
|
|
||||||
logger.Infof("Using ASK certificate from AMD KDS")
|
|
||||||
att.CertificateChain.AskCert = kdsCertChain.Ask.Raw
|
|
||||||
}
|
|
||||||
if att.CertificateChain.ArkCert == nil && kdsCertChain.Ask != nil {
|
|
||||||
logger.Infof("Using ARK certificate from AMD KDS")
|
|
||||||
att.CertificateChain.ArkCert = kdsCertChain.Ark.Raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return att, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type sevSnpCerts struct {
|
|
||||||
ask *x509.Certificate
|
|
||||||
ark *x509.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCertChain parses the certificate chain from the instanceInfo into x509-formatted ASK and ARK certificates.
|
|
||||||
// If less than 2 certificates are present, only the present certificate is returned.
|
|
||||||
// If more than 2 certificates are present, an error is returned.
|
|
||||||
func (a *azureInstanceInfo) parseCertChain() (ask, ark *x509.Certificate, retErr error) {
|
|
||||||
rest := bytes.TrimSpace(a.CertChain)
|
|
||||||
|
|
||||||
i := 1
|
|
||||||
var block *pem.Block
|
|
||||||
for block, rest = pem.Decode(rest); block != nil; block, rest = pem.Decode(rest) {
|
|
||||||
if i > 2 {
|
|
||||||
retErr = fmt.Errorf("parse certificate %d: more than 2 certificates in chain", i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.Type != "CERTIFICATE" {
|
|
||||||
retErr = fmt.Errorf("parse certificate %d: expected PEM block type 'CERTIFICATE', got '%s'", i, block.Type)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
retErr = fmt.Errorf("parse certificate %d: %w", i, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf
|
|
||||||
// Table 6 and 7
|
|
||||||
switch cert.Subject.CommonName {
|
|
||||||
case "SEV-Milan":
|
|
||||||
ask = cert
|
|
||||||
case "ARK-Milan":
|
|
||||||
ark = cert
|
|
||||||
default:
|
|
||||||
retErr = fmt.Errorf("parse certificate %d: unexpected subject CN %s", i, cert.Subject.CommonName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case i == 1:
|
|
||||||
retErr = fmt.Errorf("no PEM blocks found")
|
|
||||||
case len(rest) != 0:
|
|
||||||
retErr = fmt.Errorf("remaining PEM block is not a valid certificate: %s", rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseVCEK parses the VCEK certificate from the instanceInfo into an x509-formatted certificate.
|
|
||||||
// If the VCEK certificate is not present, nil is returned.
|
|
||||||
func (a *azureInstanceInfo) parseVCEK() (*x509.Certificate, error) {
|
|
||||||
newlinesTrimmed := bytes.TrimSpace(a.VCEK)
|
|
||||||
if len(newlinesTrimmed) == 0 {
|
|
||||||
// VCEK is not present.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block, rest := pem.Decode(newlinesTrimmed)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("no PEM blocks found")
|
|
||||||
}
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return nil, fmt.Errorf("received more data than expected")
|
|
||||||
}
|
|
||||||
if block.Type != "CERTIFICATE" {
|
|
||||||
return nil, fmt.Errorf("expected PEM block type 'CERTIFICATE', got '%s'", block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
vcek, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing VCEK certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vcek, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateAk validates that the attestation key from the TPM is trustworthy. The steps are:
|
|
||||||
// 1. runtime data read from the TPM has the same sha256 digest as reported in `report_data` of the SNP report.
|
// 1. runtime data read from the TPM has the same sha256 digest as reported in `report_data` of the SNP report.
|
||||||
// 2. modulus reported in runtime data matches modulus from key at idx 0x81000003.
|
// 2. modulus reported in runtime data matches modulus from key at idx 0x81000003.
|
||||||
// 3. exponent reported in runtime data matches exponent from key at idx 0x81000003.
|
// 3. exponent reported in runtime data matches exponent from key at idx 0x81000003.
|
||||||
// The function is currently tested manually on a Azure Ubuntu CVM.
|
// The function is currently tested manually on a Azure Ubuntu CVM.
|
||||||
func (a *azureInstanceInfo) validateAk(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error {
|
func (a *attestationKey) validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error {
|
||||||
var runtimeData runtimeData
|
if err := json.Unmarshal(runtimeDataRaw, a); err != nil {
|
||||||
if err := json.Unmarshal(runtimeDataRaw, &runtimeData); err != nil {
|
|
||||||
return fmt.Errorf("unmarshalling json: %w", err)
|
return fmt.Errorf("unmarshalling json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,10 +257,10 @@ func (a *azureInstanceInfo) validateAk(runtimeDataRaw []byte, reportData []byte,
|
|||||||
return errors.New("unexpected runtimeData digest in TPM")
|
return errors.New("unexpected runtimeData digest in TPM")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(runtimeData.Keys) < 1 {
|
if len(a.PublicPart) < 1 {
|
||||||
return errors.New("did not receive any keys in runtime data")
|
return errors.New("did not receive any keys in runtime data")
|
||||||
}
|
}
|
||||||
rawN, err := base64.RawURLEncoding.DecodeString(runtimeData.Keys[0].N)
|
rawN, err := base64.RawURLEncoding.DecodeString(a.PublicPart[0].N)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decoding modulus string: %w", err)
|
return fmt.Errorf("decoding modulus string: %w", err)
|
||||||
}
|
}
|
||||||
@ -456,7 +268,7 @@ func (a *azureInstanceInfo) validateAk(runtimeDataRaw []byte, reportData []byte,
|
|||||||
return fmt.Errorf("unexpected modulus value in TPM")
|
return fmt.Errorf("unexpected modulus value in TPM")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawE, err := base64.RawURLEncoding.DecodeString(runtimeData.Keys[0].E)
|
rawE, err := base64.RawURLEncoding.DecodeString(a.PublicPart[0].E)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decoding exponent string: %w", err)
|
return fmt.Errorf("decoding exponent string: %w", err)
|
||||||
}
|
}
|
||||||
@ -478,17 +290,13 @@ func (a *azureInstanceInfo) validateAk(runtimeDataRaw []byte, reportData []byte,
|
|||||||
// The HCL is written by Azure, and sits between the Hypervisor and CVM OS.
|
// The HCL is written by Azure, and sits between the Hypervisor and CVM OS.
|
||||||
// The HCL runs in the protected context of the CVM.
|
// The HCL runs in the protected context of the CVM.
|
||||||
type hclAkValidator interface {
|
type hclAkValidator interface {
|
||||||
validateAk(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// akPub are the public parameters of an RSA attestation key.
|
// akPub are the public parameters of an RSA attestation key.
|
||||||
type akPub struct {
|
type akPub struct {
|
||||||
E string
|
E string `json:"e"`
|
||||||
N string
|
N string `json:"n"`
|
||||||
}
|
|
||||||
|
|
||||||
type runtimeData struct {
|
|
||||||
Keys []akPub
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nopAttestationLogger is a no-op implementation of AttestationLogger.
|
// nopAttestationLogger is a no-op implementation of AttestationLogger.
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@ -19,13 +18,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp/testdata"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp/testdata"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
@ -69,264 +68,6 @@ func TestNewValidator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseCertChain tests the parsing of the certificate chain.
|
|
||||||
func TestParseCertChain(t *testing.T) {
|
|
||||||
defaultCertChain := testdata.CertChain
|
|
||||||
askOnly := strings.Split(string(defaultCertChain), "-----END CERTIFICATE-----")[0] + "-----END CERTIFICATE-----"
|
|
||||||
arkOnly := strings.Split(string(defaultCertChain), "-----END CERTIFICATE-----")[1] + "-----END CERTIFICATE-----"
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
certChain []byte
|
|
||||||
wantAsk bool
|
|
||||||
wantArk bool
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"success": {
|
|
||||||
certChain: defaultCertChain,
|
|
||||||
wantAsk: true,
|
|
||||||
wantArk: true,
|
|
||||||
},
|
|
||||||
"empty cert chain": {
|
|
||||||
certChain: []byte{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"more than two certificates": {
|
|
||||||
certChain: append(defaultCertChain, defaultCertChain...),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"invalid certificate": {
|
|
||||||
certChain: []byte("invalid"),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"ark missing": {
|
|
||||||
certChain: []byte(askOnly),
|
|
||||||
wantAsk: true,
|
|
||||||
},
|
|
||||||
"ask missing": {
|
|
||||||
certChain: []byte(arkOnly),
|
|
||||||
wantArk: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
instanceInfo := &azureInstanceInfo{
|
|
||||||
CertChain: tc.certChain,
|
|
||||||
}
|
|
||||||
|
|
||||||
ask, ark, err := instanceInfo.parseCertChain()
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(tc.wantAsk, ask != nil)
|
|
||||||
assert.Equal(tc.wantArk, ark != nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseVCEK tests the parsing of the VCEK certificate.
|
|
||||||
func TestParseVCEK(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
VCEK []byte
|
|
||||||
wantVCEK bool
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"success": {
|
|
||||||
VCEK: testdata.AzureThimVCEK,
|
|
||||||
wantVCEK: true,
|
|
||||||
},
|
|
||||||
"empty": {
|
|
||||||
VCEK: []byte{},
|
|
||||||
},
|
|
||||||
"malformed": {
|
|
||||||
VCEK: testdata.AzureThimVCEK[:len(testdata.AzureThimVCEK)-100],
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"invalid": {
|
|
||||||
VCEK: []byte("invalid"),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
instanceInfo := &azureInstanceInfo{
|
|
||||||
VCEK: tc.VCEK,
|
|
||||||
}
|
|
||||||
|
|
||||||
vcek, err := instanceInfo.parseVCEK()
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(tc.wantVCEK, vcek != nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInstanceInfoAttestation tests the basic unmarshalling of the attestation report and the ASK / ARK precedence.
|
|
||||||
func TestInstanceInfoAttestation(t *testing.T) {
|
|
||||||
defaultReport := testdata.AttestationReport
|
|
||||||
testdataArk, testdataAsk := mustCertChainToPem(t, testdata.CertChain)
|
|
||||||
exampleCert := &x509.Certificate{
|
|
||||||
Raw: []byte{1, 2, 3},
|
|
||||||
}
|
|
||||||
cfg := config.DefaultForAzureSEVSNP()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
report []byte
|
|
||||||
vcek []byte
|
|
||||||
certChain []byte
|
|
||||||
fallbackCerts sevSnpCerts
|
|
||||||
getter *stubHTTPSGetter
|
|
||||||
expectedArk *x509.Certificate
|
|
||||||
expectedAsk *x509.Certificate
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"success": {
|
|
||||||
report: defaultReport,
|
|
||||||
vcek: testdata.AzureThimVCEK,
|
|
||||||
certChain: testdata.CertChain,
|
|
||||||
expectedArk: testdataArk,
|
|
||||||
expectedAsk: testdataAsk,
|
|
||||||
},
|
|
||||||
"retrieve vcek": {
|
|
||||||
report: defaultReport,
|
|
||||||
certChain: testdata.CertChain,
|
|
||||||
getter: newStubHTTPSGetter(
|
|
||||||
&urlResponseMatcher{
|
|
||||||
vcekResponse: testdata.AmdKdsVCEK,
|
|
||||||
wantVcekRequest: true,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
expectedArk: testdataArk,
|
|
||||||
expectedAsk: testdataAsk,
|
|
||||||
},
|
|
||||||
"retrieve certchain": {
|
|
||||||
report: defaultReport,
|
|
||||||
vcek: testdata.AzureThimVCEK,
|
|
||||||
getter: newStubHTTPSGetter(
|
|
||||||
&urlResponseMatcher{
|
|
||||||
certChainResponse: testdata.CertChain,
|
|
||||||
wantCertChainRequest: true,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
expectedArk: testdataArk,
|
|
||||||
expectedAsk: testdataAsk,
|
|
||||||
},
|
|
||||||
"use fallback certs": {
|
|
||||||
report: defaultReport,
|
|
||||||
vcek: testdata.AzureThimVCEK,
|
|
||||||
fallbackCerts: sevSnpCerts{
|
|
||||||
ask: exampleCert,
|
|
||||||
ark: exampleCert,
|
|
||||||
},
|
|
||||||
getter: newStubHTTPSGetter(
|
|
||||||
&urlResponseMatcher{},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
expectedArk: exampleCert,
|
|
||||||
expectedAsk: exampleCert,
|
|
||||||
},
|
|
||||||
"use certchain with fallback certs": {
|
|
||||||
report: defaultReport,
|
|
||||||
certChain: testdata.CertChain,
|
|
||||||
vcek: testdata.AzureThimVCEK,
|
|
||||||
fallbackCerts: sevSnpCerts{
|
|
||||||
ask: &x509.Certificate{},
|
|
||||||
ark: &x509.Certificate{},
|
|
||||||
},
|
|
||||||
getter: newStubHTTPSGetter(
|
|
||||||
&urlResponseMatcher{},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
expectedArk: testdataArk,
|
|
||||||
expectedAsk: testdataAsk,
|
|
||||||
},
|
|
||||||
"retrieve vcek and certchain": {
|
|
||||||
report: defaultReport,
|
|
||||||
getter: newStubHTTPSGetter(
|
|
||||||
&urlResponseMatcher{
|
|
||||||
certChainResponse: testdata.CertChain,
|
|
||||||
vcekResponse: testdata.AmdKdsVCEK,
|
|
||||||
wantCertChainRequest: true,
|
|
||||||
wantVcekRequest: true,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
expectedArk: testdataArk,
|
|
||||||
expectedAsk: testdataAsk,
|
|
||||||
},
|
|
||||||
"report too short": {
|
|
||||||
report: defaultReport[:len(defaultReport)-100],
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"corrupted report": {
|
|
||||||
report: defaultReport[10 : len(defaultReport)-10],
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"certificate fetch error": {
|
|
||||||
report: defaultReport,
|
|
||||||
getter: newStubHTTPSGetter(nil, assert.AnError),
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
// This is important. Without this call, the trust module caches certificates across testcases.
|
|
||||||
defer trust.ClearProductCertCache()
|
|
||||||
|
|
||||||
instanceInfo := azureInstanceInfo{
|
|
||||||
AttestationReport: tc.report,
|
|
||||||
CertChain: tc.certChain,
|
|
||||||
VCEK: tc.vcek,
|
|
||||||
}
|
|
||||||
|
|
||||||
att, err := instanceInfo.attestationWithCerts(logger.NewTest(t), tc.getter, tc.fallbackCerts)
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
require.NoError(err)
|
|
||||||
assert.NotNil(att)
|
|
||||||
assert.NotNil(att.CertificateChain)
|
|
||||||
assert.NotNil(att.Report)
|
|
||||||
|
|
||||||
assert.Equal(hex.EncodeToString(att.Report.IdKeyDigest[:]), "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1")
|
|
||||||
|
|
||||||
// This is a canary for us: If this fails in the future we possibly downgraded a SVN.
|
|
||||||
// See https://github.com/google/go-sev-guest/blob/14ac50e9ffcc05cd1d12247b710c65093beedb58/validate/validate.go#L336 for decomposition of the values.
|
|
||||||
tcbValues := kds.DecomposeTCBVersion(kds.TCBVersion(att.Report.GetLaunchTcb()))
|
|
||||||
assert.True(tcbValues.BlSpl >= cfg.BootloaderVersion.Value)
|
|
||||||
assert.True(tcbValues.TeeSpl >= cfg.TEEVersion.Value)
|
|
||||||
assert.True(tcbValues.SnpSpl >= cfg.SNPVersion.Value)
|
|
||||||
assert.True(tcbValues.UcodeSpl >= cfg.MicrocodeVersion.Value)
|
|
||||||
assert.Equal(tc.expectedArk.Raw, att.CertificateChain.ArkCert)
|
|
||||||
assert.Equal(tc.expectedAsk.Raw, att.CertificateChain.AskCert)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCertChainToPem(t *testing.T, certchain []byte) (ark, ask *x509.Certificate) {
|
|
||||||
t.Helper()
|
|
||||||
a := azureInstanceInfo{CertChain: certchain}
|
|
||||||
ask, ark, err := a.parseCertChain()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return ark, ask
|
|
||||||
}
|
|
||||||
|
|
||||||
type stubHTTPSGetter struct {
|
type stubHTTPSGetter struct {
|
||||||
urlResponseMatcher *urlResponseMatcher // maps responses to requested URLs
|
urlResponseMatcher *urlResponseMatcher // maps responses to requested URLs
|
||||||
err error
|
err error
|
||||||
@ -488,18 +229,18 @@ func TestValidateAk(t *testing.T) {
|
|||||||
n := base64.RawURLEncoding.EncodeToString(key.PublicArea().RSAParameters.ModulusRaw)
|
n := base64.RawURLEncoding.EncodeToString(key.PublicArea().RSAParameters.ModulusRaw)
|
||||||
|
|
||||||
ak := akPub{E: e, N: n}
|
ak := akPub{E: e, N: n}
|
||||||
runtimeData := runtimeData{Keys: []akPub{ak}}
|
runtimeData := attestationKey{PublicPart: []akPub{ak}}
|
||||||
|
|
||||||
defaultRuntimeDataRaw, err := json.Marshal(runtimeData)
|
defaultRuntimeDataRaw, err := json.Marshal(runtimeData)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
defaultInstanceInfo := azureInstanceInfo{RuntimeData: defaultRuntimeDataRaw}
|
defaultInstanceInfo := snp.InstanceInfo{RuntimeData: defaultRuntimeDataRaw}
|
||||||
|
|
||||||
sig := sha256.Sum256(defaultRuntimeDataRaw)
|
sig := sha256.Sum256(defaultRuntimeDataRaw)
|
||||||
defaultReportData := sig[:]
|
defaultReportData := sig[:]
|
||||||
defaultRsaParams := key.PublicArea().RSAParameters
|
defaultRsaParams := key.PublicArea().RSAParameters
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
instanceInfo azureInstanceInfo
|
instanceInfo snp.InstanceInfo
|
||||||
runtimeDataRaw []byte
|
runtimeDataRaw []byte
|
||||||
reportData []byte
|
reportData []byte
|
||||||
rsaParameters *tpm2.RSAParams
|
rsaParameters *tpm2.RSAParams
|
||||||
@ -552,7 +293,8 @@ func TestValidateAk(t *testing.T) {
|
|||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
err = tc.instanceInfo.validateAk(tc.runtimeDataRaw, tc.reportData, tc.rsaParameters)
|
ak := attestationKey{}
|
||||||
|
err = ak.validate(tc.runtimeDataRaw, tc.reportData, tc.rsaParameters)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
} else {
|
} else {
|
||||||
@ -985,7 +727,7 @@ func TestTrustedKeyFromSNP(t *testing.T) {
|
|||||||
// This is important. Without this call, the trust module caches certificates across testcases.
|
// This is important. Without this call, the trust module caches certificates across testcases.
|
||||||
defer trust.ClearProductCertCache()
|
defer trust.ClearProductCertCache()
|
||||||
|
|
||||||
instanceInfo, err := newStubAzureInstanceInfo(tc.vcek, tc.certChain, tc.report, tc.runtimeData)
|
instanceInfo, err := newStubInstanceInfo(tc.vcek, tc.certChain, tc.report, tc.runtimeData)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
statement, err := json.Marshal(instanceInfo)
|
statement, err := json.Marshal(instanceInfo)
|
||||||
@ -1004,7 +746,7 @@ func TestTrustedKeyFromSNP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validator := &Validator{
|
validator := &Validator{
|
||||||
hclValidator: &instanceInfo,
|
hclValidator: &stubAttestationKey{},
|
||||||
config: defaultCfg,
|
config: defaultCfg,
|
||||||
log: logger.NewTest(t),
|
log: logger.NewTest(t),
|
||||||
getter: tc.getter,
|
getter: tc.getter,
|
||||||
@ -1050,25 +792,25 @@ func (v *stubAttestationValidator) SNPAttestation(attestation *spb.Attestation,
|
|||||||
return validate.SnpAttestation(attestation, options)
|
return validate.SnpAttestation(attestation, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
type stubAzureInstanceInfo struct {
|
type stubInstanceInfo struct {
|
||||||
AttestationReport []byte
|
AttestationReport []byte
|
||||||
RuntimeData []byte
|
RuntimeData []byte
|
||||||
VCEK []byte
|
VCEK []byte
|
||||||
CertChain []byte
|
CertChain []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStubAzureInstanceInfo(vcek, certChain []byte, report, runtimeData string) (stubAzureInstanceInfo, error) {
|
func newStubInstanceInfo(vcek, certChain []byte, report, runtimeData string) (stubInstanceInfo, error) {
|
||||||
validReport, err := hex.DecodeString(report)
|
validReport, err := hex.DecodeString(report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stubAzureInstanceInfo{}, fmt.Errorf("invalid hex string report: %s", err)
|
return stubInstanceInfo{}, fmt.Errorf("invalid hex string report: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedRuntime, err := hex.DecodeString(runtimeData)
|
decodedRuntime, err := hex.DecodeString(runtimeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stubAzureInstanceInfo{}, fmt.Errorf("invalid hex string runtimeData: %s", err)
|
return stubInstanceInfo{}, fmt.Errorf("invalid hex string runtimeData: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stubAzureInstanceInfo{
|
return stubInstanceInfo{
|
||||||
AttestationReport: validReport,
|
AttestationReport: validReport,
|
||||||
RuntimeData: decodedRuntime,
|
RuntimeData: decodedRuntime,
|
||||||
VCEK: vcek,
|
VCEK: vcek,
|
||||||
@ -1076,9 +818,12 @@ func newStubAzureInstanceInfo(vcek, certChain []byte, report, runtimeData string
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubAzureInstanceInfo) validateAk(runtimeDataRaw []byte, reportData []byte, _ *tpm2.RSAParams) error {
|
type stubAttestationKey struct {
|
||||||
var runtimeData runtimeData
|
PublicPart []akPub
|
||||||
if err := json.Unmarshal(runtimeDataRaw, &runtimeData); err != nil {
|
}
|
||||||
|
|
||||||
|
func (s *stubAttestationKey) validate(runtimeDataRaw []byte, reportData []byte, _ *tpm2.RSAParams) error {
|
||||||
|
if err := json.Unmarshal(runtimeDataRaw, s); err != nil {
|
||||||
return fmt.Errorf("unmarshalling json: %w", err)
|
return fmt.Errorf("unmarshalling json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
31
internal/attestation/snp/BUILD.bazel
Normal file
31
internal/attestation/snp/BUILD.bazel
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
load("//bazel/go:go_test.bzl", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "snp",
|
||||||
|
srcs = ["snp.go"],
|
||||||
|
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/snp",
|
||||||
|
visibility = ["//:__subpackages__"],
|
||||||
|
deps = [
|
||||||
|
"//internal/attestation",
|
||||||
|
"//internal/constants",
|
||||||
|
"@com_github_google_go_sev_guest//abi",
|
||||||
|
"@com_github_google_go_sev_guest//kds",
|
||||||
|
"@com_github_google_go_sev_guest//proto/sevsnp",
|
||||||
|
"@com_github_google_go_sev_guest//verify/trust",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "snp_test",
|
||||||
|
srcs = ["snp_test.go"],
|
||||||
|
embed = [":snp"],
|
||||||
|
deps = [
|
||||||
|
"//internal/attestation/snp/testdata",
|
||||||
|
"//internal/config",
|
||||||
|
"//internal/logger",
|
||||||
|
"@com_github_google_go_sev_guest//kds",
|
||||||
|
"@com_github_stretchr_testify//assert",
|
||||||
|
"@com_github_stretchr_testify//require",
|
||||||
|
],
|
||||||
|
)
|
217
internal/attestation/snp/snp.go
Normal file
217
internal/attestation/snp/snp.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package SNP provides types shared by SNP-based attestation implementations.
|
||||||
|
// It ensures all issuers provide the same types to the verify command.
|
||||||
|
package snp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/google/go-sev-guest/abi"
|
||||||
|
"github.com/google/go-sev-guest/kds"
|
||||||
|
spb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||||
|
"github.com/google/go-sev-guest/verify/trust"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstanceInfo contains the necessary information to establish trust in
|
||||||
|
// an Azure CVM.
|
||||||
|
type InstanceInfo struct {
|
||||||
|
// VCEK is the PEM-encoded VCEK certificate for the attestation report.
|
||||||
|
VCEK []byte
|
||||||
|
// CertChain is the PEM-encoded certificate chain for the attestation report.
|
||||||
|
CertChain []byte
|
||||||
|
// AttestationReport is the attestation report from the vTPM (NVRAM) of the CVM.
|
||||||
|
AttestationReport []byte
|
||||||
|
// RuntimeData is the Azure runtime data from the vTPM (NVRAM) of the CVM.
|
||||||
|
RuntimeData []byte
|
||||||
|
// MAAToken is the token of the MAA for the attestation report, used as a fallback
|
||||||
|
// if the IDKeyDigest cannot be verified.
|
||||||
|
MAAToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttestationWithCerts returns a formatted version of the attestation report and its certificates from the instanceInfo.
|
||||||
|
// Certificates are retrieved in the following precedence:
|
||||||
|
// 1. ASK or ARK from issuer. On Azure: THIM. One AWS: not prefilled.
|
||||||
|
// 2. ASK or ARK from fallbackCerts.
|
||||||
|
// 3. ASK or ARK from AMD KDS.
|
||||||
|
func (a *InstanceInfo) AttestationWithCerts(logger attestation.Logger, getter trust.HTTPSGetter,
|
||||||
|
fallbackCerts CertificateChain,
|
||||||
|
) (*spb.Attestation, error) {
|
||||||
|
report, err := abi.ReportToProto(a.AttestationReport)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting report to proto: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product info as reported through CPUID[EAX=1]
|
||||||
|
sevProduct := &spb.SevProduct{Name: spb.SevProduct_SEV_PRODUCT_MILAN, Stepping: 0} // Milan-B0
|
||||||
|
productName := kds.ProductString(sevProduct)
|
||||||
|
|
||||||
|
att := &spb.Attestation{
|
||||||
|
Report: report,
|
||||||
|
CertificateChain: &spb.CertificateChain{},
|
||||||
|
Product: sevProduct,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the VCEK certificate is present, parse it and format it.
|
||||||
|
vcek, err := a.ParseVCEK()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("Error parsing VCEK: %v", err)
|
||||||
|
}
|
||||||
|
if vcek != nil {
|
||||||
|
att.CertificateChain.VcekCert = vcek.Raw
|
||||||
|
} else {
|
||||||
|
// Otherwise, retrieve it from AMD KDS.
|
||||||
|
logger.Infof("VCEK certificate not present, falling back to retrieving it from AMD KDS")
|
||||||
|
vcekURL := kds.VCEKCertURL(productName, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb()))
|
||||||
|
vcek, err := getter.Get(vcekURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("retrieving VCEK certificate from AMD KDS: %w", err)
|
||||||
|
}
|
||||||
|
att.CertificateChain.VcekCert = vcek
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the certificate chain from THIM is present, parse it and format it.
|
||||||
|
ask, ark, err := a.ParseCertChain()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("Error parsing certificate chain: %v", err)
|
||||||
|
}
|
||||||
|
if ask != nil {
|
||||||
|
logger.Infof("Using ASK certificate from Azure THIM")
|
||||||
|
att.CertificateChain.AskCert = ask.Raw
|
||||||
|
}
|
||||||
|
if ark != nil {
|
||||||
|
logger.Infof("Using ARK certificate from Azure THIM")
|
||||||
|
att.CertificateChain.ArkCert = ark.Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a cached ASK or an ARK from the Constellation config is present, use it.
|
||||||
|
if att.CertificateChain.AskCert == nil && fallbackCerts.ask != nil {
|
||||||
|
logger.Infof("Using cached ASK certificate")
|
||||||
|
att.CertificateChain.AskCert = fallbackCerts.ask.Raw
|
||||||
|
}
|
||||||
|
if att.CertificateChain.ArkCert == nil && fallbackCerts.ark != nil {
|
||||||
|
logger.Infof("Using ARK certificate from %s", constants.ConfigFilename)
|
||||||
|
att.CertificateChain.ArkCert = fallbackCerts.ark.Raw
|
||||||
|
}
|
||||||
|
// Otherwise, retrieve it from AMD KDS.
|
||||||
|
if att.CertificateChain.AskCert == nil || att.CertificateChain.ArkCert == nil {
|
||||||
|
logger.Infof(
|
||||||
|
"Certificate chain not fully present (ARK present: %t, ASK present: %t), falling back to retrieving it from AMD KDS",
|
||||||
|
(att.CertificateChain.ArkCert != nil),
|
||||||
|
(att.CertificateChain.AskCert != nil),
|
||||||
|
)
|
||||||
|
kdsCertChain, err := trust.GetProductChain(productName, abi.VcekReportSigner, getter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("retrieving certificate chain from AMD KDS: %w", err)
|
||||||
|
}
|
||||||
|
if att.CertificateChain.AskCert == nil && kdsCertChain.Ask != nil {
|
||||||
|
logger.Infof("Using ASK certificate from AMD KDS")
|
||||||
|
att.CertificateChain.AskCert = kdsCertChain.Ask.Raw
|
||||||
|
}
|
||||||
|
if att.CertificateChain.ArkCert == nil && kdsCertChain.Ask != nil {
|
||||||
|
logger.Infof("Using ARK certificate from AMD KDS")
|
||||||
|
att.CertificateChain.ArkCert = kdsCertChain.Ark.Raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return att, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertificateChain struct {
|
||||||
|
ask *x509.Certificate
|
||||||
|
ark *x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCertificateChain(ask, ark *x509.Certificate) CertificateChain {
|
||||||
|
return CertificateChain{
|
||||||
|
ask: ask,
|
||||||
|
ark: ark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCertChain parses the certificate chain from the instanceInfo into x509-formatted ASK and ARK certificates.
|
||||||
|
// If less than 2 certificates are present, only the present certificate is returned.
|
||||||
|
// If more than 2 certificates are present, an error is returned.
|
||||||
|
func (a *InstanceInfo) ParseCertChain() (ask, ark *x509.Certificate, retErr error) {
|
||||||
|
rest := bytes.TrimSpace(a.CertChain)
|
||||||
|
|
||||||
|
i := 1
|
||||||
|
var block *pem.Block
|
||||||
|
for block, rest = pem.Decode(rest); block != nil; block, rest = pem.Decode(rest) {
|
||||||
|
if i > 2 {
|
||||||
|
retErr = fmt.Errorf("parse certificate %d: more than 2 certificates in chain", i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
retErr = fmt.Errorf("parse certificate %d: expected PEM block type 'CERTIFICATE', got '%s'", i, block.Type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
retErr = fmt.Errorf("parse certificate %d: %w", i, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf
|
||||||
|
// Table 6 and 7
|
||||||
|
switch cert.Subject.CommonName {
|
||||||
|
case "SEV-Milan":
|
||||||
|
ask = cert
|
||||||
|
case "ARK-Milan":
|
||||||
|
ark = cert
|
||||||
|
default:
|
||||||
|
retErr = fmt.Errorf("parse certificate %d: unexpected subject CN %s", i, cert.Subject.CommonName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case i == 1:
|
||||||
|
retErr = fmt.Errorf("no PEM blocks found")
|
||||||
|
case len(rest) != 0:
|
||||||
|
retErr = fmt.Errorf("remaining PEM block is not a valid certificate: %s", rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVCEK parses the VCEK certificate from the instanceInfo into an x509-formatted certificate.
|
||||||
|
// If the VCEK certificate is not present, nil is returned.
|
||||||
|
func (a *InstanceInfo) ParseVCEK() (*x509.Certificate, error) {
|
||||||
|
newlinesTrimmed := bytes.TrimSpace(a.VCEK)
|
||||||
|
if len(newlinesTrimmed) == 0 {
|
||||||
|
// VCEK is not present.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block, rest := pem.Decode(newlinesTrimmed)
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("no PEM blocks found")
|
||||||
|
}
|
||||||
|
if len(rest) != 0 {
|
||||||
|
return nil, fmt.Errorf("received more data than expected")
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, fmt.Errorf("expected PEM block type 'CERTIFICATE', got '%s'", block.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
vcek, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing VCEK certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vcek, nil
|
||||||
|
}
|
315
internal/attestation/snp/snp_test.go
Normal file
315
internal/attestation/snp/snp_test.go
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package snp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp/testdata"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/google/go-sev-guest/kds"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestParseCertChain tests the parsing of the certificate chain.
|
||||||
|
func TestParseCertChain(t *testing.T) {
|
||||||
|
defaultCertChain := testdata.CertChain
|
||||||
|
askOnly := strings.Split(string(defaultCertChain), "-----END CERTIFICATE-----")[0] + "-----END CERTIFICATE-----"
|
||||||
|
arkOnly := strings.Split(string(defaultCertChain), "-----END CERTIFICATE-----")[1] + "-----END CERTIFICATE-----"
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
certChain []byte
|
||||||
|
wantAsk bool
|
||||||
|
wantArk bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
certChain: defaultCertChain,
|
||||||
|
wantAsk: true,
|
||||||
|
wantArk: true,
|
||||||
|
},
|
||||||
|
"empty cert chain": {
|
||||||
|
certChain: []byte{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"more than two certificates": {
|
||||||
|
certChain: append(defaultCertChain, defaultCertChain...),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid certificate": {
|
||||||
|
certChain: []byte("invalid"),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"ark missing": {
|
||||||
|
certChain: []byte(askOnly),
|
||||||
|
wantAsk: true,
|
||||||
|
},
|
||||||
|
"ask missing": {
|
||||||
|
certChain: []byte(arkOnly),
|
||||||
|
wantArk: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
instanceInfo := &InstanceInfo{
|
||||||
|
CertChain: tc.certChain,
|
||||||
|
}
|
||||||
|
|
||||||
|
ask, ark, err := instanceInfo.ParseCertChain()
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantAsk, ask != nil)
|
||||||
|
assert.Equal(tc.wantArk, ark != nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseVCEK tests the parsing of the VCEK certificate.
|
||||||
|
func TestParseVCEK(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
VCEK []byte
|
||||||
|
wantVCEK bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
VCEK: testdata.AzureThimVCEK,
|
||||||
|
wantVCEK: true,
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
VCEK: []byte{},
|
||||||
|
},
|
||||||
|
"malformed": {
|
||||||
|
VCEK: testdata.AzureThimVCEK[:len(testdata.AzureThimVCEK)-100],
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
VCEK: []byte("invalid"),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
instanceInfo := &InstanceInfo{
|
||||||
|
VCEK: tc.VCEK,
|
||||||
|
}
|
||||||
|
|
||||||
|
vcek, err := instanceInfo.ParseVCEK()
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantVCEK, vcek != nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInstanceInfoAttestation tests the basic unmarshalling of the attestation report and the ASK / ARK precedence.
|
||||||
|
func TestInstanceInfoAttestation(t *testing.T) {
|
||||||
|
defaultReport := testdata.AttestationReport
|
||||||
|
testdataArk, testdataAsk := mustCertChainToPem(t, testdata.CertChain)
|
||||||
|
exampleCert := &x509.Certificate{
|
||||||
|
Raw: []byte{1, 2, 3},
|
||||||
|
}
|
||||||
|
cfg := config.DefaultForAzureSEVSNP()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
report []byte
|
||||||
|
vcek []byte
|
||||||
|
certChain []byte
|
||||||
|
fallbackCerts CertificateChain
|
||||||
|
getter *stubHTTPSGetter
|
||||||
|
expectedArk *x509.Certificate
|
||||||
|
expectedAsk *x509.Certificate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
report: defaultReport,
|
||||||
|
vcek: testdata.AzureThimVCEK,
|
||||||
|
certChain: testdata.CertChain,
|
||||||
|
expectedArk: testdataArk,
|
||||||
|
expectedAsk: testdataAsk,
|
||||||
|
},
|
||||||
|
"retrieve vcek": {
|
||||||
|
report: defaultReport,
|
||||||
|
certChain: testdata.CertChain,
|
||||||
|
getter: newStubHTTPSGetter(
|
||||||
|
&urlResponseMatcher{
|
||||||
|
vcekResponse: testdata.AmdKdsVCEK,
|
||||||
|
wantVcekRequest: true,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
expectedArk: testdataArk,
|
||||||
|
expectedAsk: testdataAsk,
|
||||||
|
},
|
||||||
|
"retrieve certchain": {
|
||||||
|
report: defaultReport,
|
||||||
|
vcek: testdata.AzureThimVCEK,
|
||||||
|
getter: newStubHTTPSGetter(
|
||||||
|
&urlResponseMatcher{
|
||||||
|
certChainResponse: testdata.CertChain,
|
||||||
|
wantCertChainRequest: true,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
expectedArk: testdataArk,
|
||||||
|
expectedAsk: testdataAsk,
|
||||||
|
},
|
||||||
|
"use fallback certs": {
|
||||||
|
report: defaultReport,
|
||||||
|
vcek: testdata.AzureThimVCEK,
|
||||||
|
fallbackCerts: NewCertificateChain(exampleCert, exampleCert),
|
||||||
|
getter: newStubHTTPSGetter(
|
||||||
|
&urlResponseMatcher{},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
expectedArk: exampleCert,
|
||||||
|
expectedAsk: exampleCert,
|
||||||
|
},
|
||||||
|
"use certchain with fallback certs": {
|
||||||
|
report: defaultReport,
|
||||||
|
certChain: testdata.CertChain,
|
||||||
|
vcek: testdata.AzureThimVCEK,
|
||||||
|
fallbackCerts: NewCertificateChain(&x509.Certificate{}, &x509.Certificate{}),
|
||||||
|
getter: newStubHTTPSGetter(
|
||||||
|
&urlResponseMatcher{},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
expectedArk: testdataArk,
|
||||||
|
expectedAsk: testdataAsk,
|
||||||
|
},
|
||||||
|
"retrieve vcek and certchain": {
|
||||||
|
report: defaultReport,
|
||||||
|
getter: newStubHTTPSGetter(
|
||||||
|
&urlResponseMatcher{
|
||||||
|
certChainResponse: testdata.CertChain,
|
||||||
|
vcekResponse: testdata.AmdKdsVCEK,
|
||||||
|
wantCertChainRequest: true,
|
||||||
|
wantVcekRequest: true,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
expectedArk: testdataArk,
|
||||||
|
expectedAsk: testdataAsk,
|
||||||
|
},
|
||||||
|
"report too short": {
|
||||||
|
report: defaultReport[:len(defaultReport)-100],
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"corrupted report": {
|
||||||
|
report: defaultReport[10 : len(defaultReport)-10],
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"certificate fetch error": {
|
||||||
|
report: defaultReport,
|
||||||
|
getter: newStubHTTPSGetter(nil, assert.AnError),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
instanceInfo := InstanceInfo{
|
||||||
|
AttestationReport: tc.report,
|
||||||
|
CertChain: tc.certChain,
|
||||||
|
VCEK: tc.vcek,
|
||||||
|
}
|
||||||
|
|
||||||
|
att, err := instanceInfo.AttestationWithCerts(logger.NewTest(t), tc.getter, tc.fallbackCerts)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
require.NoError(err)
|
||||||
|
assert.NotNil(att)
|
||||||
|
assert.NotNil(att.CertificateChain)
|
||||||
|
assert.NotNil(att.Report)
|
||||||
|
|
||||||
|
assert.Equal(hex.EncodeToString(att.Report.IdKeyDigest[:]), "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1")
|
||||||
|
|
||||||
|
// This is a canary for us: If this fails in the future we possibly downgraded a SVN.
|
||||||
|
// See https://github.com/google/go-sev-guest/blob/14ac50e9ffcc05cd1d12247b710c65093beedb58/validate/validate.go#L336 for decomposition of the values.
|
||||||
|
tcbValues := kds.DecomposeTCBVersion(kds.TCBVersion(att.Report.GetLaunchTcb()))
|
||||||
|
assert.True(tcbValues.BlSpl >= cfg.BootloaderVersion.Value)
|
||||||
|
assert.True(tcbValues.TeeSpl >= cfg.TEEVersion.Value)
|
||||||
|
assert.True(tcbValues.SnpSpl >= cfg.SNPVersion.Value)
|
||||||
|
assert.True(tcbValues.UcodeSpl >= cfg.MicrocodeVersion.Value)
|
||||||
|
assert.Equal(tc.expectedArk.Raw, att.CertificateChain.ArkCert)
|
||||||
|
assert.Equal(tc.expectedAsk.Raw, att.CertificateChain.AskCert)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCertChainToPem(t *testing.T, certchain []byte) (ark, ask *x509.Certificate) {
|
||||||
|
t.Helper()
|
||||||
|
a := InstanceInfo{CertChain: certchain}
|
||||||
|
ask, ark, err := a.ParseCertChain()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return ark, ask
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubHTTPSGetter struct {
|
||||||
|
urlResponseMatcher *urlResponseMatcher // maps responses to requested URLs
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStubHTTPSGetter(urlResponseMatcher *urlResponseMatcher, err error) *stubHTTPSGetter {
|
||||||
|
return &stubHTTPSGetter{
|
||||||
|
urlResponseMatcher: urlResponseMatcher,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubHTTPSGetter) Get(url string) ([]byte, error) {
|
||||||
|
if s.err != nil {
|
||||||
|
return nil, s.err
|
||||||
|
}
|
||||||
|
return s.urlResponseMatcher.match(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlResponseMatcher struct {
|
||||||
|
certChainResponse []byte
|
||||||
|
wantCertChainRequest bool
|
||||||
|
vcekResponse []byte
|
||||||
|
wantVcekRequest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *urlResponseMatcher) match(url string) ([]byte, error) {
|
||||||
|
switch {
|
||||||
|
case url == "https://kdsintf.amd.com/vcek/v1/Milan/cert_chain":
|
||||||
|
if !m.wantCertChainRequest {
|
||||||
|
return nil, fmt.Errorf("unexpected cert_chain request")
|
||||||
|
}
|
||||||
|
return m.certChainResponse, nil
|
||||||
|
case regexp.MustCompile(`https:\/\/kdsintf.amd.com\/vcek\/v1\/Milan\/.*`).MatchString(url):
|
||||||
|
if !m.wantVcekRequest {
|
||||||
|
return nil, fmt.Errorf("unexpected VCEK request")
|
||||||
|
}
|
||||||
|
return m.vcekResponse, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected URL: %s", url)
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,6 @@ go_library(
|
|||||||
"runtimedata.bin",
|
"runtimedata.bin",
|
||||||
"vcek.pem",
|
"vcek.pem",
|
||||||
],
|
],
|
||||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp/testdata",
|
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/snp/testdata",
|
||||||
visibility = ["//:__subpackages__"],
|
visibility = ["//:__subpackages__"],
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user