Skip to content

Commit

Permalink
Merge pull request #155 from THS-on/updates
Browse files Browse the repository at this point in the history
updates
  • Loading branch information
iolivergithub authored Jan 24, 2024
2 parents a3035b3 + 6ce0f40 commit b46ff85
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 30 deletions.
141 changes: 135 additions & 6 deletions ga10/rules/keylime/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package keylime

import (
"a10/configuration"
"a10/operations"
"a10/structures"
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)

func Registration() []structures.Rule {
validateMB := structures.Rule{"keylime_mb", "Checks TPM Measured Boot Log against Keylime", ValidateMB, true}
return []structures.Rule{validateMB}
validateIMA := structures.Rule{"keylime_ima", "Checks TPM Measured Boot Log against Keylime", ValidateIMA, true}
return []structures.Rule{validateMB, validateIMA}
}

type MBRequest struct {
Expand Down Expand Up @@ -44,6 +48,11 @@ func postMBRequest(mb MBRequest) (*MBResponse, error) {
if err != nil {
return nil, err
}

if resp.StatusCode != 200 {
return nil, fmt.Errorf("got invalid response %s", string(respBytes))
}

var res MBResponse
err = json.Unmarshal(respBytes, &res)
if err != nil {
Expand All @@ -53,13 +62,12 @@ func postMBRequest(mb MBRequest) (*MBResponse, error) {
}

func ValidateMB(claim structures.Claim, rule string, ev structures.ExpectedValue, session structures.Session, parameter map[string]interface{}) (structures.ResultValue, string, error) {
imaLogEncoded, ok := claim.Body["eventlog"]
mbLogEncoded, ok := claim.Body["eventlog"]
if !ok {
return structures.Fail, "eventlog cannot be found in claim", nil
}
fmt.Println(parameter)
// Check if required parameters are there
for _, name := range []string{"hash_alg", "pcrs_inquote"} {
for _, name := range []string{"hash_alg", "pcrs_inquote", "pcrscid"} {
_, ok := parameter[name]
if !ok {
return structures.Fail, fmt.Sprintf("parameter is missing %s", name), nil
Expand All @@ -71,17 +79,23 @@ func ValidateMB(claim structures.Claim, rule string, ev structures.ExpectedValue
pcrsInQuote = append(pcrsInQuote, entry.(string))
}

pcrsClaimId := parameter["pcrscid"].(string)
pcrsClaim, err := operations.GetClaimByItemID(pcrsClaimId)
if err != nil {
return structures.Fail, "Could not get PCRs claim", nil
}
pcrs := pcrsClaim.Body[hashAlg].(map[string]interface{})
var decoded structures.KeylimeMBEV

err := decoded.Decode(ev)
err = decoded.Decode(ev)
if err != nil {
return structures.Fail, "Decoding of EV failed", nil
}

req := MBRequest{
AgentID: claim.Header.Element.Name,
HashAlg: hashAlg,
MBMeasurementList: imaLogEncoded.(string),
MBMeasurementList: mbLogEncoded.(string),
PCRsInQuote: pcrsInQuote,
MBRefState: decoded.MBRefstate,
}
Expand All @@ -96,5 +110,120 @@ func ValidateMB(claim structures.Claim, rule string, ev structures.ExpectedValue
if err != nil {
return structures.Fail, "Failed to encode response", nil
}

// Check if the PCRs also match
for k, v := range resp.MBPCRHashes {
// HACK: demo machines firmware has a miss match for PCR0, that cannot explained via the UEFI eventlog
if k == "0" {
continue
}
other, ok := pcrs[k]
if !ok {
return structures.Fail, fmt.Sprintf("PCR %s is not included output from Keylime", k), nil
}
// Keylime trims leading 0s
data := other.(string)
data = strings.TrimLeft(data, "0")
if data != v {
return structures.Fail, fmt.Sprintf("Mismatch for PCR %s. Got: %s, expected %s", k, other, v), nil
}
}

return structures.Success, string(data), nil
}

type IMARequest struct {
AgentID string `json:"agent_id"`
HashAlg string `json:"hash_alg"`
IMAMeasurementList string `json:"ima_measurement_list"`
RuntimePolicy string `json:"runtime_policy"`
PCRVal string `json:"pcrval"`
}

type IMAResponse struct {
Failure string `json:"failure"`
Context string `json:"context"`
}

func postIMARequest(ima IMARequest) (*IMAResponse, error) {
url := configuration.ConfigData.Keylime.ApiUrl + "/ima/validate"
data, err := json.Marshal(ima)
if err != nil {
return nil, err
}

resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
return nil, err
}
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != 200 {
return nil, fmt.Errorf("got invalid response %s", string(respBytes))
}

var res IMAResponse
err = json.Unmarshal(respBytes, &res)
if err != nil {
return nil, err
}
return &res, nil
}

func ValidateIMA(claim structures.Claim, rule string, ev structures.ExpectedValue, session structures.Session, parameter map[string]interface{}) (structures.ResultValue, string, error) {
ima, ok := claim.Body["asciilog"]
if !ok {
return structures.Fail, "IMA log cannot be found in claim", nil
}

imaDecoded, err := base64.StdEncoding.DecodeString(ima.(string))
if err != nil {
return structures.Fail, "Cannot base64 decode IMA log", nil
}

// Check if required parameters are there
for _, name := range []string{"hash_alg", "pcrscid"} {
_, ok := parameter[name]
if !ok {
return structures.Fail, fmt.Sprintf("parameter is missing %s", name), nil
}
}

hashAlg := parameter["hash_alg"].(string)
pcrsClaimId := parameter["pcrscid"].(string)
pcrsClaim, err := operations.GetClaimByItemID(pcrsClaimId)
if err != nil {
return structures.Fail, "Could not get PCR claim", nil
}

pcrs := pcrsClaim.Body[hashAlg].(map[string]interface{})
pcr10 := pcrs["10"].(string)

var runtimePolicy structures.KeylimeIMAEV
err = runtimePolicy.Decode(ev)
if err != nil {
return structures.Fail, "Could not decode EV", nil
}

req := IMARequest{
AgentID: claim.Header.Element.Name,
HashAlg: hashAlg,
PCRVal: pcr10,
IMAMeasurementList: string(imaDecoded),
RuntimePolicy: runtimePolicy.RuntimePolicy,
}

resp, err := postIMARequest(req)
if err != nil {
return structures.Fail, fmt.Sprintf("Communication with Keylime failed %w", err), nil
}

if resp.Failure != "" {
return structures.Fail, fmt.Sprintf("IMA attestation failed: %s, context: %s", resp.Failure, resp.Context), nil
}

return structures.Success, "", nil
}
91 changes: 89 additions & 2 deletions ga10/rules/tpm2rules/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package tpm2rules

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"reflect"
"slices"
"strconv"

"a10/operations"
"a10/structures"

"a10/utilities"
Expand All @@ -15,15 +19,16 @@ import (
)

func Registration() []structures.Rule {

attestedPCRDigest := structures.Rule{"tpm2_attestedValue", "Checks the TPM's reported attested value against the expected value", AttestedPCRDigest, true}
checkPCRSelection := structures.Rule{"tpm2_PCRSelection", "Checks if a quote includes the correct PCRs", checkPCRSelection, true}
checkQuoteDigest256 := structures.Rule{"tpm2_quoteDigest256", "Checks if a claim of PCRs match the hash in the quote (sha256)", checkQuoteDigest256, false}
ruleFirmware := structures.Rule{"tpm2_firmware", "Checks the TPM firmware version against the expected value", FirmwareRule, true}
ruleMagic := structures.Rule{"tpm2_magicNumber", "Checks the quote magic number is 0xFF544347", MagicNumberRule, false}
ruleIsSafe := structures.Rule{"tpm2_safe", "Checks that the value of safe is 1", IsSafe, false}
ruleValidSignature := structures.Rule{"tpm2_validSignature", "Checks that the signature of rule is valid against the signing attestation key", ValidSignature, false}
ruleValidNonce := structures.Rule{"tpm2_validNonce", "Checks that nonce used for the claim matches the nonce in the quote", ValidNonce, false}

return []structures.Rule{ruleFirmware, ruleMagic, attestedPCRDigest, ruleIsSafe, ruleValidSignature, ruleValidNonce}
return []structures.Rule{ruleFirmware, ruleMagic, attestedPCRDigest, ruleIsSafe, ruleValidSignature, ruleValidNonce, checkQuoteDigest256, checkPCRSelection}
}

func IsSafe(claim structures.Claim, rule string, ev structures.ExpectedValue, session structures.Session, parameter map[string]interface{}) (structures.ResultValue, string, error) {
Expand Down Expand Up @@ -137,6 +142,88 @@ func ValidNonce(claim structures.Claim, rule string, ev structures.ExpectedValue
return structures.Success, "nonce matches", nil
}

func checkPCRSelection(claim structures.Claim, rule string, ev structures.ExpectedValue, session structures.Session, parameter map[string]interface{}) (structures.ResultValue, string, error) {
quote, err := getQuote(claim)
if err != nil {
return structures.Fail, "Parsing TPM quote failed", err
}

data, err := quote.Data.Attested.Quote()
if err != nil {
return structures.Fail, "Parsing Attest structure into Quote failed", err
}
selection := utilities.TPMSPCRSelectionToList(data.PCRSelect.PCRSelections)

evSelection, ok := ev.EVS["pcrselection"]
if !ok {
return structures.MissingExpectedValue, "pcrselection not given", err
}
var evSelectionList []int
for _, v := range evSelection.(primitive.A) {
index, err := strconv.Atoi(v.(string))
if err != nil {
return structures.Fail, "pcrselection contains non integer strings", err
}
evSelectionList = append(evSelectionList, index)
}
if len(evSelectionList) != len(selection) {
return structures.Fail, "not the same length", err
}
for _, v := range evSelectionList {
if !slices.Contains(selection, v) {
return structures.Fail, fmt.Sprintf("Index %d is missing in quote", v), err
}
}
return structures.Success, "", err
}

func checkQuoteDigest256(claim structures.Claim, rule string, ev structures.ExpectedValue, session structures.Session, parameter map[string]interface{}) (structures.ResultValue, string, error) {
quote, err := getQuote(claim)
if err != nil {
return structures.Fail, "Parsing TPM quote failed", err
}
pcrsClaimID := parameter["pcrscid"].(string)

pcrsClaim, err := operations.GetClaimByItemID(pcrsClaimID)
if err != nil {
return structures.Fail, "Could not get PCRs claim", err
}

data, err := quote.Data.Attested.Quote()
if err != nil {
return structures.Fail, "Parsing Attest structure into Quote failed", err
}
digest := data.PCRDigest.Buffer
selection := utilities.TPMSPCRSelectionToList(data.PCRSelect.PCRSelections)

sha256Entries := make(map[string]string)
for k, v := range pcrsClaim.Body["sha256"].(map[string]interface{}) {
sha256Entries[k] = v.(string)
}

hash := sha256.New()
for _, pcrIndex := range selection {
pcrIndexS := fmt.Sprintf("%d", pcrIndex)

entry, ok := sha256Entries[pcrIndexS]
if !ok {
return structures.Fail, fmt.Sprintf("PCR index missing in PCR claim: %s", entry), err
}
entryBytes, err := hex.DecodeString(entry)
if err != nil {
return structures.Fail, "Entry not valid hex", err
}
hash.Write(entryBytes)
}

digestPCRs := hash.Sum([]byte{})
if !bytes.Equal(digestPCRs, digest) {
return structures.Fail, "PCRs and hash in quote do not match", err
}

return structures.Success, "PCRs and hash in quote match", err
}

// Constructs AttestableData struct with signature
// TODO find way to cache this in the session object
func getQuote(claim structures.Claim) (*utilities.AttestableData, error) {
Expand Down
10 changes: 10 additions & 0 deletions ga10/structures/expectedValues.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type KeylimeMBEV struct {
MBRefstate string
}

type KeylimeIMAEV struct {
RuntimePolicy string
}

type MarbleRunCoordinatorEV struct {
SecurityVersion uint
UniqueID []byte
Expand Down Expand Up @@ -57,6 +61,12 @@ func (e *KeylimeMBEV) Decode(ev ExpectedValue) error {
return nil
}

func (e *KeylimeIMAEV) Decode(ev ExpectedValue) error {
e.RuntimePolicy = ev.EVS["runtime_policy"].(string)

return nil
}

func (e *MarbleRunInfrastructureEV) Equal(other MarbleRunInfrastructureEV) bool {
return e.UEID == other.UEID &&
e.CPUSVN == other.CPUSVN &&
Expand Down
Loading

0 comments on commit b46ff85

Please sign in to comment.