mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-06 14:04:17 -04:00
cli: print maa token in verify
Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com>
This commit is contained in:
parent
8dbe79500f
commit
e97b2afc14
8 changed files with 225 additions and 23 deletions
|
@ -15,7 +15,10 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -30,6 +33,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/golang-jwt/jwt/v5"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/kds"
|
||||
"github.com/spf13/afero"
|
||||
|
@ -127,7 +131,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
|
|||
}
|
||||
|
||||
// certificates are only available for Azure
|
||||
attDocOutput, err := formatter.format(rawAttestationDoc, conf.Provider.Azure == nil, flags.rawOutput, attConfig.GetMeasurements())
|
||||
attDocOutput, err := formatter.format(
|
||||
cmd.Context(),
|
||||
rawAttestationDoc,
|
||||
conf.Provider.Azure == nil,
|
||||
flags.rawOutput,
|
||||
attConfig.GetMeasurements(),
|
||||
flags.maaURL,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("printing attestation document: %w", err)
|
||||
}
|
||||
|
@ -240,7 +251,8 @@ func addPortIfMissing(endpoint string, defaultPort int) (string, error) {
|
|||
// an attestationDocFormatter formats the attestation document.
|
||||
type attestationDocFormatter interface {
|
||||
// format returns the raw or formatted attestation doc depending on the rawOutput argument.
|
||||
format(docString string, PCRsOnly bool, rawOutput bool, expectedPCRs measurements.M) (string, error)
|
||||
format(ctx context.Context, docString string, PCRsOnly bool, rawOutput bool, expectedPCRs measurements.M,
|
||||
attestationServiceURL string) (string, error)
|
||||
}
|
||||
|
||||
type attestationDocFormatterImpl struct {
|
||||
|
@ -248,7 +260,9 @@ type attestationDocFormatterImpl struct {
|
|||
}
|
||||
|
||||
// format returns the raw or formatted attestation doc depending on the rawOutput argument.
|
||||
func (f *attestationDocFormatterImpl) format(docString string, PCRsOnly bool, rawOutput bool, expectedPCRs measurements.M) (string, error) {
|
||||
func (f *attestationDocFormatterImpl) format(ctx context.Context, docString string, PCRsOnly bool,
|
||||
rawOutput bool, expectedPCRs measurements.M, attestationServiceURL string,
|
||||
) (string, error) {
|
||||
b := &strings.Builder{}
|
||||
b.WriteString("Attestation Document:\n")
|
||||
if rawOutput {
|
||||
|
@ -287,6 +301,9 @@ func (f *attestationDocFormatterImpl) format(docString string, PCRsOnly bool, ra
|
|||
if err := f.parseSNPReport(b, instanceInfo.AttestationReport); err != nil {
|
||||
return "", fmt.Errorf("print SNP report: %w", err)
|
||||
}
|
||||
if err := parseMAAToken(ctx, b, instanceInfo.MAAToken, attestationServiceURL); err != nil {
|
||||
return "", fmt.Errorf("print MAA token: %w", err)
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
@ -447,6 +464,158 @@ func (f *attestationDocFormatterImpl) parseSNPReport(b *strings.Builder, reportB
|
|||
return nil
|
||||
}
|
||||
|
||||
func parseMAAToken(ctx context.Context, b *strings.Builder, rawToken, attestationServiceURL string) error {
|
||||
var claims maaTokenClaims
|
||||
_, err := jwt.ParseWithClaims(rawToken, &claims, keyFromJKUFunc(ctx, attestationServiceURL), jwt.WithIssuedAt())
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing token: %w", err)
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(claims, "\t\t", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling claims: %w", err)
|
||||
}
|
||||
|
||||
b.WriteString("\tMicrosoft Azure Attestation Token:\n\t")
|
||||
b.WriteString(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyFromJKUFunc returns a function that gets the JSON Web Key URI from the token
|
||||
// and fetches the key from that URI. The keys are then parsed, and the key with
|
||||
// the kid that matches the token header is returned.
|
||||
func keyFromJKUFunc(ctx context.Context, webKeysURLBase string) func(token *jwt.Token) (any, error) {
|
||||
return func(token *jwt.Token) (any, error) {
|
||||
webKeysURL, err := url.JoinPath(webKeysURLBase, "certs")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("joining web keys base URL with path: %w", err)
|
||||
}
|
||||
|
||||
if token.Header["alg"] != "RS256" {
|
||||
return nil, fmt.Errorf("invalid signing algorithm: %s", token.Header["alg"])
|
||||
}
|
||||
kid, ok := token.Header["kid"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid kid: %v", token.Header["kid"])
|
||||
}
|
||||
jku, ok := token.Header["jku"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid jku: %v", token.Header["jku"])
|
||||
}
|
||||
if jku != webKeysURL {
|
||||
return nil, fmt.Errorf("jku from token (%s) does not match configured attestation service (%s)", jku, webKeysURL)
|
||||
}
|
||||
|
||||
keySetBytes, err := httpGet(ctx, jku)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting signing keys from jku %s: %w", jku, err)
|
||||
}
|
||||
|
||||
var rawKeySet struct {
|
||||
Keys []struct {
|
||||
X5c [][]byte
|
||||
Kid string
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(keySetBytes, &rawKeySet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, key := range rawKeySet.Keys {
|
||||
if key.Kid != kid {
|
||||
continue
|
||||
}
|
||||
cert, err := x509.ParseCertificate(key.X5c[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing certificate: %w", err)
|
||||
}
|
||||
|
||||
return cert.PublicKey, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no key found for kid %s", kid)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
type attestationDoc struct {
|
||||
Attestation struct {
|
||||
|
@ -531,3 +700,23 @@ func writeIndentfln(b *strings.Builder, indentLvl int, format string, args ...an
|
|||
}
|
||||
b.WriteString(fmt.Sprintf(format+"\n", args...))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue