Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: print vcek certificate extensions and snp attestation report during verify #2140

Merged
merged 3 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/internal/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ 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",
"@com_github_siderolabs_talos_pkg_machinery//config/encoder",
Expand Down
166 changes: 142 additions & 24 deletions cli/internal/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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"
"google.golang.org/grpc"
Expand Down Expand Up @@ -282,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)
Expand All @@ -308,37 +311,142 @@ func (f *attestationDocFormatterImpl) parseCerts(b *strings.Builder, certTypeNam
return fmt.Errorf("parse %s: %w", certTypeName, err)
}

b.WriteString(fmt.Sprintf("\t%s (%d):\n", certTypeName, i))
b.WriteString(fmt.Sprintf("\t\tSerial Number: %s\n", cert.SerialNumber))
b.WriteString(fmt.Sprintf("\t\tSubject: %s\n", cert.Subject))
b.WriteString(fmt.Sprintf("\t\tIssuer: %s\n", cert.Issuer))
b.WriteString(fmt.Sprintf("\t\tNot Before: %s\n", cert.NotBefore))
b.WriteString(fmt.Sprintf("\t\tNot After: %s\n", cert.NotAfter))
b.WriteString(fmt.Sprintf("\t\tSignature Algorithm: %s\n", cert.SignatureAlgorithm))
b.WriteString(fmt.Sprintf("\t\tPublic Key Algorithm: %s\n", cert.PublicKeyAlgorithm))
writeIndentfln(b, 1, "%s (%d):", certTypeName, i)
writeIndentfln(b, 2, "Serial Number: %s", cert.SerialNumber)
writeIndentfln(b, 2, "Subject: %s", cert.Subject)
writeIndentfln(b, 2, "Issuer: %s", cert.Issuer)
writeIndentfln(b, 2, "Not Before: %s", cert.NotBefore)
writeIndentfln(b, 2, "Not After: %s", cert.NotAfter)
writeIndentfln(b, 2, "Signature Algorithm: %s", cert.SignatureAlgorithm)
writeIndentfln(b, 2, "Public Key Algorithm: %s", cert.PublicKeyAlgorithm)

if certTypeName == "VCEK certificate" {
// Extensions documented in Table 8 and Table 9 of
// https://www.amd.com/system/files/TechDocs/57230.pdf
vcekExts, err := kds.VcekCertificateExtensions(cert)
if err != nil {
return fmt.Errorf("parsing VCEK certificate extensions: %w", err)
}

writeIndentfln(b, 2, "Struct version: %d", vcekExts.StructVersion)
writeIndentfln(b, 2, "Product name: %s", vcekExts.ProductName)
tcb := kds.DecomposeTCBVersion(vcekExts.TCBVersion)
writeIndentfln(b, 2, "Secure Processor bootloader SVN: %d", tcb.BlSpl)
writeIndentfln(b, 2, "Secure Processor operating system SVN: %d", tcb.TeeSpl)
writeIndentfln(b, 2, "SVN 4 (reserved): %d", tcb.Spl4)
writeIndentfln(b, 2, "SVN 5 (reserved): %d", tcb.Spl5)
writeIndentfln(b, 2, "SVN 6 (reserved): %d", tcb.Spl6)
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)
}

i++
}

if len(rest) != 0 {
return fmt.Errorf("parse %s: remaining PEM block is not a valid certificate: %s", certTypeName, rest)
}

return nil
}

// parseQuotes parses the base64-encoded quotes and writes their details to the output builder.
func (f *attestationDocFormatterImpl) parseQuotes(b *strings.Builder, quotes []quote, expectedPCRs measurements.M) error {
b.WriteString("\tQuote:\n")
writeIndentfln(b, 1, "Quote:")
for pcrNum, expectedPCR := range expectedPCRs {
encPCR := quotes[1].Pcrs.Pcrs[fmt.Sprintf("%d", pcrNum)]
actualPCR, err := base64.StdEncoding.DecodeString(encPCR)
if err != nil {
return fmt.Errorf("decode PCR %d: %w", pcrNum, err)
}
b.WriteString(fmt.Sprintf("\t\tPCR %d (Strict: %t):\n", pcrNum, !expectedPCR.ValidationOpt))
b.WriteString(fmt.Sprintf("\t\t\tExpected:\t%x\n", expectedPCR.Expected))
b.WriteString(fmt.Sprintf("\t\t\tActual:\t\t%x\n", actualPCR))
writeIndentfln(b, 2, "PCR %d (Strict: %t):", pcrNum, !expectedPCR.ValidationOpt)
writeIndentfln(b, 3, "Expected:\t%x", expectedPCR.Expected)
writeIndentfln(b, 3, "Actual:\t\t%x", actualPCR)
}
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 {
Expand All @@ -363,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 {
Expand Down Expand Up @@ -413,3 +522,12 @@ type verifyClient interface {
type grpcInsecureDialer interface {
DialInsecure(ctx context.Context, endpoint string) (conn *grpc.ClientConn, err error)
}

// writeIndentfln writes a formatted string to the builder with the given indentation level
// and a newline at the end.
func writeIndentfln(b *strings.Builder, indentLvl int, format string, args ...any) {
for i := 0; i < indentLvl; i++ {
b.WriteByte('\t')
}
b.WriteString(fmt.Sprintf(format+"\n", args...))
}
22 changes: 11 additions & 11 deletions cli/internal/cmd/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}

Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ require (
github.com/google/go-attestation v0.5.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-containerregistry v0.15.2 // indirect
github.com/google/go-sev-guest v0.6.1 // indirect
github.com/google/go-sev-guest v0.6.1
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/logger v1.1.1 // indirect
Expand Down