mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-21 12:44:20 -04:00
cli: new flag for Azure JSON output of constellation verify
(#2391)
This commit is contained in:
parent
cc4ec80e48
commit
fdd47b7a00
6 changed files with 483 additions and 173 deletions
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
9
internal/verify/BUILD.bazel
Normal file
9
internal/verify/BUILD.bazel
Normal 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
179
internal/verify/verify.go
Normal 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"`
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue