From 372aa0fc08e4739ae90b113d26838ff4a5745126 Mon Sep 17 00:00:00 2001 From: Paul Meyer <49727155+katexochen@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:43:39 +0200 Subject: [PATCH] verify: print formatted SNP report Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com> --- cli/internal/cmd/BUILD.bazel | 1 + cli/internal/cmd/verify.go | 112 ++++++++++++++++++++++++++++---- cli/internal/cmd/verify_test.go | 22 +++---- 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index ddbf6ecd5c..47157035e9 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -83,6 +83,7 @@ go_library( "//internal/versions", "//operators/constellation-node-operator/api/v1alpha1", "//verify/verifyproto", + "@com_github_google_go_sev_guest//abi", "@com_github_google_go_sev_guest//kds", "@com_github_google_uuid//:uuid", "@com_github_mattn_go_isatty//:go-isatty", diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index d2516a344a..86f22d5727 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -30,6 +30,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/grpc/dialer" "github.com/edgelesssys/constellation/v2/verify/verifyproto" + "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -283,22 +284,23 @@ func (f *attestationDocFormatterImpl) format(docString string, PCRsOnly bool, ra if err := f.parseCerts(b, "Certificate chain", instanceInfo.CertChain); err != nil { return "", fmt.Errorf("print certificate chain: %w", err) } + if err := f.parseSNPReport(b, instanceInfo.AttestationReport); err != nil { + return "", fmt.Errorf("print SNP report: %w", err) + } return b.String(), nil } -// parseCerts parses the base64-encoded PEM certificates and writes their details to the output builder. -func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeName string, encCertString string) error { - certBytes, err := base64.StdEncoding.DecodeString(encCertString) - if err != nil { - return fmt.Errorf("decode %s: %w", certTypeName, err) - } - formattedCert := strings.ReplaceAll(string(certBytes[:len(certBytes)-1]), "\n", "\n\t\t") + "\n" +// 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 { + formattedCert := strings.ReplaceAll(string(cert[:len(cert)-1]), "\n", "\n\t\t") + "\n" b.WriteString(fmt.Sprintf("\tRaw %s:\n\t\t%s", certTypeName, formattedCert)) f.log.Debugf("Decoding PEM certificate: %s", certTypeName) i := 1 - for block, rest := pem.Decode(certBytes); block != nil; block, rest = pem.Decode(rest) { + var rest []byte + var block *pem.Block + for block, rest = pem.Decode(cert); block != nil; block, rest = pem.Decode(rest) { f.log.Debugf("Parsing PEM block: %d", i) if block.Type != "CERTIFICATE" { return fmt.Errorf("parse %s: expected PEM block type 'CERTIFICATE', got '%s'", certTypeName, block.Type) @@ -337,12 +339,16 @@ func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeNam writeIndentfln(b, 2, "SVN 7 (reserved): %d", tcb.Spl7) writeIndentfln(b, 2, "SEV-SNP firmware SVN: %d", tcb.SnpSpl) writeIndentfln(b, 2, "Microcode SVN: %d", tcb.UcodeSpl) - writeIndentfln(b, 2, "Hardware ID: %#x", vcekExts.HWID) + writeIndentfln(b, 2, "Hardware ID: %x", vcekExts.HWID) } i++ } + if len(rest) != 0 { + return fmt.Errorf("parse %s: remaining PEM block is not a valid certificate: %s", certTypeName, rest) + } + return nil } @@ -362,6 +368,85 @@ func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []q return nil } +func (f *attestationDocFormatterImpl) parseSNPReport(b *strings.Builder, reportBytes []byte) error { + report, err := abi.ReportToProto(reportBytes) + if err != nil { + return fmt.Errorf("parsing report to proto: %w", err) + } + + 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) + } + + 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 5 (reserved): %d", tcb.Spl5) + writeIndentfln(b, 3, "SVN 6 (reserved): %d", tcb.Spl6) + writeIndentfln(b, 3, "SVN 7 (reserved): %d", tcb.Spl7) + writeIndentfln(b, 3, "SEV-SNP firmware SVN: %d", tcb.SnpSpl) + writeIndentfln(b, 3, "Microcode SVN: %d", tcb.UcodeSpl) + } + + writeIndentfln(b, 1, "SNP Report:") + writeIndentfln(b, 2, "Version: %d", report.Version) + writeIndentfln(b, 2, "Guest SVN: %d", report.GuestSvn) + writeIndentfln(b, 2, "Policy:") + writeIndentfln(b, 3, "ABI Minor: %d", policy.ABIMinor) + writeIndentfln(b, 3, "ABI Major: %d", policy.ABIMajor) + writeIndentfln(b, 3, "Symmetric Multithreading enabled: %t", policy.SMT) + writeIndentfln(b, 3, "Migration agent enabled: %t", policy.MigrateMA) + writeIndentfln(b, 3, "Debugging enabled (host decryption of VM): %t", policy.Debug) + writeIndentfln(b, 3, "Single socket enabled: %t", policy.SingleSocket) + writeIndentfln(b, 2, "Family ID: %x", report.FamilyId) + writeIndentfln(b, 2, "Image ID: %x", report.ImageId) + writeIndentfln(b, 2, "VMPL: %d", report.Vmpl) + writeIndentfln(b, 2, "Signature Algorithm: %d", report.SignatureAlgo) + writeIndentfln(b, 2, "Current TCB:") + writeTCB(report.CurrentTcb) + writeIndentfln(b, 2, "Platform Info:") + writeIndentfln(b, 3, "Symmetric Multithreading enabled (SMT): %t", platformInfo.SMTEnabled) + writeIndentfln(b, 3, "Transparent secure memory encryption (TSME): %t", platformInfo.TSMEEnabled) + writeIndentfln(b, 2, "Author Key ID: %x", report.AuthorKeyEn) + writeIndentfln(b, 2, "Report Data: %x", report.ReportData) + writeIndentfln(b, 2, "Measurement: %x", report.Measurement) + writeIndentfln(b, 2, "Host Data: %x", report.HostData) + writeIndentfln(b, 2, "ID Key Digest: %x", report.IdKeyDigest) + writeIndentfln(b, 2, "Author Key Digest: %x", report.AuthorKeyDigest) + writeIndentfln(b, 2, "Report ID: %x", report.ReportId) + writeIndentfln(b, 2, "Report ID MA: %x", report.ReportIdMa) + writeIndentfln(b, 2, "Reported TCB:") + writeTCB(report.ReportedTcb) + writeIndentfln(b, 2, "Chip ID: %x", report.ChipId) + writeIndentfln(b, 2, "Committed TCB:") + writeTCB(report.CommittedTcb) + writeIndentfln(b, 2, "Current Build: %d", report.CurrentBuild) + writeIndentfln(b, 2, "Current Minor: %d", report.CurrentMinor) + writeIndentfln(b, 2, "Current Major: %d", report.CurrentMajor) + writeIndentfln(b, 2, "Committed Build: %d", report.CommittedBuild) + writeIndentfln(b, 2, "Committed Minor: %d", report.CommittedMinor) + writeIndentfln(b, 2, "Committed Major: %d", report.CommittedMajor) + writeIndentfln(b, 2, "Launch TCB:") + writeTCB(report.LaunchTcb) + writeIndentfln(b, 2, "Signature (DER):") + writeIndentfln(b, 3, "%x", signature) + + return nil +} + // attestationDoc is the attestation document returned by the verifier. type attestationDoc struct { Attestation struct { @@ -386,10 +471,11 @@ type quote struct { // azureInstanceInfo is the b64-decoded InstanceInfo field of the attestation document. // as of now (2023-04-03), it only contains interesting data on Azure. type azureInstanceInfo struct { - Vcek string `json:"Vcek"` - CertChain string `json:"CertChain"` - AttestationReport string `json:"AttestationReport"` - RuntimeData string `json:"RuntimeData"` + Vcek []byte + CertChain []byte + AttestationReport []byte + RuntimeData []byte + MAAToken string } type constellationVerifier struct { diff --git a/cli/internal/cmd/verify_test.go b/cli/internal/cmd/verify_test.go index 6e95eea280..7a0dfbd650 100644 --- a/cli/internal/cmd/verify_test.go +++ b/cli/internal/cmd/verify_test.go @@ -278,20 +278,20 @@ F/SjRih31+SAtWb42jueAA== validCertExpected := "\tRaw Some Cert:\n\t\t-----BEGIN CERTIFICATE-----\n\t\tMIIFTDCCAvugAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA\n\t\toRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATB7MRQwEgYD\n\t\tVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDASBgNVBAcMC1NhbnRhIENs\n\t\tYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5jZWQgTWljcm8gRGV2aWNl\n\t\tczESMBAGA1UEAwwJU0VWLU1pbGFuMB4XDTIyMTEyMzIyMzM0N1oXDTI5MTEyMzIy\n\t\tMzM0N1owejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYD\n\t\tVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2Vk\n\t\tIE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WQ0VLMHYwEAYHKoZIzj0CAQYF\n\t\tK4EEACIDYgAEVGm4GomfpkiziqEYP61nfKaz5OjDLr8Y0POrv4iAnFVHAmBT81Ms\n\t\tgfSLKL5r3V3mNzl1Zh7jwSBft14uhGdwpARoK0YNQc4OvptqVIiv2RprV53DMzge\n\t\trtwiumIargiCo4IBFjCCARIwEAYJKwYBBAGceAEBBAMCAQAwFwYJKwYBBAGceAEC\n\t\tBAoWCE1pbGFuLUIwMBEGCisGAQQBnHgBAwEEAwIBAzARBgorBgEEAZx4AQMCBAMC\n\t\tAQAwEQYKKwYBBAGceAEDBAQDAgEAMBEGCisGAQQBnHgBAwUEAwIBADARBgorBgEE\n\t\tAZx4AQMGBAMCAQAwEQYKKwYBBAGceAEDBwQDAgEAMBEGCisGAQQBnHgBAwMEAwIB\n\t\tCDARBgorBgEEAZx4AQMIBAMCAXMwTQYJKwYBBAGceAEEBEB80kCZ1oAyCjWC6w3m\n\t\txOz+i4t6dFjk/Bqhm7+Jscf8D62CXtlwcKc4aM9CdO4LuKlwpdTU80VNQc6ZEuMF\n\t\tVzbRMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0B\n\t\tAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBA4ICAQCN1qBYOywoZWGnQvk6u0Oh\n\t\t5zkEKykXU6sK8hA6L65rQcqWUjEHDa9AZUpx3UuCmpPc24dx6DTHc58M7TxcyKry\n\t\t8s4CvruBKFbQ6B8MHnH6k07MzsmiBnsiIhAscZ0ipGm6h8e/VM/6ULrAcVSxZ+Mh\n\t\tD/IogZAuCQARsGQ4QYXBT8Qc5mLnTkx30m1rZVlp1VcN4ngOo/1tz1jj1mfpG2zv\n\t\twNcQa9LwAzRLnnmLpxXA2OMbl7AaTWQenpL9rzBON2sg4OBl6lVhaSU0uBbFyCmR\n\t\tRvBqKC0iDD6TvyIikkMq05v5YwIKFYw++ICndz+fKcLEULZbziAsZ52qjM8iPVHC\n\t\tpN0yhVOr2g22F9zxlGH3WxTl9ymUytuv3vJL/aJiQM+n/Ri90Sc05EK4oIJ3+BS8\n\t\tyu5cVy9o2cQcOcQ8rhQh+Kv1sR9xrs25EXZF8KEETfhoJnN6KY1RwG7HsOfAQ3dV\n\t\tLWInQRaC/8JPyVS2zbd0+NRBJOnq4/quv/P3C4SBP98/ZuGrqN59uifyqC3Kodkl\n\t\tWkG/2UdhiLlCmOtsU+BYDZrSiYK1R9FNnlQCOGrkuVxpDwa2TbbvEEzQP7RXxotA\n\t\tKlxejvrY4VuK8agNqvffVofbdIIperK65K4+0mYIb+A6fU8QQHlCbti4ERSZ6UYD\n\t\tF/SjRih31+SAtWb42jueAA==\n\t\t-----END CERTIFICATE-----\n\tSome Cert (1):\n\t\tSerial Number: 0\n\t\tSubject: CN=SEV-VCEK,OU=Engineering,O=Advanced Micro Devices,L=Santa Clara,ST=CA,C=US\n\t\tIssuer: CN=SEV-Milan,OU=Engineering,O=Advanced Micro Devices,L=Santa Clara,ST=CA,C=US\n\t\tNot Before: 2022-11-23 22:33:47 +0000 UTC\n\t\tNot After: 2029-11-23 22:33:47 +0000 UTC\n\t\tSignature Algorithm: SHA384-RSAPSS\n\t\tPublic Key Algorithm: ECDSA\n" testCases := map[string]struct { - formatter *attestationDocFormatterImpl - encodedCert string - expected string - wantErr bool + formatter *attestationDocFormatterImpl + cert []byte + expected string + wantErr bool }{ "one cert": { - formatter: formatter(), - encodedCert: base64.StdEncoding.EncodeToString([]byte(validCert)), - expected: validCertExpected, + formatter: formatter(), + cert: []byte(validCert), + expected: validCertExpected, }, "invalid cert": { - formatter: formatter(), - encodedCert: "invalid", - wantErr: true, + formatter: formatter(), + cert: []byte("invalid"), + wantErr: true, }, } @@ -300,7 +300,7 @@ F/SjRih31+SAtWb42jueAA== assert := assert.New(t) b := &strings.Builder{} - err := tc.formatter.parseCerts(b, "Some Cert", tc.encodedCert) + err := tc.formatter.parseCerts(b, "Some Cert", tc.cert) if tc.wantErr { assert.Error(err) } else {