constellation/hack/azure-snp-report-verify/verify.go

167 lines
4.3 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Verify verifies an MAA JWT and prints the SNP ID key digest on success.
package main
import (
2022-09-12 08:39:31 -04:00
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"time"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
// IsolationTEE describes an Azure SNP TEE.
type IsolationTEE struct {
IDKeyDigest string `json:"x-ms-sevsnpvm-idkeydigest"`
TEESvn int `json:"x-ms-sevsnpvm-tee-svn"`
SNPFwSvn int `json:"x-ms-sevsnpvm-snpfw-svn"`
MicrocodeSvn int `json:"x-ms-sevsnpvm-microcode-svn"`
BootloaderSvn int `json:"x-ms-sevsnpvm-bootloader-svn"`
GuestSvn int `json:"x-ms-sevsnpvm-guestsvn"`
}
// PrintSVNs prints the relevant Security Version Numbers (SVNs).
func (i *IsolationTEE) PrintSVNs() {
fmt.Println("\tTEE SVN:", i.TEESvn)
fmt.Println("\tSNP FW SVN:", i.SNPFwSvn)
fmt.Println("\tMicrocode SVN:", i.MicrocodeSvn)
fmt.Println("\tBootloader SVN:", i.BootloaderSvn)
fmt.Println("\tGuest SVN:", i.GuestSvn)
}
func main() {
configAPIExportPath := flag.String("export-path", "azure-sev-snp-version.json", "Path to the exported config API file.")
maaJWT := flag.String("report", "", "MAA JWT report to verify")
flag.Parse()
if *maaJWT == "" {
fmt.Println("Must provide --report")
return
}
report, err := getTEEReport(*maaJWT)
if err != nil {
panic(err)
}
fmt.Println("Successfully validated ID key digest:", report.IDKeyDigest)
fmt.Println("Currently reported SVNs:")
report.PrintSVNs()
if *configAPIExportPath != "" {
configAPIVersion := convertToConfigAPIFile(report)
if err := exportToJSONFile(configAPIVersion, *configAPIExportPath); err != nil {
panic(err)
}
fmt.Println("Successfully exported config API file to:", configAPIExportPath)
}
}
func convertToConfigAPIFile(i IsolationTEE) configapi.AzureSEVSNPVersion {
return configapi.AzureSEVSNPVersion{
Bootloader: uint8(i.BootloaderSvn),
TEE: uint8(i.TEESvn),
SNP: uint8(i.SNPFwSvn),
Microcode: uint8(i.MicrocodeSvn),
}
}
func exportToJSONFile(configAPIVersion configapi.AzureSEVSNPVersion, configAPIExportPath string) error {
data, err := json.Marshal(configAPIVersion)
if err != nil {
return err
}
return os.WriteFile(configAPIExportPath, data, 0o644)
}
func getTEEReport(rawToken string) (IsolationTEE, error) {
// Parse token.
token, err := jwt.ParseSigned(rawToken)
if err != nil {
return IsolationTEE{}, err
}
// Get JSON Web Key set.
2022-09-12 08:39:31 -04:00
keySetBytes, err := httpGet(context.Background(), "https://sharedeus.eus.attest.azure.net/certs")
if err != nil {
return IsolationTEE{}, err
}
keySet, err := parseKeySet(keySetBytes)
if err != nil {
return IsolationTEE{}, err
}
// Get claims. Private claims contain ID Key digest.
var publicClaims jwt.Claims
var privateClaims struct {
IsolationTEE IsolationTEE `json:"x-ms-isolation-tee"`
}
if err := token.Claims(&keySet, &publicClaims, &privateClaims); err != nil {
return IsolationTEE{}, err
}
if err := publicClaims.Validate(jwt.Expected{Time: time.Now()}); err != nil {
return IsolationTEE{}, err
}
return privateClaims.IsolationTEE, nil
}
func parseKeySet(keySetBytes []byte) (jose.JSONWebKeySet, error) {
var rawKeySet struct {
Keys []struct {
X5c []string
Kid string
}
}
if err := json.Unmarshal(keySetBytes, &rawKeySet); err != nil {
return jose.JSONWebKeySet{}, err
}
var keySet jose.JSONWebKeySet
for _, key := range rawKeySet.Keys {
rawCert, _ := base64.StdEncoding.DecodeString(key.X5c[0])
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return jose.JSONWebKeySet{}, err
}
keySet.Keys = append(keySet.Keys, jose.JSONWebKey{KeyID: key.Kid, Key: cert.PublicKey})
}
return keySet, nil
}
2022-09-12 08:39:31 -04:00
func httpGet(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}