add tooling to obtain Azure ID key digest

This commit is contained in:
Thomas Tendyck 2022-08-30 12:28:15 +02:00 committed by Thomas Tendyck
parent 66d8c8037b
commit 2d611e8148
3 changed files with 142 additions and 0 deletions

View File

@ -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"]

View File

@ -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 <jwt>
```
On success it prints the ID key digest.

View File

@ -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], "<maa-jwt>")
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
}