cli: new flag for Azure JSON output of constellation verify (#2391)

This commit is contained in:
Adrian Stobbe 2023-10-07 16:24:29 +02:00 committed by GitHub
parent cc4ec80e48
commit fdd47b7a00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 483 additions and 173 deletions

View file

@ -75,6 +75,7 @@ go_library(
"//internal/semver", "//internal/semver",
"//internal/sigstore", "//internal/sigstore",
"//internal/sigstore/keyselect", "//internal/sigstore/keyselect",
"//internal/verify",
"//internal/versions", "//internal/versions",
"//verify/verifyproto", "//verify/verifyproto",
"@com_github_golang_jwt_jwt_v5//:jwt", "@com_github_golang_jwt_jwt_v5//:jwt",

View file

@ -32,11 +32,13 @@ import (
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/crypto" "github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer" "github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/verify"
"github.com/edgelesssys/constellation/v2/verify/verifyproto" "github.com/edgelesssys/constellation/v2/verify/verifyproto"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/abi"
@ -57,7 +59,7 @@ func NewVerifyCmd() *cobra.Command {
RunE: runVerify, RunE: runVerify,
} }
cmd.Flags().String("cluster-id", "", "expected cluster identifier") cmd.Flags().String("cluster-id", "", "expected cluster identifier")
cmd.Flags().Bool("raw", false, "print raw attestation document") cmd.Flags().StringP("output", "o", "", "print the attestation document in the output format {json|raw}")
cmd.Flags().StringP("node-endpoint", "e", "", "endpoint of the node to verify, passed as HOST[:PORT]") cmd.Flags().StringP("node-endpoint", "e", "", "endpoint of the node to verify, passed as HOST[:PORT]")
return cmd return cmd
} }
@ -78,16 +80,29 @@ func runVerify(cmd *cobra.Command, _ []string) error {
dialer: dialer.New(nil, nil, &net.Dialer{}), dialer: dialer.New(nil, nil, &net.Dialer{}),
log: log, log: log,
} }
formatter := &attestationDocFormatterImpl{ formatterFactory := func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error) {
log: log, if output == "json" && provider != cloudprovider.Azure {
return nil, errors.New("json output is only supported for Azure")
}
switch output {
case "json":
return &jsonAttestationDocFormatter{log}, nil
case "raw":
return &rawAttestationDocFormatter{log}, nil
case "":
return &defaultAttestationDocFormatter{log}, nil
default:
return nil, fmt.Errorf("invalid output value for formatter: %s", output)
}
} }
v := &verifyCmd{log: log} v := &verifyCmd{log: log}
fetcher := attestationconfigapi.NewFetcher() fetcher := attestationconfigapi.NewFetcher()
return v.verify(cmd, fileHandler, verifyClient, formatter, fetcher) return v.verify(cmd, fileHandler, verifyClient, formatterFactory, fetcher)
} }
func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, formatter attestationDocFormatter, configFetcher attestationconfigapi.Fetcher) error { type formatterFactory func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error)
func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, factory formatterFactory, configFetcher attestationconfigapi.Fetcher) error {
flags, err := c.parseVerifyFlags(cmd, fileHandler) flags, err := c.parseVerifyFlags(cmd, fileHandler)
if err != nil { if err != nil {
return fmt.Errorf("parsing flags: %w", err) return fmt.Errorf("parsing flags: %w", err)
@ -136,11 +151,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
} }
// certificates are only available for Azure // certificates are only available for Azure
formatter, err := factory(flags.output, conf.GetProvider(), c.log)
if err != nil {
return fmt.Errorf("creating formatter: %w", err)
}
attDocOutput, err := formatter.format( attDocOutput, err := formatter.format(
cmd.Context(), cmd.Context(),
rawAttestationDoc, rawAttestationDoc,
conf.Provider.Azure == nil, conf.Provider.Azure == nil,
flags.rawOutput,
attConfig.GetMeasurements(), attConfig.GetMeasurements(),
flags.maaURL, flags.maaURL,
) )
@ -148,7 +166,7 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
return fmt.Errorf("printing attestation document: %w", err) return fmt.Errorf("printing attestation document: %w", err)
} }
cmd.Println(attDocOutput) cmd.Println(attDocOutput)
cmd.Println("Verification OK") cmd.PrintErrln("Verification OK")
return nil return nil
} }
@ -180,11 +198,11 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle
} }
c.log.Debugf("Flag 'force' set to %t", force) c.log.Debugf("Flag 'force' set to %t", force)
raw, err := cmd.Flags().GetBool("raw") output, err := cmd.Flags().GetString("output")
if err != nil { if err != nil {
return verifyFlags{}, fmt.Errorf("parsing raw argument: %w", err) return verifyFlags{}, fmt.Errorf("parsing raw argument: %w", err)
} }
c.log.Debugf("Flag 'raw' set to %t", force) c.log.Debugf("Flag 'output' set to %t", output)
var idFile clusterid.File var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) {
@ -197,11 +215,11 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle
if emptyEndpoint || emptyIDs { if emptyEndpoint || emptyIDs {
c.log.Debugf("Trying to supplement empty flag values from %q", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) c.log.Debugf("Trying to supplement empty flag values from %q", pf.PrefixPrintablePath(constants.ClusterIDsFilename))
if emptyEndpoint { if emptyEndpoint {
cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) cmd.PrintErrf("Using endpoint from %q. Specify --node-endpoint to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename))
endpoint = idFile.IP endpoint = idFile.IP
} }
if emptyIDs { if emptyIDs {
cmd.Printf("Using ID from %q. Specify --cluster-id to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) cmd.PrintErrf("Using ID from %q. Specify --cluster-id to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename))
ownerID = idFile.OwnerID ownerID = idFile.OwnerID
clusterID = idFile.ClusterID clusterID = idFile.ClusterID
} }
@ -222,7 +240,7 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle
ownerID: ownerID, ownerID: ownerID,
clusterID: clusterID, clusterID: clusterID,
maaURL: idFile.AttestationURL, maaURL: idFile.AttestationURL,
rawOutput: raw, output: output,
force: force, force: force,
}, nil }, nil
} }
@ -232,7 +250,7 @@ type verifyFlags struct {
ownerID string ownerID string
clusterID string clusterID string
maaURL string maaURL string
rawOutput bool output string
force bool force bool
pf pathprefix.PathPrefixer pf pathprefix.PathPrefixer
} }
@ -257,24 +275,75 @@ func addPortIfMissing(endpoint string, defaultPort int) (string, error) {
// an attestationDocFormatter formats the attestation document. // an attestationDocFormatter formats the attestation document.
type attestationDocFormatter interface { type attestationDocFormatter interface {
// format returns the raw or formatted attestation doc depending on the rawOutput argument. // format returns the raw or formatted attestation doc depending on the rawOutput argument.
format(ctx context.Context, docString string, PCRsOnly bool, rawOutput bool, expectedPCRs measurements.M, format(ctx context.Context, docString string, PCRsOnly bool, expectedPCRs measurements.M,
attestationServiceURL string) (string, error) attestationServiceURL string) (string, error)
} }
type attestationDocFormatterImpl struct { type jsonAttestationDocFormatter struct {
log debugLog log debugLog
} }
// format returns the raw or formatted attestation doc depending on the rawOutput argument. // format returns the json formatted attestation doc.
func (f *attestationDocFormatterImpl) format(ctx context.Context, docString string, PCRsOnly bool, func (f *jsonAttestationDocFormatter) format(ctx context.Context, docString string, _ bool,
rawOutput bool, expectedPCRs measurements.M, attestationServiceURL string, _ measurements.M, attestationServiceURL string,
) (string, error) {
instanceInfo, err := extractAzureInstanceInfo(docString)
if err != nil {
return "", fmt.Errorf("unmarshal instance info: %w", err)
}
snpReport, err := newSNPReport(instanceInfo.AttestationReport)
if err != nil {
return "", fmt.Errorf("parsing SNP report: %w", err)
}
vcek, err := newCertificates("VCEK certificate", instanceInfo.Vcek, f.log)
if err != nil {
return "", fmt.Errorf("parsing VCEK certificate: %w", err)
}
certChain, err := newCertificates("Certificate chain", instanceInfo.CertChain, f.log)
if err != nil {
return "", fmt.Errorf("parsing certificate chain: %w", err)
}
maaToken, err := newMAAToken(ctx, instanceInfo.MAAToken, attestationServiceURL)
if err != nil {
return "", fmt.Errorf("parsing MAA token: %w", err)
}
report := verify.Report{
SNPReport: snpReport,
VCEK: vcek,
CertChain: certChain,
MAAToken: maaToken,
}
jsonBytes, err := json.Marshal(report)
return string(jsonBytes), err
}
type rawAttestationDocFormatter struct {
log debugLog
}
// format returns the raw attestation doc.
func (f *rawAttestationDocFormatter) format(_ context.Context, docString string, _ bool,
_ measurements.M, _ string,
) (string, error) {
b := &strings.Builder{}
b.WriteString("Attestation Document:\n")
b.WriteString(fmt.Sprintf("%s\n", docString))
return b.String(), nil
}
type defaultAttestationDocFormatter struct {
log debugLog
}
// format returns the formatted attestation doc.
func (f *defaultAttestationDocFormatter) format(ctx context.Context, docString string, PCRsOnly bool,
expectedPCRs measurements.M, attestationServiceURL string,
) (string, error) { ) (string, error) {
b := &strings.Builder{} b := &strings.Builder{}
b.WriteString("Attestation Document:\n") b.WriteString("Attestation Document:\n")
if rawOutput {
b.WriteString(fmt.Sprintf("%s\n", docString))
return b.String(), nil
}
var doc attestationDoc var doc attestationDoc
if err := json.Unmarshal([]byte(docString), &doc); err != nil { if err := json.Unmarshal([]byte(docString), &doc); err != nil {
@ -304,9 +373,11 @@ func (f *attestationDocFormatterImpl) format(ctx context.Context, docString stri
if err := f.parseCerts(b, "Certificate chain", instanceInfo.CertChain); err != nil { if err := f.parseCerts(b, "Certificate chain", instanceInfo.CertChain); err != nil {
return "", fmt.Errorf("print certificate chain: %w", err) return "", fmt.Errorf("print certificate chain: %w", err)
} }
if err := f.parseSNPReport(b, instanceInfo.AttestationReport); err != nil { snpReport, err := newSNPReport(instanceInfo.AttestationReport)
return "", fmt.Errorf("print SNP report: %w", err) if err != nil {
return "", fmt.Errorf("parsing SNP report: %w", err)
} }
f.buildSNPReport(b, snpReport)
if err := parseMAAToken(ctx, b, instanceInfo.MAAToken, attestationServiceURL); err != nil { if err := parseMAAToken(ctx, b, instanceInfo.MAAToken, attestationServiceURL); err != nil {
return "", fmt.Errorf("print MAA token: %w", err) return "", fmt.Errorf("print MAA token: %w", err)
} }
@ -315,7 +386,7 @@ func (f *attestationDocFormatterImpl) format(ctx context.Context, docString stri
} }
// parseCerts parses the PEM certificates and writes their details to the output builder. // parseCerts parses the PEM certificates and writes their details to the output builder.
func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeName string, cert []byte) error { func (f *defaultAttestationDocFormatter) parseCerts(b *strings.Builder, certTypeName string, cert []byte) error {
newlinesTrimmed := strings.TrimSpace(string(cert)) newlinesTrimmed := strings.TrimSpace(string(cert))
formattedCert := strings.ReplaceAll(newlinesTrimmed, "\n", "\n\t\t") + "\n" formattedCert := strings.ReplaceAll(newlinesTrimmed, "\n", "\n\t\t") + "\n"
b.WriteString(fmt.Sprintf("\tRaw %s:\n\t\t%s", certTypeName, formattedCert)) b.WriteString(fmt.Sprintf("\tRaw %s:\n\t\t%s", certTypeName, formattedCert))
@ -380,7 +451,7 @@ func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeNam
} }
// parseQuotes parses the base64-encoded quotes and writes their details to the output builder. // parseQuotes parses the base64-encoded quotes and writes their details to the output builder.
func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error { func (f *defaultAttestationDocFormatter) parseQuotes(b *strings.Builder, quotes []*tpmProto.Quote, expectedPCRs measurements.M) error {
writeIndentfln(b, 1, "Quote:") writeIndentfln(b, 1, "Quote:")
var pcrNumbers []uint32 var pcrNumbers []uint32
@ -407,79 +478,53 @@ func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []*
return nil return nil
} }
func (f *attestationDocFormatterImpl) parseSNPReport(b *strings.Builder, reportBytes []byte) error { func (f *defaultAttestationDocFormatter) buildSNPReport(b *strings.Builder, report verify.SNPReport) {
report, err := abi.ReportToProto(reportBytes) writeTCB := func(tcb verify.TCBVersion) {
if err != nil { writeIndentfln(b, 3, "Secure Processor bootloader SVN: %d", tcb.Bootloader)
return fmt.Errorf("parsing report to proto: %w", err) writeIndentfln(b, 3, "Secure Processor operating system SVN: %d", tcb.TEE)
}
policy, err := abi.ParseSnpPolicy(report.Policy)
if err != nil {
return fmt.Errorf("parsing policy: %w", err)
}
platformInfo, err := abi.ParseSnpPlatformInfo(report.PlatformInfo)
if err != nil {
return fmt.Errorf("parsing platform info: %w", err)
}
signature, err := abi.ReportToSignatureDER(reportBytes)
if err != nil {
return fmt.Errorf("parsing signature: %w", err)
}
signerInfo, err := abi.ParseSignerInfo(report.SignerInfo)
if err != nil {
return fmt.Errorf("parsing signer info: %w", err)
}
writeTCB := func(tcbVersion uint64) {
tcb := kds.DecomposeTCBVersion(kds.TCBVersion(tcbVersion))
writeIndentfln(b, 3, "Secure Processor bootloader SVN: %d", tcb.BlSpl)
writeIndentfln(b, 3, "Secure Processor operating system SVN: %d", tcb.TeeSpl)
writeIndentfln(b, 3, "SVN 4 (reserved): %d", tcb.Spl4) writeIndentfln(b, 3, "SVN 4 (reserved): %d", tcb.Spl4)
writeIndentfln(b, 3, "SVN 5 (reserved): %d", tcb.Spl5) writeIndentfln(b, 3, "SVN 5 (reserved): %d", tcb.Spl5)
writeIndentfln(b, 3, "SVN 6 (reserved): %d", tcb.Spl6) writeIndentfln(b, 3, "SVN 6 (reserved): %d", tcb.Spl6)
writeIndentfln(b, 3, "SVN 7 (reserved): %d", tcb.Spl7) writeIndentfln(b, 3, "SVN 7 (reserved): %d", tcb.Spl7)
writeIndentfln(b, 3, "SEV-SNP firmware SVN: %d", tcb.SnpSpl) writeIndentfln(b, 3, "SEV-SNP firmware SVN: %d", tcb.SNP)
writeIndentfln(b, 3, "Microcode SVN: %d", tcb.UcodeSpl) writeIndentfln(b, 3, "Microcode SVN: %d", tcb.Microcode)
} }
writeIndentfln(b, 1, "SNP Report:") writeIndentfln(b, 1, "SNP Report:")
writeIndentfln(b, 2, "Version: %d", report.Version) writeIndentfln(b, 2, "Version: %d", report.Version)
writeIndentfln(b, 2, "Guest SVN: %d", report.GuestSvn) writeIndentfln(b, 2, "Guest SVN: %d", report.GuestSvn)
writeIndentfln(b, 2, "Policy:") writeIndentfln(b, 2, "Policy:")
writeIndentfln(b, 3, "ABI Minor: %d", policy.ABIMinor) writeIndentfln(b, 3, "ABI Minor: %d", report.PolicyABIMinor)
writeIndentfln(b, 3, "ABI Major: %d", policy.ABIMajor) writeIndentfln(b, 3, "ABI Major: %d", report.PolicyABIMajor)
writeIndentfln(b, 3, "Symmetric Multithreading enabled: %t", policy.SMT) writeIndentfln(b, 3, "Symmetric Multithreading enabled: %t", report.PolicySMT)
writeIndentfln(b, 3, "Migration agent enabled: %t", policy.MigrateMA) writeIndentfln(b, 3, "Migration agent enabled: %t", report.PolicyMigrationAgent)
writeIndentfln(b, 3, "Debugging enabled (host decryption of VM): %t", policy.Debug) writeIndentfln(b, 3, "Debugging enabled (host decryption of VM): %t", report.PolicyDebug)
writeIndentfln(b, 3, "Single socket enabled: %t", policy.SingleSocket) writeIndentfln(b, 3, "Single socket enabled: %t", report.PolicySingleSocket)
writeIndentfln(b, 2, "Family ID: %x", report.FamilyId) writeIndentfln(b, 2, "Family ID: %x", report.FamilyID)
writeIndentfln(b, 2, "Image ID: %x", report.ImageId) writeIndentfln(b, 2, "Image ID: %x", report.ImageID)
writeIndentfln(b, 2, "VMPL: %d", report.Vmpl) writeIndentfln(b, 2, "VMPL: %d", report.Vmpl)
writeIndentfln(b, 2, "Signature Algorithm: %d", report.SignatureAlgo) writeIndentfln(b, 2, "Signature Algorithm: %d", report.SignatureAlgo)
writeIndentfln(b, 2, "Current TCB:") writeIndentfln(b, 2, "Current TCB:")
writeTCB(report.CurrentTcb) writeTCB(report.CurrentTCB)
writeIndentfln(b, 2, "Platform Info:") writeIndentfln(b, 2, "Platform Info:")
writeIndentfln(b, 3, "Symmetric Multithreading enabled (SMT): %t", platformInfo.SMTEnabled) writeIndentfln(b, 3, "Symmetric Multithreading enabled (SMT): %t", report.PlatformInfo.SMT)
writeIndentfln(b, 3, "Transparent secure memory encryption (TSME): %t", platformInfo.TSMEEnabled) writeIndentfln(b, 3, "Transparent secure memory encryption (TSME): %t", report.PlatformInfo.TSME)
writeIndentfln(b, 2, "Signer Info:") writeIndentfln(b, 2, "Signer Info:")
writeIndentfln(b, 3, "Author Key Enabled: %t", signerInfo.AuthorKeyEn) writeIndentfln(b, 3, "Author Key Enabled: %t", report.SignerInfo.AuthorKey)
writeIndentfln(b, 3, "Chip ID Masking: %t", signerInfo.MaskChipKey) writeIndentfln(b, 3, "Chip ID Masking: %t", report.SignerInfo.MaskChipKey)
writeIndentfln(b, 3, "Signing Type: %s", signerInfo.SigningKey) writeIndentfln(b, 3, "Signing Type: %s", report.SignerInfo.SigningKey)
writeIndentfln(b, 2, "Report Data: %x", report.ReportData) writeIndentfln(b, 2, "Report Data: %x", report.ReportData)
writeIndentfln(b, 2, "Measurement: %x", report.Measurement) writeIndentfln(b, 2, "Measurement: %x", report.Measurement)
writeIndentfln(b, 2, "Host Data: %x", report.HostData) writeIndentfln(b, 2, "Host Data: %x", report.HostData)
writeIndentfln(b, 2, "ID Key Digest: %x", report.IdKeyDigest) writeIndentfln(b, 2, "ID Key Digest: %x", report.IDKeyDigest)
writeIndentfln(b, 2, "Author Key Digest: %x", report.AuthorKeyDigest) writeIndentfln(b, 2, "Author Key Digest: %x", report.AuthorKeyDigest)
writeIndentfln(b, 2, "Report ID: %x", report.ReportId) writeIndentfln(b, 2, "Report ID: %x", report.ReportID)
writeIndentfln(b, 2, "Report ID MA: %x", report.ReportIdMa) writeIndentfln(b, 2, "Report ID MA: %x", report.ReportIDMa)
writeIndentfln(b, 2, "Reported TCB:") writeIndentfln(b, 2, "Reported TCB:")
writeTCB(report.ReportedTcb) writeTCB(report.ReportedTCB)
writeIndentfln(b, 2, "Chip ID: %x", report.ChipId) writeIndentfln(b, 2, "Chip ID: %x", report.ChipID)
writeIndentfln(b, 2, "Committed TCB:") writeIndentfln(b, 2, "Committed TCB:")
writeTCB(report.CommittedTcb) writeTCB(report.CommittedTCB)
writeIndentfln(b, 2, "Current Build: %d", report.CurrentBuild) writeIndentfln(b, 2, "Current Build: %d", report.CurrentBuild)
writeIndentfln(b, 2, "Current Minor: %d", report.CurrentMinor) writeIndentfln(b, 2, "Current Minor: %d", report.CurrentMinor)
writeIndentfln(b, 2, "Current Major: %d", report.CurrentMajor) writeIndentfln(b, 2, "Current Major: %d", report.CurrentMajor)
@ -487,15 +532,13 @@ func (f *attestationDocFormatterImpl) parseSNPReport(b *strings.Builder, reportB
writeIndentfln(b, 2, "Committed Minor: %d", report.CommittedMinor) writeIndentfln(b, 2, "Committed Minor: %d", report.CommittedMinor)
writeIndentfln(b, 2, "Committed Major: %d", report.CommittedMajor) writeIndentfln(b, 2, "Committed Major: %d", report.CommittedMajor)
writeIndentfln(b, 2, "Launch TCB:") writeIndentfln(b, 2, "Launch TCB:")
writeTCB(report.LaunchTcb) writeTCB(report.LaunchTCB)
writeIndentfln(b, 2, "Signature (DER):") writeIndentfln(b, 2, "Signature (DER):")
writeIndentfln(b, 3, "%x", signature) writeIndentfln(b, 3, "%x", report.Signature)
return nil
} }
func parseMAAToken(ctx context.Context, b *strings.Builder, rawToken, attestationServiceURL string) error { func parseMAAToken(ctx context.Context, b *strings.Builder, rawToken, attestationServiceURL string) error {
var claims maaTokenClaims var claims verify.MaaTokenClaims
_, err := jwt.ParseWithClaims(rawToken, &claims, keyFromJKUFunc(ctx, attestationServiceURL), jwt.WithIssuedAt()) _, err := jwt.ParseWithClaims(rawToken, &claims, keyFromJKUFunc(ctx, attestationServiceURL), jwt.WithIssuedAt())
if err != nil { if err != nil {
return fmt.Errorf("parsing token: %w", err) return fmt.Errorf("parsing token: %w", err)
@ -568,84 +611,6 @@ func keyFromJKUFunc(ctx context.Context, webKeysURLBase string) func(token *jwt.
} }
} }
type maaTokenClaims struct {
jwt.RegisteredClaims
Secureboot bool `json:"secureboot,omitempty"`
XMsAttestationType string `json:"x-ms-attestation-type,omitempty"`
XMsAzurevmAttestationProtocolVer string `json:"x-ms-azurevm-attestation-protocol-ver,omitempty"`
XMsAzurevmAttestedPcrs []int `json:"x-ms-azurevm-attested-pcrs,omitempty"`
XMsAzurevmBootdebugEnabled bool `json:"x-ms-azurevm-bootdebug-enabled,omitempty"`
XMsAzurevmDbvalidated bool `json:"x-ms-azurevm-dbvalidated,omitempty"`
XMsAzurevmDbxvalidated bool `json:"x-ms-azurevm-dbxvalidated,omitempty"`
XMsAzurevmDebuggersdisabled bool `json:"x-ms-azurevm-debuggersdisabled,omitempty"`
XMsAzurevmDefaultSecurebootkeysvalidated bool `json:"x-ms-azurevm-default-securebootkeysvalidated,omitempty"`
XMsAzurevmElamEnabled bool `json:"x-ms-azurevm-elam-enabled,omitempty"`
XMsAzurevmFlightsigningEnabled bool `json:"x-ms-azurevm-flightsigning-enabled,omitempty"`
XMsAzurevmHvciPolicy int `json:"x-ms-azurevm-hvci-policy,omitempty"`
XMsAzurevmHypervisordebugEnabled bool `json:"x-ms-azurevm-hypervisordebug-enabled,omitempty"`
XMsAzurevmIsWindows bool `json:"x-ms-azurevm-is-windows,omitempty"`
XMsAzurevmKerneldebugEnabled bool `json:"x-ms-azurevm-kerneldebug-enabled,omitempty"`
XMsAzurevmOsbuild string `json:"x-ms-azurevm-osbuild,omitempty"`
XMsAzurevmOsdistro string `json:"x-ms-azurevm-osdistro,omitempty"`
XMsAzurevmOstype string `json:"x-ms-azurevm-ostype,omitempty"`
XMsAzurevmOsversionMajor int `json:"x-ms-azurevm-osversion-major,omitempty"`
XMsAzurevmOsversionMinor int `json:"x-ms-azurevm-osversion-minor,omitempty"`
XMsAzurevmSigningdisabled bool `json:"x-ms-azurevm-signingdisabled,omitempty"`
XMsAzurevmTestsigningEnabled bool `json:"x-ms-azurevm-testsigning-enabled,omitempty"`
XMsAzurevmVmid string `json:"x-ms-azurevm-vmid,omitempty"`
XMsIsolationTee struct {
XMsAttestationType string `json:"x-ms-attestation-type,omitempty"`
XMsComplianceStatus string `json:"x-ms-compliance-status,omitempty"`
XMsRuntime struct {
Keys []struct {
E string `json:"e,omitempty"`
KeyOps []string `json:"key_ops,omitempty"`
Kid string `json:"kid,omitempty"`
Kty string `json:"kty,omitempty"`
N string `json:"n,omitempty"`
} `json:"keys,omitempty"`
VMConfiguration struct {
ConsoleEnabled bool `json:"console-enabled,omitempty"`
CurrentTime int `json:"current-time,omitempty"`
SecureBoot bool `json:"secure-boot,omitempty"`
TpmEnabled bool `json:"tpm-enabled,omitempty"`
VMUniqueID string `json:"vmUniqueId,omitempty"`
} `json:"vm-configuration,omitempty"`
} `json:"x-ms-runtime,omitempty"`
XMsSevsnpvmAuthorkeydigest string `json:"x-ms-sevsnpvm-authorkeydigest,omitempty"`
XMsSevsnpvmBootloaderSvn int `json:"x-ms-sevsnpvm-bootloader-svn,omitempty"`
XMsSevsnpvmFamilyID string `json:"x-ms-sevsnpvm-familyId,omitempty"`
XMsSevsnpvmGuestsvn int `json:"x-ms-sevsnpvm-guestsvn,omitempty"`
XMsSevsnpvmHostdata string `json:"x-ms-sevsnpvm-hostdata,omitempty"`
XMsSevsnpvmIdkeydigest string `json:"x-ms-sevsnpvm-idkeydigest,omitempty"`
XMsSevsnpvmImageID string `json:"x-ms-sevsnpvm-imageId,omitempty"`
XMsSevsnpvmIsDebuggable bool `json:"x-ms-sevsnpvm-is-debuggable,omitempty"`
XMsSevsnpvmLaunchmeasurement string `json:"x-ms-sevsnpvm-launchmeasurement,omitempty"`
XMsSevsnpvmMicrocodeSvn int `json:"x-ms-sevsnpvm-microcode-svn,omitempty"`
XMsSevsnpvmMigrationAllowed bool `json:"x-ms-sevsnpvm-migration-allowed,omitempty"`
XMsSevsnpvmReportdata string `json:"x-ms-sevsnpvm-reportdata,omitempty"`
XMsSevsnpvmReportid string `json:"x-ms-sevsnpvm-reportid,omitempty"`
XMsSevsnpvmSmtAllowed bool `json:"x-ms-sevsnpvm-smt-allowed,omitempty"`
XMsSevsnpvmSnpfwSvn int `json:"x-ms-sevsnpvm-snpfw-svn,omitempty"`
XMsSevsnpvmTeeSvn int `json:"x-ms-sevsnpvm-tee-svn,omitempty"`
XMsSevsnpvmVmpl int `json:"x-ms-sevsnpvm-vmpl,omitempty"`
} `json:"x-ms-isolation-tee,omitempty"`
XMsPolicyHash string `json:"x-ms-policy-hash,omitempty"`
XMsRuntime struct {
ClientPayload struct {
Nonce string `json:"nonce,omitempty"`
} `json:"client-payload,omitempty"`
Keys []struct {
E string `json:"e,omitempty"`
KeyOps []string `json:"key_ops,omitempty"`
Kid string `json:"kid,omitempty"`
Kty string `json:"kty,omitempty"`
N string `json:"n,omitempty"`
} `json:"keys,omitempty"`
} `json:"x-ms-runtime,omitempty"`
XMsVer string `json:"x-ms-ver,omitempty"`
}
// attestationDoc is the attestation document returned by the verifier. // attestationDoc is the attestation document returned by the verifier.
type attestationDoc struct { type attestationDoc struct {
Attestation struct { Attestation struct {
@ -741,3 +706,157 @@ func httpGet(ctx context.Context, url string) ([]byte, error) {
} }
return body, nil return body, nil
} }
func newCertificates(certTypeName string, cert []byte, log debugLog) (certs []verify.Certificate, err error) {
newlinesTrimmed := strings.TrimSpace(string(cert))
log.Debugf("Decoding PEM certificate: %s", certTypeName)
i := 1
var rest []byte
var block *pem.Block
for block, rest = pem.Decode([]byte(newlinesTrimmed)); block != nil; block, rest = pem.Decode(rest) {
log.Debugf("Parsing PEM block: %d", i)
if block.Type != "CERTIFICATE" {
return certs, fmt.Errorf("parse %s: expected PEM block type 'CERTIFICATE', got '%s'", certTypeName, block.Type)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return certs, fmt.Errorf("parse %s: %w", certTypeName, err)
}
if certTypeName == "VCEK certificate" {
vcekExts, err := kds.VcekCertificateExtensions(cert)
if err != nil {
return certs, fmt.Errorf("parsing VCEK certificate extensions: %w", err)
}
certs = append(certs, verify.Certificate{
Certificate: cert,
CertTypeName: certTypeName,
StructVersion: vcekExts.StructVersion,
ProductName: vcekExts.ProductName,
TCBVersion: newTCBVersion(vcekExts.TCBVersion),
HardwareID: vcekExts.HWID,
})
} else {
certs = append(certs, verify.Certificate{
Certificate: cert,
CertTypeName: certTypeName,
})
}
i++
}
if i == 1 {
return certs, fmt.Errorf("parse %s: no PEM blocks found", certTypeName)
}
if len(rest) != 0 {
return certs, fmt.Errorf("parse %s: remaining PEM block is not a valid certificate: %s", certTypeName, rest)
}
return certs, nil
}
func newSNPReport(reportBytes []byte) (res verify.SNPReport, err error) {
report, err := abi.ReportToProto(reportBytes)
if err != nil {
return res, fmt.Errorf("parsing report to proto: %w", err)
}
policy, err := abi.ParseSnpPolicy(report.Policy)
if err != nil {
return res, fmt.Errorf("parsing policy: %w", err)
}
platformInfo, err := abi.ParseSnpPlatformInfo(report.PlatformInfo)
if err != nil {
return res, fmt.Errorf("parsing platform info: %w", err)
}
signature, err := abi.ReportToSignatureDER(reportBytes)
if err != nil {
return res, fmt.Errorf("parsing signature: %w", err)
}
signerInfo, err := abi.ParseSignerInfo(report.SignerInfo)
if err != nil {
return res, fmt.Errorf("parsing signer info: %w", err)
}
return verify.SNPReport{
Version: report.Version,
GuestSvn: report.GuestSvn,
PolicyABIMinor: policy.ABIMinor,
PolicyABIMajor: policy.ABIMajor,
PolicySMT: policy.SMT,
PolicyMigrationAgent: policy.MigrateMA,
PolicyDebug: policy.Debug,
PolicySingleSocket: policy.SingleSocket,
FamilyID: report.FamilyId,
ImageID: report.ImageId,
Vmpl: report.Vmpl,
SignatureAlgo: report.SignatureAlgo,
CurrentTCB: newTCBVersion(kds.TCBVersion(report.CurrentTcb)),
PlatformInfo: verify.PlatformInfo{
SMT: platformInfo.SMTEnabled,
TSME: platformInfo.TSMEEnabled,
},
SignerInfo: verify.SignerInfo{
AuthorKey: signerInfo.AuthorKeyEn,
MaskChipKey: signerInfo.MaskChipKey,
SigningKey: signerInfo.SigningKey,
},
ReportData: report.ReportData,
Measurement: report.Measurement,
HostData: report.HostData,
IDKeyDigest: report.IdKeyDigest,
AuthorKeyDigest: report.AuthorKeyDigest,
ReportID: report.ReportId,
ReportIDMa: report.ReportIdMa,
ReportedTCB: newTCBVersion(kds.TCBVersion(report.ReportedTcb)),
ChipID: report.ChipId,
CommittedTCB: newTCBVersion(kds.TCBVersion(report.CommittedTcb)),
CurrentBuild: report.CurrentBuild,
CurrentMinor: report.CurrentMinor,
CurrentMajor: report.CurrentMajor,
CommittedBuild: report.CommittedBuild,
CommittedMinor: report.CommittedMinor,
CommittedMajor: report.CommittedMajor,
LaunchTCB: newTCBVersion(kds.TCBVersion(report.LaunchTcb)),
Signature: signature,
}, nil
}
func newMAAToken(ctx context.Context, rawToken, attestationServiceURL string) (verify.MaaTokenClaims, error) {
var claims verify.MaaTokenClaims
_, err := jwt.ParseWithClaims(rawToken, &claims, keyFromJKUFunc(ctx, attestationServiceURL), jwt.WithIssuedAt())
return claims, err
}
func newTCBVersion(tcbVersion kds.TCBVersion) (res verify.TCBVersion) {
tcb := kds.DecomposeTCBVersion(tcbVersion)
return verify.TCBVersion{
Bootloader: tcb.BlSpl,
TEE: tcb.TeeSpl,
SNP: tcb.SnpSpl,
Microcode: tcb.UcodeSpl,
Spl4: tcb.Spl4,
Spl5: tcb.Spl5,
Spl6: tcb.Spl6,
Spl7: tcb.Spl7,
}
}
func extractAzureInstanceInfo(docString string) (azureInstanceInfo, error) {
var doc attestationDoc
if err := json.Unmarshal([]byte(docString), &doc); err != nil {
return azureInstanceInfo{}, fmt.Errorf("unmarshal attestation document: %w", err)
}
instanceInfoString, err := base64.StdEncoding.DecodeString(doc.InstanceInfo)
if err != nil {
return azureInstanceInfo{}, fmt.Errorf("decode instance info: %w", err)
}
var instanceInfo azureInstanceInfo
if err := json.Unmarshal(instanceInfoString, &instanceInfo); err != nil {
return azureInstanceInfo{}, fmt.Errorf("unmarshal instance info: %w", err)
}
return instanceInfo, nil
}

View file

@ -167,8 +167,7 @@ func TestVerify(t *testing.T) {
cmd.Flags().String("workspace", "", "") // register persistent flag manually cmd.Flags().String("workspace", "", "") // register persistent flag manually
cmd.Flags().Bool("force", true, "") // register persistent flag manually cmd.Flags().Bool("force", true, "") // register persistent flag manually
out := &bytes.Buffer{} out := &bytes.Buffer{}
cmd.SetOut(out) cmd.SetErr(out)
cmd.SetErr(&bytes.Buffer{})
if tc.clusterIDFlag != "" { if tc.clusterIDFlag != "" {
require.NoError(cmd.Flags().Set("cluster-id", tc.clusterIDFlag)) require.NoError(cmd.Flags().Set("cluster-id", tc.clusterIDFlag))
} }
@ -186,7 +185,10 @@ func TestVerify(t *testing.T) {
} }
v := &verifyCmd{log: logger.NewTest(t)} v := &verifyCmd{log: logger.NewTest(t)}
err := v.verify(cmd, fileHandler, tc.protoClient, tc.formatter, stubAttestationFetcher{}) formatterFac := func(_ string, _ cloudprovider.Provider, _ debugLog) (attestationDocFormatter, error) {
return tc.formatter, nil
}
err := v.verify(cmd, fileHandler, tc.protoClient, formatterFac, stubAttestationFetcher{})
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
} else { } else {
@ -202,19 +204,19 @@ type stubAttDocFormatter struct {
formatErr error formatErr error
} }
func (f *stubAttDocFormatter) format(_ context.Context, _ string, _ bool, _ bool, _ measurements.M, _ string) (string, error) { func (f *stubAttDocFormatter) format(_ context.Context, _ string, _ bool, _ measurements.M, _ string) (string, error) {
return "", f.formatErr return "", f.formatErr
} }
func TestFormat(t *testing.T) { func TestFormat(t *testing.T) {
formatter := func() *attestationDocFormatterImpl { formatter := func() *defaultAttestationDocFormatter {
return &attestationDocFormatterImpl{ return &defaultAttestationDocFormatter{
log: logger.NewTest(t), log: logger.NewTest(t),
} }
} }
testCases := map[string]struct { testCases := map[string]struct {
formatter *attestationDocFormatterImpl formatter *defaultAttestationDocFormatter
doc string doc string
wantErr bool wantErr bool
}{ }{
@ -227,7 +229,7 @@ func TestFormat(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) {
_, err := tc.formatter.format(context.Background(), tc.doc, false, false, nil, "") _, err := tc.formatter.format(context.Background(), tc.doc, false, nil, "")
if tc.wantErr { if tc.wantErr {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
@ -299,7 +301,7 @@ F/SjRih31+SAtWb42jueAA==
assert := assert.New(t) assert := assert.New(t)
b := &strings.Builder{} b := &strings.Builder{}
formatter := &attestationDocFormatterImpl{ formatter := &defaultAttestationDocFormatter{
log: logger.NewTest(t), log: logger.NewTest(t),
} }
err := formatter.parseCerts(b, "Some Cert", tc.cert) err := formatter.parseCerts(b, "Some Cert", tc.cert)
@ -545,7 +547,7 @@ func TestParseQuotes(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
b := &strings.Builder{} b := &strings.Builder{}
parser := &attestationDocFormatterImpl{} parser := &defaultAttestationDocFormatter{}
err := parser.parseQuotes(b, tc.quotes, tc.expectedPCRs) err := parser.parseQuotes(b, tc.quotes, tc.expectedPCRs)
if tc.wantErr { if tc.wantErr {

View file

@ -392,7 +392,7 @@ constellation verify [flags]
--cluster-id string expected cluster identifier --cluster-id string expected cluster identifier
-h, --help help for verify -h, --help help for verify
-e, --node-endpoint string endpoint of the node to verify, passed as HOST[:PORT] -e, --node-endpoint string endpoint of the node to verify, passed as HOST[:PORT]
--raw print raw attestation document -o, --output string print the attestation document in the output format {json|raw}
``` ```
### Options inherited from parent commands ### Options inherited from parent commands

View file

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "verify",
srcs = ["verify.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/verify",
visibility = ["//:__subpackages__"],
deps = ["@com_github_golang_jwt_jwt_v5//:jwt"],
)

179
internal/verify/verify.go Normal file
View file

@ -0,0 +1,179 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package verify provides the types for the verify report in JSON format.
The package provides an interface for constellation verify and
the attestationconfigapi upload tool through JSON serialization.
*/
package verify
import (
"crypto/x509"
"fmt"
"github.com/golang-jwt/jwt/v5"
)
// Report contains the entire data reported by constellation verify.
type Report struct {
SNPReport SNPReport `json:"snp_report"`
VCEK []Certificate `json:"vcek"`
CertChain []Certificate `json:"cert_chain"`
MAAToken MaaTokenClaims `json:"maa_token"`
}
// Certificate contains the certificate data and additional information.
type Certificate struct {
*x509.Certificate `json:"certificate"`
CertTypeName string `json:"cert_type_name"`
StructVersion uint8 `json:"struct_version"`
ProductName string `json:"product_name"`
HardwareID []byte `json:"hardware_id"`
TCBVersion TCBVersion `json:"tcb_version"`
}
// TCBVersion contains the TCB version data.
type TCBVersion struct {
Bootloader uint8 `json:"bootloader"`
TEE uint8 `json:"tee"`
SNP uint8 `json:"snp"`
Microcode uint8 `json:"microcode"`
Spl4 uint8 `json:"spl4"`
Spl5 uint8 `json:"spl5"`
Spl6 uint8 `json:"spl6"`
Spl7 uint8 `json:"spl7"`
}
// PlatformInfo contains the platform information.
type PlatformInfo struct {
SMT bool `json:"smt"`
TSME bool `json:"tsme"`
}
// SignerInfo contains the signer information.
type SignerInfo struct {
AuthorKey bool `json:"author_key_en"`
MaskChipKey bool `json:"mask_chip_key"`
SigningKey fmt.Stringer `json:"signing_key"`
}
// SNPReport contains the SNP report data.
type SNPReport struct {
Version uint32 `json:"version"`
GuestSvn uint32 `json:"guest_svn"`
PolicyABIMinor uint8 `json:"policy_abi_minor"`
PolicyABIMajor uint8 `json:"policy_abi_major"`
PolicySMT bool `json:"policy_symmetric_multi_threading"`
PolicyMigrationAgent bool `json:"policy_migration_agent"`
PolicyDebug bool `json:"policy_debug"`
PolicySingleSocket bool `json:"policy_single_socket"`
FamilyID []byte `json:"family_id"`
ImageID []byte `json:"image_id"`
Vmpl uint32 `json:"vmpl"`
SignatureAlgo uint32 `json:"signature_algo"`
CurrentTCB TCBVersion `json:"current_tcb"`
PlatformInfo PlatformInfo `json:"platform_info"`
SignerInfo SignerInfo `json:"signer_info"`
ReportData []byte `json:"report_data"`
Measurement []byte `json:"measurement"`
HostData []byte `json:"host_data"`
IDKeyDigest []byte `json:"id_key_digest"`
AuthorKeyDigest []byte `json:"author_key_digest"`
ReportID []byte `json:"report_id"`
ReportIDMa []byte `json:"report_id_ma"`
ReportedTCB TCBVersion `json:"reported_tcb"`
ChipID []byte `json:"chip_id"`
CommittedTCB TCBVersion `json:"committed_tcb"`
CurrentBuild uint32 `json:"current_build"`
CurrentMinor uint32 `json:"current_minor"`
CurrentMajor uint32 `json:"current_major"`
CommittedBuild uint32 `json:"committed_build"`
CommittedMinor uint32 `json:"committed_minor"`
CommittedMajor uint32 `json:"committed_major"`
LaunchTCB TCBVersion `json:"launch_tcb"`
Signature []byte `json:"signature"`
}
// MaaTokenClaims contains the MAA token claims.
type MaaTokenClaims struct {
jwt.RegisteredClaims
Secureboot bool `json:"secureboot,omitempty"`
XMsAttestationType string `json:"x-ms-attestation-type,omitempty"`
XMsAzurevmAttestationProtocolVer string `json:"x-ms-azurevm-attestation-protocol-ver,omitempty"`
XMsAzurevmAttestedPcrs []int `json:"x-ms-azurevm-attested-pcrs,omitempty"`
XMsAzurevmBootdebugEnabled bool `json:"x-ms-azurevm-bootdebug-enabled,omitempty"`
XMsAzurevmDbvalidated bool `json:"x-ms-azurevm-dbvalidated,omitempty"`
XMsAzurevmDbxvalidated bool `json:"x-ms-azurevm-dbxvalidated,omitempty"`
XMsAzurevmDebuggersdisabled bool `json:"x-ms-azurevm-debuggersdisabled,omitempty"`
XMsAzurevmDefaultSecurebootkeysvalidated bool `json:"x-ms-azurevm-default-securebootkeysvalidated,omitempty"`
XMsAzurevmElamEnabled bool `json:"x-ms-azurevm-elam-enabled,omitempty"`
XMsAzurevmFlightsigningEnabled bool `json:"x-ms-azurevm-flightsigning-enabled,omitempty"`
XMsAzurevmHvciPolicy int `json:"x-ms-azurevm-hvci-policy,omitempty"`
XMsAzurevmHypervisordebugEnabled bool `json:"x-ms-azurevm-hypervisordebug-enabled,omitempty"`
XMsAzurevmIsWindows bool `json:"x-ms-azurevm-is-windows,omitempty"`
XMsAzurevmKerneldebugEnabled bool `json:"x-ms-azurevm-kerneldebug-enabled,omitempty"`
XMsAzurevmOsbuild string `json:"x-ms-azurevm-osbuild,omitempty"`
XMsAzurevmOsdistro string `json:"x-ms-azurevm-osdistro,omitempty"`
XMsAzurevmOstype string `json:"x-ms-azurevm-ostype,omitempty"`
XMsAzurevmOsversionMajor int `json:"x-ms-azurevm-osversion-major,omitempty"`
XMsAzurevmOsversionMinor int `json:"x-ms-azurevm-osversion-minor,omitempty"`
XMsAzurevmSigningdisabled bool `json:"x-ms-azurevm-signingdisabled,omitempty"`
XMsAzurevmTestsigningEnabled bool `json:"x-ms-azurevm-testsigning-enabled,omitempty"`
XMsAzurevmVmid string `json:"x-ms-azurevm-vmid,omitempty"`
XMsIsolationTee struct {
XMsAttestationType string `json:"x-ms-attestation-type,omitempty"`
XMsComplianceStatus string `json:"x-ms-compliance-status,omitempty"`
XMsRuntime struct {
Keys []struct {
E string `json:"e,omitempty"`
KeyOps []string `json:"key_ops,omitempty"`
Kid string `json:"kid,omitempty"`
Kty string `json:"kty,omitempty"`
N string `json:"n,omitempty"`
} `json:"keys,omitempty"`
VMConfiguration struct {
ConsoleEnabled bool `json:"console-enabled,omitempty"`
CurrentTime int `json:"current-time,omitempty"`
SecureBoot bool `json:"secure-boot,omitempty"`
TpmEnabled bool `json:"tpm-enabled,omitempty"`
VMUniqueID string `json:"vmUniqueId,omitempty"`
} `json:"vm-configuration,omitempty"`
} `json:"x-ms-runtime,omitempty"`
XMsSevsnpvmAuthorkeydigest string `json:"x-ms-sevsnpvm-authorkeydigest,omitempty"`
XMsSevsnpvmBootloaderSvn int `json:"x-ms-sevsnpvm-bootloader-svn,omitempty"`
XMsSevsnpvmFamilyID string `json:"x-ms-sevsnpvm-familyId,omitempty"`
XMsSevsnpvmGuestsvn int `json:"x-ms-sevsnpvm-guestsvn,omitempty"`
XMsSevsnpvmHostdata string `json:"x-ms-sevsnpvm-hostdata,omitempty"`
XMsSevsnpvmIdkeydigest string `json:"x-ms-sevsnpvm-idkeydigest,omitempty"`
XMsSevsnpvmImageID string `json:"x-ms-sevsnpvm-imageId,omitempty"`
XMsSevsnpvmIsDebuggable bool `json:"x-ms-sevsnpvm-is-debuggable,omitempty"`
XMsSevsnpvmLaunchmeasurement string `json:"x-ms-sevsnpvm-launchmeasurement,omitempty"`
XMsSevsnpvmMicrocodeSvn int `json:"x-ms-sevsnpvm-microcode-svn,omitempty"`
XMsSevsnpvmMigrationAllowed bool `json:"x-ms-sevsnpvm-migration-allowed,omitempty"`
XMsSevsnpvmReportdata string `json:"x-ms-sevsnpvm-reportdata,omitempty"`
XMsSevsnpvmReportid string `json:"x-ms-sevsnpvm-reportid,omitempty"`
XMsSevsnpvmSmtAllowed bool `json:"x-ms-sevsnpvm-smt-allowed,omitempty"`
XMsSevsnpvmSnpfwSvn int `json:"x-ms-sevsnpvm-snpfw-svn,omitempty"`
XMsSevsnpvmTeeSvn int `json:"x-ms-sevsnpvm-tee-svn,omitempty"`
XMsSevsnpvmVmpl int `json:"x-ms-sevsnpvm-vmpl,omitempty"`
} `json:"x-ms-isolation-tee,omitempty"`
XMsPolicyHash string `json:"x-ms-policy-hash,omitempty"`
XMsRuntime struct {
ClientPayload struct {
Nonce string `json:"nonce,omitempty"`
} `json:"client-payload,omitempty"`
Keys []struct {
E string `json:"e,omitempty"`
KeyOps []string `json:"key_ops,omitempty"`
Kid string `json:"kid,omitempty"`
Kty string `json:"kty,omitempty"`
N string `json:"n,omitempty"`
} `json:"keys,omitempty"`
} `json:"x-ms-runtime,omitempty"`
XMsVer string `json:"x-ms-ver,omitempty"`
}