diff --git a/hack/azure-snp-idkey-digest/Dockerfile b/hack/azure-snp-idkey-digest/Dockerfile new file mode 100644 index 000000000..951f9bf56 --- /dev/null +++ b/hack/azure-snp-idkey-digest/Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:20.04 AS build +RUN apt-get update && apt-get install -y \ + build-essential \ + libcurl4-openssl-dev \ + wget +RUN wget https://packages.microsoft.com/repos/azurecore/pool/main/a/azguestattestation1/azguestattestation1_1.0.2_amd64.deb \ + && apt-get install /azguestattestation1_1.0.2_amd64.deb +RUN wget https://github.com/Azure/confidential-computing-cvm-guest-attestation/raw/4bd89d2808912fbaa319e8853e6f5e1e245d45ca/cvm-guest-attestation-linux-app/main.cpp \ + && sed -i s/test.attest.azure.net/attest.azure.net/ main.cpp \ + && touch Utils.h \ + && g++ -Os -I/usr/include/azguestattestation1 -oclient main.cpp -lazguestattestation + +FROM ubuntu:20.04 +COPY --from=build client azguestattestation1_1.0.2_amd64.deb / +RUN apt-get update && apt-get install -y /azguestattestation1_1.0.2_amd64.deb +ENTRYPOINT ["/client"] diff --git a/hack/azure-snp-idkey-digest/README.md b/hack/azure-snp-idkey-digest/README.md new file mode 100644 index 000000000..85acedfc3 --- /dev/null +++ b/hack/azure-snp-idkey-digest/README.md @@ -0,0 +1,20 @@ +# Obtain current Azure SNP ID key digest + +On Azure, Constellation verifies that the SNP attestation report contains Azure's ID key digest. +Currently, the only way to verify this digest's origin is to perform guest attestation with the help of the Microsoft Azure Attestation (MAA) service. +There's a [sample](https://github.com/Azure/confidential-computing-cvm-guest-attestation) on how to do this, but it's not straightforward. +So we created tooling to make things easier. + +Perform the following steps to get the ID key digest: + +1. Create an Ubuntu CVM on Azure with secure boot enabled and ssh into it. +2. Run + ``` + docker run --rm --privileged -v/sys/kernel/security:/sys/kernel/security ghcr.io/edgelesssys/constellation/get-azure-snp-jwt + ``` + This executes the guest attestation and prints the JWT received from the MAA. (It's the long base64 blob.) +3. Copy the JWT and run **on your local trusted machine**: + ``` + go run verify.go + ``` + On success it prints the ID key digest. diff --git a/hack/azure-snp-idkey-digest/verify.go b/hack/azure-snp-idkey-digest/verify.go new file mode 100644 index 000000000..28fad3566 --- /dev/null +++ b/hack/azure-snp-idkey-digest/verify.go @@ -0,0 +1,106 @@ +// Verify verifies an MAA JWT and prints the SNP ID key digest on success. +package main + +import ( + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "time" + + "gopkg.in/square/go-jose.v2" + "gopkg.in/square/go-jose.v2/jwt" +) + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage:", os.Args[0], "") + return + } + idKeyDigest, err := getIDKeyDigest(os.Args[1]) + if err != nil { + panic(err) + } + fmt.Println("Successfully validated ID key digest:", idKeyDigest) +} + +func getIDKeyDigest(rawToken string) (string, error) { + // Parse token. + token, err := jwt.ParseSigned(rawToken) + if err != nil { + return "", err + } + + // Get JSON Web Key set. + keySetBytes, err := httpGet("https://sharedeus.eus.attest.azure.net/certs") + if err != nil { + return "", err + } + keySet, err := parseKeySet(keySetBytes) + if err != nil { + return "", err + } + + // Get claims. Private claims contain ID Key digest. + + var publicClaims jwt.Claims + + var privateClaims struct { + IsolationTEE struct { + IDKeyDigest string `json:"x-ms-sevsnpvm-idkeydigest"` + } `json:"x-ms-isolation-tee"` + } + + if err := token.Claims(&keySet, &publicClaims, &privateClaims); err != nil { + return "", err + } + if err := publicClaims.Validate(jwt.Expected{Time: time.Now()}); err != nil { + return "", err + } + + return privateClaims.IsolationTEE.IDKeyDigest, 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 +} + +func httpGet(url string) ([]byte, error) { + resp, err := http.Get(url) + 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 +}