From 39b0f7f0d65503511dff09bd90f46795eb3bb614 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 11:54:19 +0000 Subject: [PATCH 01/26] update .gitignore Signed-off-by: Simon Ott --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3612f9a6..d6591aef 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ est/estserver/estserver tools/cmc-signing-tool/cmc-signing-tool tools/cmc-converter/cmc-converter tools/fmspc-retrieval-tool/fmspc-retrieval-tool +tools/measurefs/measurefs +tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1 +tools/measure-bundle/measure-bundle tpm/test_encrypted_ak.json example-setup/data-cmc/ example-setup/**/*.cbor @@ -20,4 +23,4 @@ tpmdriver/test_encrypted_ak.json est/server/server attestationreport/cache/* testtool/private.pem -testtool/public.pem \ No newline at end of file +testtool/public.pem From 87cb897bc3324ffd3dd92115a6b1d9bbec0f61e1 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 11:54:50 +0000 Subject: [PATCH 02/26] update go modules Signed-off-by: Simon Ott --- go.mod | 1 + go.sum | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 315ac33a..1f0ec09b 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/dsnet/golib/memfile v1.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/certificate-transparency-go v1.1.6 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/go.sum b/go.sum index 14cfa01d..1ddb38de 100644 --- a/go.sum +++ b/go.sum @@ -18,7 +18,8 @@ github.com/google/certificate-transparency-go v1.1.6/go.mod h1:0OJjOsOk+wj6aYQgP github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888 h1:HURgKPRPJSozDuMHpjdV+iyFVLhB6bi1JanhGgSzI1k= github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888/go.mod h1:xCfWZojUHwedNcs780T8cblW9XHss9XKD2s3U44FVbo= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= From d2d9faa1158c352611bda6f80ef89c80ad756764 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 11:56:03 +0000 Subject: [PATCH 03/26] jsoncannonicalizer: add open source project add the apache licened jsoncanonicalizer source files to be able to generate reproducible JSON container configuration files for measuring container configs. Signed-off-by: Simon Ott --- jsoncanonicalizer/es6numfmt.go | 71 +++++ jsoncanonicalizer/jsoncanonicalizer.go | 378 +++++++++++++++++++++++++ 2 files changed, 449 insertions(+) create mode 100644 jsoncanonicalizer/es6numfmt.go create mode 100644 jsoncanonicalizer/jsoncanonicalizer.go diff --git a/jsoncanonicalizer/es6numfmt.go b/jsoncanonicalizer/es6numfmt.go new file mode 100644 index 00000000..ae17f3d9 --- /dev/null +++ b/jsoncanonicalizer/es6numfmt.go @@ -0,0 +1,71 @@ +// +// Copyright 2006-2019 WebPKI.org (http://webpki.org). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This package converts numbers in IEEE-754 double precision into the +// format specified for JSON in EcmaScript Version 6 and forward. +// The core application for this is canonicalization: +// https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-02 + +package jsoncanonicalizer + +import ( + "errors" + "math" + "strconv" + "strings" +) + +const invalidPattern uint64 = 0x7ff0000000000000 + +func NumberToJSON(ieeeF64 float64) (res string, err error) { + ieeeU64 := math.Float64bits(ieeeF64) + + // Special case: NaN and Infinity are invalid in JSON + if (ieeeU64 & invalidPattern) == invalidPattern { + return "null", errors.New("Invalid JSON number: " + strconv.FormatUint(ieeeU64, 16)) + } + + // Special case: eliminate "-0" as mandated by the ES6-JSON/JCS specifications + if ieeeF64 == 0 { // Right, this line takes both -0 and 0 + return "0", nil + } + + // Deal with the sign separately + var sign string = "" + if ieeeF64 < 0 { + ieeeF64 = -ieeeF64 + sign = "-" + } + + // ES6 has a unique "g" format + var format byte = 'e' + if ieeeF64 < 1e+21 && ieeeF64 >= 1e-6 { + format = 'f' + } + + // The following should do the trick: + es6Formatted := strconv.FormatFloat(ieeeF64, format, -1, 64) + + // Minor cleanup + exponent := strings.IndexByte(es6Formatted, 'e') + if exponent > 0 { + // Go outputs "1e+09" which must be rewritten as "1e+9" + if es6Formatted[exponent+2] == '0' { + es6Formatted = es6Formatted[:exponent+2] + es6Formatted[exponent+3:] + } + } + return sign + es6Formatted, nil +} diff --git a/jsoncanonicalizer/jsoncanonicalizer.go b/jsoncanonicalizer/jsoncanonicalizer.go new file mode 100644 index 00000000..ab7a1be4 --- /dev/null +++ b/jsoncanonicalizer/jsoncanonicalizer.go @@ -0,0 +1,378 @@ +// +// Copyright 2006-2019 WebPKI.org (http://webpki.org). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This package transforms JSON data in UTF-8 according to: +// https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-02 + +package jsoncanonicalizer + +import ( + "container/list" + "errors" + "fmt" + "strconv" + "strings" + "unicode/utf16" +) + +type nameValueType struct { + name string + sortKey []uint16 + value string +} + +// JSON standard escapes (modulo \u) +var asciiEscapes = []byte{'\\', '"', 'b', 'f', 'n', 'r', 't'} +var binaryEscapes = []byte{'\\', '"', '\b', '\f', '\n', '\r', '\t'} + +// JSON literals +var literals = []string{"true", "false", "null"} + +func Transform(jsonData []byte) (result []byte, e error) { + + // JSON data MUST be UTF-8 encoded + var jsonDataLength int = len(jsonData) + + // Current pointer in jsonData + var index int = 0 + + // "Forward" declarations are needed for closures referring each other + var parseElement func() string + var parseSimpleType func() string + var parseQuotedString func() string + var parseObject func() string + var parseArray func() string + + var globalError error = nil + + checkError := func(e error) { + // We only honor the first reported error + if globalError == nil { + globalError = e + } + } + + setError := func(msg string) { + checkError(errors.New(msg)) + } + + isWhiteSpace := func(c byte) bool { + return c == 0x20 || c == 0x0a || c == 0x0d || c == 0x09 + } + + nextChar := func() byte { + if index < jsonDataLength { + c := jsonData[index] + if c > 0x7f { + setError("Unexpected non-ASCII character") + } + index++ + return c + } + setError("Unexpected EOF reached") + return '"' + } + + scan := func() byte { + for { + c := nextChar() + if isWhiteSpace(c) { + continue + } + return c + } + } + + scanFor := func(expected byte) { + c := scan() + if c != expected { + setError("Expected '" + string(expected) + "' but got '" + string(c) + "'") + } + } + + getUEscape := func() rune { + start := index + nextChar() + nextChar() + nextChar() + nextChar() + if globalError != nil { + return 0 + } + u16, err := strconv.ParseUint(string(jsonData[start:index]), 16, 64) + checkError(err) + return rune(u16) + } + + testNextNonWhiteSpaceChar := func() byte { + save := index + c := scan() + index = save + return c + } + + decorateString := func(rawUTF8 string) string { + var quotedString strings.Builder + quotedString.WriteByte('"') + CoreLoop: + for _, c := range []byte(rawUTF8) { + // Is this within the JSON standard escapes? + for i, esc := range binaryEscapes { + if esc == c { + quotedString.WriteByte('\\') + quotedString.WriteByte(asciiEscapes[i]) + continue CoreLoop + } + } + if c < 0x20 { + // Other ASCII control characters must be escaped with \uhhhh + quotedString.WriteString(fmt.Sprintf("\\u%04x", c)) + } else { + quotedString.WriteByte(c) + } + } + quotedString.WriteByte('"') + return quotedString.String() + } + + parseQuotedString = func() string { + var rawString strings.Builder + CoreLoop: + for globalError == nil { + var c byte + if index < jsonDataLength { + c = jsonData[index] + index++ + } else { + nextChar() + break + } + if c == '"' { + break + } + if c < ' ' { + setError("Unterminated string literal") + } else if c == '\\' { + // Escape sequence + c = nextChar() + if c == 'u' { + // The \u escape + firstUTF16 := getUEscape() + if utf16.IsSurrogate(firstUTF16) { + // If the first UTF-16 code unit has a certain value there must be + // another succeeding UTF-16 code unit as well + if nextChar() != '\\' || nextChar() != 'u' { + setError("Missing surrogate") + } else { + // Output the UTF-32 code point as UTF-8 + rawString.WriteRune(utf16.DecodeRune(firstUTF16, getUEscape())) + } + } else { + // Single UTF-16 code identical to UTF-32. Output as UTF-8 + rawString.WriteRune(firstUTF16) + } + } else if c == '/' { + // Benign but useless escape + rawString.WriteByte('/') + } else { + // The JSON standard escapes + for i, esc := range asciiEscapes { + if esc == c { + rawString.WriteByte(binaryEscapes[i]) + continue CoreLoop + } + } + setError("Unexpected escape: \\" + string(c)) + } + } else { + // Just an ordinary ASCII character alternatively a UTF-8 byte + // outside of ASCII. + // Note that properly formatted UTF-8 never clashes with ASCII + // making byte per byte search for ASCII break characters work + // as expected. + rawString.WriteByte(c) + } + } + return rawString.String() + } + + parseSimpleType = func() string { + var token strings.Builder + index-- + for globalError == nil { + c := testNextNonWhiteSpaceChar() + if c == ',' || c == ']' || c == '}' { + break + } + c = nextChar() + if isWhiteSpace(c) { + break + } + token.WriteByte(c) + } + if token.Len() == 0 { + setError("Missing argument") + } + value := token.String() + // Is it a JSON literal? + for _, literal := range literals { + if literal == value { + return literal + } + } + // Apparently not so we assume that it is a I-JSON number + ieeeF64, err := strconv.ParseFloat(value, 64) + checkError(err) + value, err = NumberToJSON(ieeeF64) + checkError(err) + return value + } + + parseElement = func() string { + switch scan() { + case '{': + return parseObject() + case '"': + return decorateString(parseQuotedString()) + case '[': + return parseArray() + default: + return parseSimpleType() + } + } + + parseArray = func() string { + var arrayData strings.Builder + arrayData.WriteByte('[') + var next bool = false + for globalError == nil && testNextNonWhiteSpaceChar() != ']' { + if next { + scanFor(',') + arrayData.WriteByte(',') + } else { + next = true + } + arrayData.WriteString(parseElement()) + } + scan() + arrayData.WriteByte(']') + return arrayData.String() + } + + lexicographicallyPrecedes := func(sortKey []uint16, e *list.Element) bool { + // Find the minimum length of the sortKeys + oldSortKey := e.Value.(nameValueType).sortKey + minLength := len(oldSortKey) + if minLength > len(sortKey) { + minLength = len(sortKey) + } + for q := 0; q < minLength; q++ { + diff := int(sortKey[q]) - int(oldSortKey[q]) + if diff < 0 { + // Smaller => Precedes + return true + } else if diff > 0 { + // Bigger => No match + return false + } + // Still equal => Continue + } + // The sortKeys compared equal up to minLength + if len(sortKey) < len(oldSortKey) { + // Shorter => Precedes + return true + } + if len(sortKey) == len(oldSortKey) { + setError("Duplicate key: " + e.Value.(nameValueType).name) + } + // Longer => No match + return false + } + + parseObject = func() string { + nameValueList := list.New() + var next bool = false + CoreLoop: + for globalError == nil && testNextNonWhiteSpaceChar() != '}' { + if next { + scanFor(',') + } + next = true + scanFor('"') + rawUTF8 := parseQuotedString() + if globalError != nil { + break + } + // Sort keys on UTF-16 code units + // Since UTF-8 doesn't have endianess this is just a value transformation + // In the Go case the transformation is UTF-8 => UTF-32 => UTF-16 + sortKey := utf16.Encode([]rune(rawUTF8)) + scanFor(':') + nameValue := nameValueType{rawUTF8, sortKey, parseElement()} + for e := nameValueList.Front(); e != nil; e = e.Next() { + // Check if the key is smaller than a previous key + if lexicographicallyPrecedes(sortKey, e) { + // Precedes => Insert before and exit sorting + nameValueList.InsertBefore(nameValue, e) + continue CoreLoop + } + // Continue searching for a possibly succeeding sortKey + // (which is straightforward since the list is ordered) + } + // The sortKey is either the first or is succeeding all previous sortKeys + nameValueList.PushBack(nameValue) + } + // Scan away '}' + scan() + // Now everything is sorted so we can properly serialize the object + var objectData strings.Builder + objectData.WriteByte('{') + next = false + for e := nameValueList.Front(); e != nil; e = e.Next() { + if next { + objectData.WriteByte(',') + } + next = true + nameValue := e.Value.(nameValueType) + objectData.WriteString(decorateString(nameValue.name)) + objectData.WriteByte(':') + objectData.WriteString(nameValue.value) + } + objectData.WriteByte('}') + return objectData.String() + } + + ///////////////////////////////////////////////// + // This is where Transform actually begins... // + ///////////////////////////////////////////////// + var transformed string + + if testNextNonWhiteSpaceChar() == '[' { + scan() + transformed = parseArray() + } else { + scanFor('{') + transformed = parseObject() + } + for index < jsonDataLength { + if !isWhiteSpace(jsonData[index]) { + setError("Improperly terminated JSON object") + break + } + index++ + } + return []byte(transformed), globalError +} From 58be690647b8de2de16d1f2784d65cd7b5150e9c Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 11:57:53 +0000 Subject: [PATCH 04/26] measure: add module for measuring OCI runtime bundles The measure module implements functionality to measure OCI runtime bundles (config.json container specification and rootfs) into a specified hardware trust anchor. For now, this module implements the TPM as the hardware trust anchor. Signed-off-by: Simon Ott --- measure/measure.go | 154 ++++++++++++++++++++++++++++++++++++++++++++ measure/rootfs.go | 92 ++++++++++++++++++++++++++ measure/rtconfig.go | 79 +++++++++++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 measure/measure.go create mode 100644 measure/rootfs.go create mode 100644 measure/rtconfig.go diff --git a/measure/measure.go b/measure/measure.go new file mode 100644 index 00000000..15936ab2 --- /dev/null +++ b/measure/measure.go @@ -0,0 +1,154 @@ +// Copyright (c) 2024 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package measure + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "os" + "strings" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/tpmutil" + + "github.com/sirupsen/logrus" +) + +var log = logrus.WithField("service", "measure") + +type MeasureEntry struct { + Pcr int `json:"pcr,omitempty" cbor:"0,keyasint,omitempty"` + TemplateSha256 ar.HexByte `json:"templateSha256,omitempty" cbor:"1,keyasint,omitempty"` + Name string `json:"name,omitempty" cbor:"2,keyasint,omitempty"` + ConfigSha256 ar.HexByte `json:"configSha256,omitempty" cbor:"3,keyasint,omitempty"` + RootfsSha256 ar.HexByte `json:"rootfsSha256,omitempty" cbor:"4,keyasint,omitempty"` +} + +type MeasureConfig struct { + Serializer ar.Serializer + Pcr int + LogFile string + Driver string +} + +func Measure(name string, configSha256, rootfsSha256 []byte, mc *MeasureConfig) error { + + if mc == nil { + return errors.New("internal error: measure config is nil") + } + + // Hash config entry + // TODO discuss if name is required + // tbh := []byte(name) + // tbh = append(tbh, configSha256...) + tbh := []byte(configSha256) + tbh = append(tbh, rootfsSha256...) + + hasher := sha256.New() + hasher.Write(tbh) + hash := hasher.Sum(nil) + + // Generate config entry + entry := MeasureEntry{ + Pcr: mc.Pcr, + TemplateSha256: hash, + Name: name, + ConfigSha256: configSha256, + RootfsSha256: rootfsSha256, + } + + log.Tracef("Recording measurement %v: %v", name, hex.EncodeToString(entry.TemplateSha256)) + + // Read the existing measurement list if it already exists + var measureList []MeasureEntry + if _, err := os.Stat(mc.LogFile); err == nil { + data, err := os.ReadFile(mc.LogFile) + if err != nil { + return fmt.Errorf("failed to read measurement list: %w", err) + } + + err = mc.Serializer.Unmarshal(data, &measureList) + if err != nil { + return fmt.Errorf("failed to unmarshal measurement list: %w", err) + } + } + + // Search the existing measurement list.. + found := false + for _, e := range measureList { + if bytes.Equal(e.TemplateSha256, entry.TemplateSha256) { + found = true + break + } + } + + // ..if the container was already measured, exit + if found { + log.Tracef("Measurement %v already exists, nothing to do", name) + return nil + } + + // ..otherwise append it to the measurement list and record the measurement + measureList = append(measureList, entry) + log.Tracef("Recorded measurement %v into measurement list", name) + + data, err := mc.Serializer.Marshal(measureList) + if err != nil { + return fmt.Errorf("failed to marshal measurement list: %w", err) + } + + err = os.WriteFile(mc.LogFile, data, 0644) + if err != nil { + return fmt.Errorf("failed to write measurement list: %w", err) + } + + if strings.EqualFold(mc.Driver, "tpm") { + addr, err := getTpmAddr() + if err != nil { + return fmt.Errorf("failed to get TPM path: %v", err) + } + + rwc, err := tpm2.OpenTPM(addr) + if err != nil { + return fmt.Errorf("failed to open TPM%v: %w", addr, err) + } + defer rwc.Close() + + err = tpm2.PCRExtend(rwc, tpmutil.Handle(mc.Pcr), tpm2.AlgSHA256, entry.TemplateSha256, "") + if err != nil { + return fmt.Errorf("failed to extend PCR%v: %w", mc.Pcr, err) + } + + log.Tracef("Recorded measurement %v into PCR%v", name, mc.Pcr) + } + + return nil +} + +func getTpmAddr() (string, error) { + // TODO more robust + if _, err := os.Stat("/dev/tpmrm0"); err == nil { + return "/dev/tpmrm0", nil + } else if _, err := os.Stat("/dev/tpm0"); err == nil { + return "/dev/tpm0", nil + } else { + return "", errors.New("failed to find TPM device in /dev") + } +} diff --git a/measure/rootfs.go b/measure/rootfs.go new file mode 100644 index 00000000..5d896138 --- /dev/null +++ b/measure/rootfs.go @@ -0,0 +1,92 @@ +// Copyright (c) 2021 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package measure + +import ( + "archive/tar" + "bytes" + "crypto/sha256" + "fmt" + "io" + "os" + "path/filepath" + "time" +) + +func GetRootfsMeasurement(rootfsPath string) ([]byte, error) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + rootfsPath = filepath.Clean(rootfsPath) + string(os.PathSeparator) + + err := filepath.Walk(rootfsPath, func(file string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + var link string + if fi.Mode()&os.ModeSymlink != 0 { + link, err = os.Readlink(file) + if err != nil { + return err + } + } + + header, err := tar.FileInfoHeader(fi, link) + if err != nil { + return err + } + + header.ModTime = time.Unix(0, 0) + + relativePath, err := filepath.Rel(rootfsPath, file) + if err != nil { + return err + } + header.Name = relativePath + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if !fi.IsDir() && fi.Mode()&os.ModeSymlink == 0 { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.Copy(tw, f); err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to walk through rootfs: %w", err) + } + + if err := tw.Close(); err != nil { + return nil, fmt.Errorf("failed to close tar writer: %w", err) + } + + hasher := sha256.New() + hasher.Write(buf.Bytes()) + hash := hasher.Sum(nil) + + return hash, nil +} diff --git a/measure/rtconfig.go b/measure/rtconfig.go new file mode 100644 index 00000000..c4ce8f08 --- /dev/null +++ b/measure/rtconfig.go @@ -0,0 +1,79 @@ +// Copyright (c) 2021 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package measure + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "strings" + + jcs "github.com/Fraunhofer-AISEC/cmc/jsoncanonicalizer" +) + +func GetConfigMeasurement(id string, configData []byte) ([]byte, []byte, error) { + + // The container id varies depending on the client. For ctr, this is a user-specified + // name. For docker, this is a random UUID which changes on different container invocations + // replace the id with a placeholder to enable reproducible configs + shortId := id + if len(id) > 12 { + shortId = shortId[:12] + } + configString := string(configData) + configString = strings.ReplaceAll(configString, id, "") + configString = strings.ReplaceAll(configString, shortId, "") + reproducibleConfig := []byte(configString) + + // Unmarshal the config for further modifications + var config map[string]interface{} + err := json.Unmarshal(reproducibleConfig, &config) + if err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal OCI runtime config: %w", err) + } + + // Some values from the config, which are not security relevant and may change + // on different container invocations will be ignored + delete(config, "annotations") + delete(config, "root") + delete(config, "hooks") + process, ok := config["process"].(map[string]interface{}) + if ok { + delete(process, "terminal") + delete(process, "consoleSize") + } + linux, ok := config["linux"].(map[string]interface{}) + if ok { + delete(linux, "cgroupsPath") + } + + data, err := json.Marshal(config) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal config: %w", err) + } + + // Transform to RFC 8785 canonical JSON form for reproducible hashing + tbh, err := jcs.Transform(data) + if err != nil { + return nil, nil, fmt.Errorf("failed to cannonicalize json: %w", err) + } + + hasher := sha256.New() + hasher.Write(tbh) + hash := hasher.Sum(nil) + + return hash, tbh, nil +} From 68c3c27096013251ed685a0406b7ab883a7d5ed7 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:00:02 +0000 Subject: [PATCH 05/26] measure: add unit tests Signed-off-by: Simon Ott --- measure/rtconfig_test.go | 393 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 measure/rtconfig_test.go diff --git a/measure/rtconfig_test.go b/measure/rtconfig_test.go new file mode 100644 index 00000000..de0c864c --- /dev/null +++ b/measure/rtconfig_test.go @@ -0,0 +1,393 @@ +// Copyright (c) 2021 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package measure + +import ( + "reflect" + "testing" +) + +func TestGetConfigMeasurement(t *testing.T) { + type args struct { + id string + configData []byte + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "Measure Config Success", + args: args{ + id: "mycontainer", + configData: configData, + }, + want: []byte{0x9e, 0x4a, 0x82, 0x2d, 0xa5, 0x65, 0x2e, 0x40, 0x42, 0x39, 0xd9, 0xb8, 0x02, 0xdd, 0x59, 0x0a, 0x04, 0x13, 0x23, 0x76, 0xdf, 0xc6, 0xe0, 0xe1, 0x29, 0x68, 0x5c, 0x4e, 0x3f, 0x25, 0x35, 0x54}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetConfigMeasurement(tt.args.id, tt.args.configData) + if (err != nil) != tt.wantErr { + t.Errorf("GetConfigMeasurement() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetConfigMeasurement() = %v, want %v", got, tt.want) + } + }) + } +} + +var ( + configData = []byte{ + 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x6f, 0x63, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x2e, 0x30, 0x2e, 0x32, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x3a, 0x20, 0x74, 0x72, + 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x73, 0x65, + 0x72, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x75, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x61, 0x72, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x70, 0x65, + 0x62, 0x62, 0x6c, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x2d, 0x2d, 0x76, 0x65, 0x72, 0x62, 0x6f, + 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x76, 0x22, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x41, 0x54, 0x48, 0x3d, + 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x73, + 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, + 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, + 0x6e, 0x3a, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x62, 0x69, 0x6e, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x45, + 0x52, 0x4d, 0x3d, 0x78, 0x74, 0x65, 0x72, 0x6d, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x48, 0x4f, 0x4d, 0x45, 0x3d, 0x2f, + 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x77, 0x64, 0x22, 0x3a, 0x20, + 0x22, 0x2f, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, + 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3a, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, + 0x41, 0x55, 0x44, 0x49, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, + 0x41, 0x50, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, 0x4e, + 0x45, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, + 0x49, 0x43, 0x45, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x66, 0x66, + 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, + 0x41, 0x55, 0x44, 0x49, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, + 0x41, 0x50, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, 0x4e, + 0x45, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, + 0x49, 0x43, 0x45, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6e, 0x68, + 0x65, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, + 0x50, 0x5f, 0x41, 0x55, 0x44, 0x49, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, + 0x45, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x43, 0x41, 0x50, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, + 0x5f, 0x4e, 0x45, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, + 0x52, 0x56, 0x49, 0x43, 0x45, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, + 0x50, 0x5f, 0x41, 0x55, 0x44, 0x49, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, + 0x45, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x43, 0x41, 0x50, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, + 0x5f, 0x4e, 0x45, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, + 0x52, 0x56, 0x49, 0x43, 0x45, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, + 0x6d, 0x62, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, + 0x41, 0x55, 0x44, 0x49, 0x54, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, + 0x41, 0x50, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x41, 0x50, 0x5f, 0x4e, + 0x45, 0x54, 0x5f, 0x42, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, + 0x49, 0x43, 0x45, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x72, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, + 0x20, 0x22, 0x52, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x5f, 0x4e, 0x4f, 0x46, + 0x49, 0x4c, 0x45, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x68, 0x61, 0x72, 0x64, 0x22, 0x3a, 0x20, 0x31, 0x30, + 0x32, 0x34, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x73, 0x6f, 0x66, 0x74, 0x22, 0x3a, 0x20, 0x31, 0x30, 0x32, 0x34, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x4e, + 0x65, 0x77, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, + 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x22, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x74, 0x68, 0x22, 0x3a, + 0x20, 0x22, 0x72, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x22, 0x0a, 0x20, 0x20, + 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x6d, 0x6f, 0x63, 0x69, 0x2d, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x22, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3a, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x63, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, + 0x3a, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x63, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x64, 0x65, 0x76, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x74, 0x6d, 0x70, 0x66, 0x73, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x74, 0x6d, 0x70, 0x66, 0x73, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x73, 0x75, 0x69, + 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x61, 0x74, 0x69, 0x6d, 0x65, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x37, 0x35, 0x35, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x69, 0x7a, 0x65, + 0x3d, 0x36, 0x35, 0x35, 0x33, 0x36, 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x70, 0x74, 0x73, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x64, 0x65, 0x76, 0x70, 0x74, 0x73, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x64, 0x65, 0x76, 0x70, + 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x73, + 0x75, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x65, 0x77, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x74, 0x6d, 0x78, + 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x30, 0x36, 0x36, 0x36, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x6f, 0x64, + 0x65, 0x3d, 0x30, 0x36, 0x32, 0x30, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x3a, 0x20, 0x22, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x68, 0x6d, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3a, 0x20, 0x22, 0x74, 0x6d, 0x70, 0x66, 0x73, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x73, 0x68, 0x6d, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x73, 0x75, 0x69, 0x64, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, + 0x65, 0x78, 0x65, 0x63, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x64, 0x65, 0x76, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x6f, 0x64, + 0x65, 0x3d, 0x31, 0x37, 0x37, 0x37, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x36, + 0x35, 0x35, 0x33, 0x36, 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, + 0x20, 0x22, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x6d, 0x71, 0x75, 0x65, 0x75, + 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6d, 0x71, 0x75, 0x65, 0x75, + 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6d, 0x71, 0x75, + 0x65, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, + 0x73, 0x75, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, + 0x64, 0x65, 0x76, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, + 0x2f, 0x73, 0x79, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x62, 0x69, + 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x73, + 0x79, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x62, 0x69, + 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6e, 0x6f, 0x73, 0x75, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x65, 0x78, + 0x65, 0x63, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6e, 0x6f, 0x64, 0x65, 0x76, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6f, 0x22, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x72, + 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3a, 0x20, 0x22, 0x62, 0x69, 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x22, 0x3a, 0x20, 0x22, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x72, 0x65, 0x73, + 0x6f, 0x6c, 0x76, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x73, 0x75, 0x69, 0x64, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x6f, 0x64, + 0x65, 0x76, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6e, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x62, 0x69, 0x6e, + 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x72, 0x6f, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x5d, 0x2c, 0x0a, + 0x20, 0x20, 0x22, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x2e, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, + 0x65, 0x22, 0x3a, 0x20, 0x22, 0x61, 0x6d, 0x64, 0x36, 0x34, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x64, 0x69, 0x67, 0x65, 0x73, + 0x74, 0x22, 0x3a, 0x20, 0x22, 0x63, 0x36, 0x38, 0x37, 0x31, 0x61, 0x65, + 0x38, 0x62, 0x35, 0x34, 0x66, 0x62, 0x33, 0x65, 0x64, 0x30, 0x62, 0x61, + 0x34, 0x64, 0x66, 0x34, 0x65, 0x62, 0x39, 0x38, 0x35, 0x32, 0x37, 0x65, + 0x39, 0x61, 0x36, 0x36, 0x39, 0x32, 0x30, 0x38, 0x38, 0x66, 0x65, 0x30, + 0x63, 0x32, 0x66, 0x32, 0x32, 0x36, 0x30, 0x61, 0x39, 0x33, 0x33, 0x34, + 0x38, 0x35, 0x33, 0x30, 0x39, 0x32, 0x62, 0x34, 0x37, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x22, 0x3a, 0x20, 0x22, 0x32, 0x30, 0x32, 0x33, 0x2d, 0x31, 0x30, + 0x2d, 0x30, 0x35, 0x54, 0x30, 0x37, 0x3a, 0x33, 0x33, 0x3a, 0x33, 0x32, + 0x2e, 0x39, 0x30, 0x32, 0x30, 0x33, 0x36, 0x35, 0x35, 0x34, 0x5a, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x41, 0x70, 0x61, + 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x2e, 0x6f, 0x73, 0x22, 0x3a, 0x20, 0x22, 0x6c, 0x69, + 0x6e, 0x75, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, + 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, + 0x72, 0x65, 0x66, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, + 0x6e, 0x67, 0x69, 0x6e, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x2e, 0x73, 0x74, 0x6f, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6e, 0x67, + 0x69, 0x6e, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, + 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x73, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x31, + 0x2e, 0x32, 0x33, 0x2e, 0x33, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x22, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x22, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x69, 0x64, 0x4d, 0x61, 0x70, + 0x70, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x49, 0x44, 0x22, 0x3a, 0x20, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x44, 0x22, + 0x3a, 0x20, 0x31, 0x30, 0x30, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x20, + 0x31, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x69, + 0x64, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x3a, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x22, 0x3a, 0x20, 0x30, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x6f, 0x73, + 0x74, 0x49, 0x44, 0x22, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x30, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x69, 0x7a, + 0x65, 0x22, 0x3a, 0x20, 0x31, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, + 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, + 0x70, 0x69, 0x64, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3a, 0x20, 0x22, 0x69, 0x70, 0x63, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x74, 0x73, 0x22, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, + 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6d, 0x61, 0x73, 0x6b, 0x65, 0x64, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x6b, 0x63, 0x6f, + 0x72, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x70, + 0x72, 0x6f, 0x63, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x65, + 0x62, 0x75, 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x2f, 0x73, 0x79, 0x73, 0x2f, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, + 0x72, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x73, 0x63, 0x73, 0x69, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x50, 0x61, 0x74, 0x68, + 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x61, 0x73, 0x6f, 0x75, 0x6e, + 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, + 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x62, 0x75, 0x73, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, + 0x66, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x69, 0x72, 0x71, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, + 0x2f, 0x73, 0x79, 0x73, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x2f, 0x70, 0x72, 0x6f, 0x63, 0x2f, 0x73, 0x79, 0x73, 0x72, + 0x71, 0x2d, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, + } +) From 6d4d371a992e36fd14d1c06338482f264f677033 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:02:39 +0000 Subject: [PATCH 06/26] tools/measure-bundle: add standalone tool Add standalone tool to measure OCI runtime bundles. The tool is used to generate reference values. Signed-off-by: Simon Ott --- tools/measure-bundle/config.go | 80 ++++++++++++++++++++++++++ tools/measure-bundle/main.go | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 tools/measure-bundle/config.go create mode 100644 tools/measure-bundle/main.go diff --git a/tools/measure-bundle/config.go b/tools/measure-bundle/config.go new file mode 100644 index 00000000..73f8f7cb --- /dev/null +++ b/tools/measure-bundle/config.go @@ -0,0 +1,80 @@ +// Copyright (c) 2024 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/exp/maps" +) + +// Install github packages with "go get [url]" + +type config struct { + config string + rootfs string + args string + logLevel logrus.Level +} + +var ( + logLevels = map[string]logrus.Level{ + "panic": logrus.PanicLevel, + "fatal": logrus.FatalLevel, + "error": logrus.ErrorLevel, + "warn": logrus.WarnLevel, + "info": logrus.InfoLevel, + "debug": logrus.DebugLevel, + "trace": logrus.TraceLevel, + } +) + +func getConfig() (*config, error) { + + configPath := flag.String("config", "", "OCI runtime bundle config path") + rootfsPath := flag.String("rootfs", "", "OCI runtime bundle rootfs path") + args := flag.String("args", "", "OCI runtime bundle additional args") + logLevel := flag.String("log-level", "info", + fmt.Sprintf("Possible logging: %v", strings.Join(maps.Keys(logLevels), ","))) + flag.Parse() + + if *configPath == "" { + flag.Usage() + log.Fatal("OCI runtime bundle config path not provided") + } + if *rootfsPath == "" { + flag.Usage() + log.Fatal("OCI runtime bundle rootfs path not provided") + } + + l, ok := logLevels[strings.ToLower(*logLevel)] + if !ok { + flag.Usage() + log.Fatalf("LogLevel %v does not exist", *logLevel) + } + + c := &config{ + config: *configPath, + rootfs: *rootfsPath, + args: *args, + logLevel: l, + } + + return c, nil +} diff --git a/tools/measure-bundle/main.go b/tools/measure-bundle/main.go new file mode 100644 index 00000000..2e0ba441 --- /dev/null +++ b/tools/measure-bundle/main.go @@ -0,0 +1,102 @@ +// Copyright (c) 2024 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/sirupsen/logrus" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + m "github.com/Fraunhofer-AISEC/cmc/measure" +) + +var ( + log = logrus.WithField("service", "containerd-shim-linux-cmc") +) + +func main() { + + // Parse parameters + c, err := getConfig() + if err != nil { + log.Fatalf("Failed to get config: %v", err) + } + + logrus.SetLevel(c.logLevel) + log.Debug("Running measure-bundle") + + // Print + log.Debugf("Config path: %v", c.config) + log.Debugf("Rootfs path: %v", c.rootfs) + log.Debugf("Arguments : %v", c.args) + + // Record the measurement via the cmcd measure interface + ctrConfig, err := os.ReadFile(c.config) + if err != nil { + log.Fatalf("Failed to read container config: %v", err) + } + + configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + if err != nil { + log.Fatalf("Failed to measure config: %v", err) + } + + rootfsHash, err := m.GetRootfsMeasurement(c.rootfs) + if err != nil { + log.Fatalf("Failed to measure rootfs: %v", err) + } + log.Debug("Generating reference values..") + + // TODO decide on format for reference container reference values (config + rootfs) + // For now: just the hashes + + // Generate reference value array + refs := make([]ar.ReferenceValue, 0) + + // Fill reference values + // TODO make trust anchor and PCR configurable + pcr := 11 + configRef := ar.ReferenceValue{ + Type: "TPM Reference Value", + Name: "OCI Runtime Config", + Sha256: configHash, + Pcr: &pcr, + Optional: true, + } + refs = append(refs, configRef) + + rootfsRef := ar.ReferenceValue{ + Type: "TPM Reference Value", + Name: "OCI Runtime Rootfs", + Sha256: rootfsHash, + Pcr: &pcr, + Optional: true, + } + refs = append(refs, rootfsRef) + + // Marshal reference value array + refData, err := json.MarshalIndent(refs, "", " ") + if err != nil { + log.Fatalf("Failed to marshal reference values: %v", err) + } + + fmt.Printf("%v\n", string(refData)) + + log.Debugf("Finished measure-bundle") +} From 28eda8b5a92c12e472ad2b8d9f687e0920edd8a3 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:05:01 +0000 Subject: [PATCH 07/26] cmcd: Add measure API call Add measure API call for receiving and recording measurements via the measure module to all supported APIs Signed-off-by: Simon Ott --- cmcd/coap.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ cmcd/config.go | 41 ++++++++++++++++++++++++++++++++++++----- cmcd/grpc.go | 35 +++++++++++++++++++++++++++++++++++ cmcd/socket.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/cmcd/coap.go b/cmcd/coap.go index 2636b809..cf672862 100644 --- a/cmcd/coap.go +++ b/cmcd/coap.go @@ -37,6 +37,7 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/cmc" "github.com/Fraunhofer-AISEC/cmc/internal" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) // CoapServer is the CoAP server structure @@ -58,6 +59,7 @@ func (s CoapServer) Serve(addr string, c *cmc.Cmc) error { r.Use(loggingMiddleware) r.Handle("/Attest", mux.HandlerFunc(Attest)) r.Handle("/Verify", mux.HandlerFunc(Verify)) + r.Handle("/Measure", mux.HandlerFunc(Measure)) r.Handle("/TLSSign", mux.HandlerFunc(TlsSign)) r.Handle("/TLSCert", mux.HandlerFunc(TlsCert)) @@ -197,6 +199,49 @@ func Verify(w mux.ResponseWriter, r *mux.Message) { log.Debug("Verifier: Finished") } +func Measure(w mux.ResponseWriter, r *mux.Message) { + + log.Debug("Received Connection Request Type 'Measure Request'") + + var req api.MeasureRequest + err := unmarshalCoapPayload(r, &req) + if err != nil { + sendCoapError(w, r, codes.InternalServerError, + "failed to unmarshal CoAP payload: %v", err) + return + } + + log.Debug("Measurer: Recording measurement") + var success bool + err = m.Measure(req.Name, req.ConfigSha256, req.RootfsSha256, + &m.MeasureConfig{ + Serializer: Cmc.Serializer, + Pcr: Cmc.CtrPcr, + LogFile: Cmc.CtrLog, + Driver: Cmc.CtrDriver, + }) + if err != nil { + success = false + } else { + success = true + } + + // Serialize CoAP payload + resp := api.MeasureResponse{ + Success: success, + } + payload, err := cbor.Marshal(&resp) + if err != nil { + sendCoapError(w, r, codes.InternalServerError, "failed to marshal message: %v", err) + return + } + + // CoAP response + SendCoapResponse(w, r, payload) + + log.Debug("Measurer: Finished") +} + func TlsSign(w mux.ResponseWriter, r *mux.Message) { log.Debug("Received CoAP TLS sign request") diff --git a/cmcd/config.go b/cmcd/config.go index 69b5ecd0..1fabdc68 100644 --- a/cmcd/config.go +++ b/cmcd/config.go @@ -64,6 +64,10 @@ const ( storageFlag = "storage" cacheFlag = "cache" measurementLogFlag = "measurementLog" + ctrFlag = "ctr" + ctrDriverFlag = "ctrdriver" + ctrPcrFlag = "ctrpcr" + ctrLogFlag = "ctrlog" ) func getConfig() (*cmc.Config, error) { @@ -83,18 +87,25 @@ func getConfig() (*cmc.Config, error) { fmt.Sprintf("Drivers (comma separated list). Possible: %v", strings.Join(maps.Keys(cmc.GetDrivers()), ","))) ima := flag.Bool(imaFlag, false, - "Indicates whether to use Integrity Measurement Architecture (IMA)") + "Specifies whether to use Integrity Measurement Architecture (IMA)") pcr := flag.Int(imaPcrFlag, 0, "IMA PCR") keyConfig := flag.String(keyConfigFlag, "", "Key configuration") - api := flag.String(apiFlag, "", "API") + api := flag.String(apiFlag, "", "API to use. Possible: [coap grpc libapi socket]") network := flag.String(networkFlag, "", "Network for socket API [unix tcp]") policyEngine := flag.String(policyEngineFlag, "", - fmt.Sprintf("Possible policy engines: %v", strings.Join(maps.Keys(cmc.GetPolicyEngines()), ","))) + fmt.Sprintf("Possible policy engines: %v", + strings.Join(maps.Keys(cmc.GetPolicyEngines()), ","))) logLevel := flag.String(logFlag, "", fmt.Sprintf("Possible logging: %v", strings.Join(maps.Keys(logLevels), ","))) storage := flag.String(storageFlag, "", "Optional folder to store internal CMC data in") cache := flag.String(cacheFlag, "", "Optional folder to cache metadata for offline backup") - measurementLog := flag.Bool(measurementLogFlag, false, "Indicates whether to include measured events in measurement and validation report") + measurementLog := flag.Bool(measurementLogFlag, false, + "Specifies whether to include measured events in measurement and validation report") + ctr := flag.Bool(ctrFlag, false, "Specifies whether to conduct container measurements") + ctrDriver := flag.String(ctrDriverFlag, "", + "Specifies which driver to use for container measurements") + ctrPcr := flag.Int(ctrPcrFlag, 0, "Container PCR") + ctrLog := flag.String(ctrLogFlag, "", "Container runtime measurements path") flag.Parse() // Create default configuration @@ -160,6 +171,18 @@ func getConfig() (*cmc.Config, error) { if internal.FlagPassed(measurementLogFlag) { c.MeasurementLog = *measurementLog } + if internal.FlagPassed(ctrFlag) { + c.UseCtr = *ctr + } + if internal.FlagPassed(ctrDriverFlag) { + c.CtrDriver = *ctrDriver + } + if internal.FlagPassed(ctrPcrFlag) { + c.CtrPcr = *ctrPcr + } + if internal.FlagPassed(ctrLogFlag) { + c.CtrLog = *ctrLog + } // Configure the logger l, ok := logLevels[strings.ToLower(c.LogLevel)] @@ -224,7 +247,9 @@ func printConfig(c *cmc.Config) { log.Debugf("\tProvisioning Server URL : %v", c.ProvServerAddr) log.Debugf("\tMetadata Locations : %v", strings.Join(c.Metadata, ",")) log.Debugf("\tUse IMA : %v", c.UseIma) - log.Debugf("\tIMA PCR : %v", c.ImaPcr) + if c.UseIma { + log.Debugf("\tIMA PCR : %v", c.ImaPcr) + } log.Debugf("\tAPI : %v", c.Api) log.Debugf("\tNetwork : %v", c.Network) log.Debugf("\tPolicy Engine : %v", c.PolicyEngine) @@ -232,6 +257,12 @@ func printConfig(c *cmc.Config) { log.Debugf("\tLogging Level : %v", c.LogLevel) log.Debugf("\tDrivers : %v", strings.Join(c.Drivers, ",")) log.Debugf("\tMeasurement Log : %v", c.MeasurementLog) + log.Debugf("\tMeasure containers : %v", c.UseCtr) + if c.UseCtr { + log.Debugf("\tContainer Driver : %v", c.CtrDriver) + log.Debugf("\tContainer Measurements : %v", c.CtrLog) + log.Debugf("\tContainer PCR : %v", c.CtrPcr) + } if c.Storage != "" { log.Debugf("\tInternal storage path : %v", c.Storage) } diff --git a/cmcd/grpc.go b/cmcd/grpc.go index eb5dd423..e9295584 100644 --- a/cmcd/grpc.go +++ b/cmcd/grpc.go @@ -38,6 +38,7 @@ import ( "github.com/Fraunhofer-AISEC/cmc/cmc" api "github.com/Fraunhofer-AISEC/cmc/grpcapi" "github.com/Fraunhofer-AISEC/cmc/internal" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) type GrpcServerWrapper struct{} @@ -150,6 +151,40 @@ func (s *GrpcServer) Verify(ctx context.Context, in *api.VerificationRequest) (* return response, nil } +func (s *GrpcServer) Measure(ctx context.Context, in *api.MeasureRequest) (*api.MeasureResponse, error) { + + var status api.Status + var success bool + + log.Info("Received Connection Request Type 'Measure Request'") + + log.Info("Measurer: Recording measurement") + err := m.Measure(in.Name, in.ConfigSha256, in.RootfsSha256, + &m.MeasureConfig{ + Serializer: s.cmc.Serializer, + Pcr: s.cmc.CtrPcr, + LogFile: s.cmc.CtrLog, + Driver: s.cmc.CtrDriver, + }) + if err != nil { + log.Errorf("Failed to record measurement: %v", err) + success = false + status = api.Status_FAIL + } else { + success = true + status = api.Status_OK + } + + response := &api.MeasureResponse{ + Status: status, + Success: success, + } + + log.Info("Measure: Finished") + + return response, nil +} + func (s *GrpcServer) TLSSign(ctx context.Context, in *api.TLSSignRequest) (*api.TLSSignResponse, error) { var err error var sr *api.TLSSignResponse diff --git a/cmcd/socket.go b/cmcd/socket.go index 00681501..dc6556b6 100644 --- a/cmcd/socket.go +++ b/cmcd/socket.go @@ -36,6 +36,7 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/cmc" "github.com/Fraunhofer-AISEC/cmc/internal" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) // Server is the server structure @@ -89,6 +90,8 @@ func handleIncoming(conn net.Conn, cmc *cmc.Cmc) { attest(conn, payload, cmc) case api.TypeVerify: verify(conn, payload, cmc) + case api.TypeMeasure: + measure(conn, payload, cmc) case api.TypeTLSCert: tlscert(conn, payload, cmc) case api.TypeTLSSign: @@ -191,6 +194,50 @@ func verify(conn net.Conn, payload []byte, cmc *cmc.Cmc) { log.Debug("Verifier: Finished") } +func measure(conn net.Conn, payload []byte, cmc *cmc.Cmc) { + + log.Debug("Received Connection Request Type 'Measure Request'") + + req := new(api.MeasureRequest) + err := cbor.Unmarshal(payload, req) + if err != nil { + api.SendError(conn, "Failed to unmarshal measure request: %v", err) + return + } + + log.Debug("Measurer: recording measurement") + var success bool + err = m.Measure(req.Name, req.ConfigSha256, req.RootfsSha256, + &m.MeasureConfig{ + Serializer: cmc.Serializer, + Pcr: cmc.CtrPcr, + LogFile: cmc.CtrLog, + Driver: cmc.CtrDriver, + }) + if err != nil { + success = false + } else { + success = true + } + + // Serialize payload + resp := api.MeasureResponse{ + Success: success, + } + data, err := cbor.Marshal(&resp) + if err != nil { + api.SendError(conn, "failed to marshal message: %v", err) + return + } + + err = api.Send(conn, data, api.TypeMeasure) + if err != nil { + api.SendError(conn, "failed to send: %v", err) + } + + log.Debug("Measurer: Finished") +} + func tlssign(conn net.Conn, payload []byte, cmc *cmc.Cmc) { log.Debug("Received TLS sign request") From a0f67ac492e86fa5531ce844b49abe2c62aa87f3 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:06:17 +0000 Subject: [PATCH 08/26] cmc: add configuration for container measurements Signed-off-by: Simon Ott --- cmc/cmc.go | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/cmc/cmc.go b/cmc/cmc.go index 4317091d..08d41262 100644 --- a/cmc/cmc.go +++ b/cmc/cmc.go @@ -22,6 +22,7 @@ import ( "github.com/sirupsen/logrus" ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/Fraunhofer-AISEC/cmc/internal" ) var ( @@ -39,17 +40,22 @@ type Config struct { Addr string `json:"addr"` ProvServerAddr string `json:"provServerAddr"` Metadata []string `json:"metadata"` - Drivers []string `json:"drivers"` // TPM, SNP - UseIma bool `json:"useIma"` // TRUE, FALSE - ImaPcr int `json:"imaPcr"` // 10-15 - KeyConfig string `json:"keyConfig,omitempty"` // RSA2048 RSA4096 EC256 EC384 EC521 - Api string `json:"api"` // gRPC, CoAP, Socket - Network string `json:"network,omitempty"` // unix, socket - PolicyEngine string `json:"policyEngine,omitempty"` // JS, DUKTAPE + Drivers []string `json:"drivers"` + UseIma bool `json:"useIma"` + ImaPcr int `json:"imaPcr"` + KeyConfig string `json:"keyConfig,omitempty"` + Api string `json:"api"` + Network string `json:"network,omitempty"` + PolicyEngine string `json:"policyEngine,omitempty"` LogLevel string `json:"logLevel,omitempty"` Storage string `json:"storage,omitempty"` Cache string `json:"cache,omitempty"` MeasurementLog bool `json:"measurementLog,omitempty"` + // Only for container measurements + UseCtr bool `json:"useCtr,omitempty"` + CtrDriver string `json:"ctrDriver,omitempty"` + CtrPcr int `json:"ctrPcr,omitempty"` + CtrLog string `json:"ctrLog"` } type Cmc struct { @@ -59,6 +65,10 @@ type Cmc struct { Serializer ar.Serializer Network string IntelStorage string + UseCtr bool + CtrDriver string + CtrPcr int + CtrLog string } func GetDrivers() map[string]ar.Driver { @@ -85,6 +95,9 @@ func NewCmc(c *Config) (*Cmc, error) { ImaPcr: c.ImaPcr, MeasurementLog: c.MeasurementLog, Serializer: s, + CtrPcr: c.CtrPcr, + CtrLog: c.CtrLog, + UseCtr: c.UseCtr, } // Get policy engine @@ -107,6 +120,14 @@ func NewCmc(c *Config) (*Cmc, error) { usedDrivers = append(usedDrivers, d) } + // Check container driver + if c.UseCtr { + if !internal.Contains(c.CtrDriver, c.Drivers) { + return nil, fmt.Errorf("cannot use %v as container driver: driver not configured", + c.CtrDriver) + } + } + cmc := &Cmc{ Metadata: metadata, PolicyEngineSelect: sel, @@ -114,6 +135,10 @@ func NewCmc(c *Config) (*Cmc, error) { Serializer: s, Network: c.Network, IntelStorage: c.Storage, + UseCtr: c.UseCtr, + CtrDriver: c.CtrDriver, + CtrPcr: c.CtrPcr, + CtrLog: c.CtrLog, } return cmc, nil From 232e046747f2e2f132a06ec7b72965b46854e7c8 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:06:42 +0000 Subject: [PATCH 09/26] grpcapi: add measure API call Signed-off-by: Simon Ott --- grpcapi/grpcapi.pb.go | 279 +++++++++++++++++++++++++++++-------- grpcapi/grpcapi.proto | 12 ++ grpcapi/grpcapi_grpc.pb.go | 38 ++++- 3 files changed, 270 insertions(+), 59 deletions(-) diff --git a/grpcapi/grpcapi.pb.go b/grpcapi/grpcapi.pb.go index b08dcc88..0b9eab86 100644 --- a/grpcapi/grpcapi.pb.go +++ b/grpcapi/grpcapi.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.12.4 +// protoc-gen-go v1.30.0 +// protoc v3.14.0 // source: grpcapi.proto package grpcapi @@ -692,6 +692,124 @@ func (x *VerificationResponse) GetVerificationResult() []byte { return nil } +type MeasureRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ConfigSha256 []byte `protobuf:"bytes,2,opt,name=ConfigSha256,proto3" json:"ConfigSha256,omitempty"` + RootfsSha256 []byte `protobuf:"bytes,3,opt,name=RootfsSha256,proto3" json:"RootfsSha256,omitempty"` +} + +func (x *MeasureRequest) Reset() { + *x = MeasureRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpcapi_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MeasureRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MeasureRequest) ProtoMessage() {} + +func (x *MeasureRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpcapi_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MeasureRequest.ProtoReflect.Descriptor instead. +func (*MeasureRequest) Descriptor() ([]byte, []int) { + return file_grpcapi_proto_rawDescGZIP(), []int{9} +} + +func (x *MeasureRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *MeasureRequest) GetConfigSha256() []byte { + if x != nil { + return x.ConfigSha256 + } + return nil +} + +func (x *MeasureRequest) GetRootfsSha256() []byte { + if x != nil { + return x.RootfsSha256 + } + return nil +} + +type MeasureResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status Status `protobuf:"varint,1,opt,name=status,proto3,enum=grpcapi.Status" json:"status,omitempty"` + Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *MeasureResponse) Reset() { + *x = MeasureResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpcapi_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MeasureResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MeasureResponse) ProtoMessage() {} + +func (x *MeasureResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpcapi_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MeasureResponse.ProtoReflect.Descriptor instead. +func (*MeasureResponse) Descriptor() ([]byte, []int) { + return file_grpcapi_proto_rawDescGZIP(), []int{10} +} + +func (x *MeasureResponse) GetStatus() Status { + if x != nil { + return x.Status + } + return Status_OK +} + +func (x *MeasureResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + var File_grpcapi_proto protoreflect.FileDescriptor var file_grpcapi_proto_rawDesc = []byte{ @@ -749,47 +867,63 @@ var file_grpcapi_proto_rawDesc = []byte{ 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, 0x2f, 0x0a, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x08, 0x0a, - 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, - 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x92, 0x02, 0x0a, - 0x0c, 0x48, 0x61, 0x73, 0x68, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, - 0x04, 0x53, 0x48, 0x41, 0x31, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x32, 0x32, - 0x34, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x02, 0x12, - 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x53, - 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x4d, 0x44, 0x34, 0x10, 0x05, - 0x12, 0x07, 0x0a, 0x03, 0x4d, 0x44, 0x35, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x44, 0x35, - 0x53, 0x48, 0x41, 0x31, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x49, 0x50, 0x45, 0x4d, 0x44, - 0x31, 0x36, 0x30, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x32, 0x32, - 0x34, 0x10, 0x09, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x32, 0x35, 0x36, 0x10, - 0x0a, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x33, 0x38, 0x34, 0x10, 0x0b, 0x12, - 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x35, 0x31, 0x32, 0x10, 0x0c, 0x12, 0x0e, 0x0a, - 0x0a, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x32, 0x32, 0x34, 0x10, 0x0d, 0x12, 0x0e, 0x0a, - 0x0a, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x0e, 0x12, 0x0f, 0x0a, - 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x73, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x0f, 0x12, 0x0f, - 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x10, 0x12, - 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, 0x33, 0x38, 0x34, 0x10, 0x11, - 0x12, 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, 0x35, 0x31, 0x32, 0x10, - 0x12, 0x32, 0x9c, 0x02, 0x0a, 0x0a, 0x43, 0x4d, 0x43, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x3e, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x17, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, - 0x4c, 0x53, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x3e, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, - 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x45, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, - 0x69, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x06, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x79, 0x12, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x6c, 0x0a, 0x0e, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x68, 0x61, 0x32, 0x35, + 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, + 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x53, + 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x52, 0x6f, 0x6f, + 0x74, 0x66, 0x73, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x22, 0x54, 0x0a, 0x0f, 0x4d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2a, + 0x2f, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, + 0x00, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4e, + 0x4f, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x02, + 0x2a, 0x92, 0x02, 0x0a, 0x0c, 0x48, 0x61, 0x73, 0x68, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x48, 0x41, 0x31, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, + 0x48, 0x41, 0x32, 0x32, 0x34, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x03, 0x12, + 0x0a, 0x0a, 0x06, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x4d, + 0x44, 0x34, 0x10, 0x05, 0x12, 0x07, 0x0a, 0x03, 0x4d, 0x44, 0x35, 0x10, 0x06, 0x12, 0x0b, 0x0a, + 0x07, 0x4d, 0x44, 0x35, 0x53, 0x48, 0x41, 0x31, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x49, + 0x50, 0x45, 0x4d, 0x44, 0x31, 0x36, 0x30, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, + 0x33, 0x5f, 0x32, 0x32, 0x34, 0x10, 0x09, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, + 0x32, 0x35, 0x36, 0x10, 0x0a, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x33, 0x38, + 0x34, 0x10, 0x0b, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x35, 0x31, 0x32, 0x10, + 0x0c, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x32, 0x32, 0x34, 0x10, + 0x0d, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x32, 0x35, 0x36, 0x10, + 0x0e, 0x12, 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x73, 0x5f, 0x32, 0x35, 0x36, + 0x10, 0x0f, 0x12, 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, 0x32, 0x35, + 0x36, 0x10, 0x10, 0x12, 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, 0x33, + 0x38, 0x34, 0x10, 0x11, 0x12, 0x0f, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x62, 0x5f, + 0x35, 0x31, 0x32, 0x10, 0x12, 0x32, 0xdc, 0x02, 0x0a, 0x0a, 0x43, 0x4d, 0x43, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x53, 0x69, 0x67, 0x6e, 0x12, + 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x53, 0x69, 0x67, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, + 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x12, + 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, + 0x70, 0x69, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1b, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x06, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, + 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x07, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, + 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x61, + 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x61, + 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -805,7 +939,7 @@ func file_grpcapi_proto_rawDescGZIP() []byte { } var file_grpcapi_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_grpcapi_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_grpcapi_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_grpcapi_proto_goTypes = []interface{}{ (Status)(0), // 0: grpcapi.Status (HashFunction)(0), // 1: grpcapi.HashFunction @@ -818,6 +952,8 @@ var file_grpcapi_proto_goTypes = []interface{}{ (*AttestationResponse)(nil), // 8: grpcapi.AttestationResponse (*VerificationRequest)(nil), // 9: grpcapi.VerificationRequest (*VerificationResponse)(nil), // 10: grpcapi.VerificationResponse + (*MeasureRequest)(nil), // 11: grpcapi.MeasureRequest + (*MeasureResponse)(nil), // 12: grpcapi.MeasureResponse } var file_grpcapi_proto_depIdxs = []int32{ 1, // 0: grpcapi.TLSSignRequest.hashtype:type_name -> grpcapi.HashFunction @@ -826,19 +962,22 @@ var file_grpcapi_proto_depIdxs = []int32{ 0, // 3: grpcapi.TLSCertResponse.status:type_name -> grpcapi.Status 0, // 4: grpcapi.AttestationResponse.status:type_name -> grpcapi.Status 0, // 5: grpcapi.VerificationResponse.status:type_name -> grpcapi.Status - 3, // 6: grpcapi.CMCService.TLSSign:input_type -> grpcapi.TLSSignRequest - 5, // 7: grpcapi.CMCService.TLSCert:input_type -> grpcapi.TLSCertRequest - 7, // 8: grpcapi.CMCService.Attest:input_type -> grpcapi.AttestationRequest - 9, // 9: grpcapi.CMCService.Verify:input_type -> grpcapi.VerificationRequest - 4, // 10: grpcapi.CMCService.TLSSign:output_type -> grpcapi.TLSSignResponse - 6, // 11: grpcapi.CMCService.TLSCert:output_type -> grpcapi.TLSCertResponse - 8, // 12: grpcapi.CMCService.Attest:output_type -> grpcapi.AttestationResponse - 10, // 13: grpcapi.CMCService.Verify:output_type -> grpcapi.VerificationResponse - 10, // [10:14] is the sub-list for method output_type - 6, // [6:10] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 0, // 6: grpcapi.MeasureResponse.status:type_name -> grpcapi.Status + 3, // 7: grpcapi.CMCService.TLSSign:input_type -> grpcapi.TLSSignRequest + 5, // 8: grpcapi.CMCService.TLSCert:input_type -> grpcapi.TLSCertRequest + 7, // 9: grpcapi.CMCService.Attest:input_type -> grpcapi.AttestationRequest + 9, // 10: grpcapi.CMCService.Verify:input_type -> grpcapi.VerificationRequest + 11, // 11: grpcapi.CMCService.Measure:input_type -> grpcapi.MeasureRequest + 4, // 12: grpcapi.CMCService.TLSSign:output_type -> grpcapi.TLSSignResponse + 6, // 13: grpcapi.CMCService.TLSCert:output_type -> grpcapi.TLSCertResponse + 8, // 14: grpcapi.CMCService.Attest:output_type -> grpcapi.AttestationResponse + 10, // 15: grpcapi.CMCService.Verify:output_type -> grpcapi.VerificationResponse + 12, // 16: grpcapi.CMCService.Measure:output_type -> grpcapi.MeasureResponse + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_grpcapi_proto_init() } @@ -955,6 +1094,30 @@ func file_grpcapi_proto_init() { return nil } } + file_grpcapi_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MeasureRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpcapi_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MeasureResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -962,7 +1125,7 @@ func file_grpcapi_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_grpcapi_proto_rawDesc, NumEnums: 2, - NumMessages: 9, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/grpcapi/grpcapi.proto b/grpcapi/grpcapi.proto index 96ec438c..58bf50a7 100644 --- a/grpcapi/grpcapi.proto +++ b/grpcapi/grpcapi.proto @@ -52,6 +52,7 @@ service CMCService { rpc TLSCert(TLSCertRequest) returns (TLSCertResponse) {} rpc Attest(AttestationRequest) returns (AttestationResponse) {} rpc Verify(VerificationRequest) returns (VerificationResponse) {} + rpc Measure(MeasureRequest) returns (MeasureResponse) {} } message PSSOptions { @@ -101,3 +102,14 @@ message VerificationResponse { Status status = 1; bytes verification_result = 2; } + +message MeasureRequest { + string name = 1; + bytes ConfigSha256 = 2; + bytes RootfsSha256 = 3; +} + +message MeasureResponse { + Status status = 1; + bool success = 2; +} \ No newline at end of file diff --git a/grpcapi/grpcapi_grpc.pb.go b/grpcapi/grpcapi_grpc.pb.go index be359f4c..ab3d397c 100644 --- a/grpcapi/grpcapi_grpc.pb.go +++ b/grpcapi/grpcapi_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.12.4 +// - protoc v3.14.0 // source: grpcapi.proto package grpcapi @@ -27,6 +27,7 @@ type CMCServiceClient interface { TLSCert(ctx context.Context, in *TLSCertRequest, opts ...grpc.CallOption) (*TLSCertResponse, error) Attest(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*AttestationResponse, error) Verify(ctx context.Context, in *VerificationRequest, opts ...grpc.CallOption) (*VerificationResponse, error) + Measure(ctx context.Context, in *MeasureRequest, opts ...grpc.CallOption) (*MeasureResponse, error) } type cMCServiceClient struct { @@ -73,6 +74,15 @@ func (c *cMCServiceClient) Verify(ctx context.Context, in *VerificationRequest, return out, nil } +func (c *cMCServiceClient) Measure(ctx context.Context, in *MeasureRequest, opts ...grpc.CallOption) (*MeasureResponse, error) { + out := new(MeasureResponse) + err := c.cc.Invoke(ctx, "/grpcapi.CMCService/Measure", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // CMCServiceServer is the server API for CMCService service. // All implementations must embed UnimplementedCMCServiceServer // for forward compatibility @@ -82,6 +92,7 @@ type CMCServiceServer interface { TLSCert(context.Context, *TLSCertRequest) (*TLSCertResponse, error) Attest(context.Context, *AttestationRequest) (*AttestationResponse, error) Verify(context.Context, *VerificationRequest) (*VerificationResponse, error) + Measure(context.Context, *MeasureRequest) (*MeasureResponse, error) mustEmbedUnimplementedCMCServiceServer() } @@ -101,6 +112,9 @@ func (UnimplementedCMCServiceServer) Attest(context.Context, *AttestationRequest func (UnimplementedCMCServiceServer) Verify(context.Context, *VerificationRequest) (*VerificationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Verify not implemented") } +func (UnimplementedCMCServiceServer) Measure(context.Context, *MeasureRequest) (*MeasureResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Measure not implemented") +} func (UnimplementedCMCServiceServer) mustEmbedUnimplementedCMCServiceServer() {} // UnsafeCMCServiceServer may be embedded to opt out of forward compatibility for this service. @@ -186,6 +200,24 @@ func _CMCService_Verify_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _CMCService_Measure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MeasureRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CMCServiceServer).Measure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpcapi.CMCService/Measure", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CMCServiceServer).Measure(ctx, req.(*MeasureRequest)) + } + return interceptor(ctx, in, info, handler) +} + // CMCService_ServiceDesc is the grpc.ServiceDesc for CMCService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -209,6 +241,10 @@ var CMCService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Verify", Handler: _CMCService_Verify_Handler, }, + { + MethodName: "Measure", + Handler: _CMCService_Measure_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "grpcapi.proto", From a1f03dbdf13b7b6acfe40ba88bc2af3d133e77be Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:06:58 +0000 Subject: [PATCH 10/26] api: add measure api call Signed-off-by: Simon Ott --- api/api.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/api/api.go b/api/api.go index 6db92e73..45ab7d0f 100644 --- a/api/api.go +++ b/api/api.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Fraunhofer AISEC +// Copyright (c) 2021 - 2024 Fraunhofer AISEC // Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Contains the API definitions for the CoAP and unix domain socket API +// Contains the API definitions for the CoAP and socket API. // The gRPC API is in a separate file package api @@ -54,6 +54,16 @@ type VerificationResponse struct { VerificationResult []byte `json:"verificationResult" cbor:"0,keyasint"` } +type MeasureRequest struct { + Name string `json:"name,omitempty" cbor:"0,keyasint,omitempty"` + ConfigSha256 []byte `json:"configSha256,omitempty" cbor:"1,keyasint,omitempty"` + RootfsSha256 []byte `json:"rootfsSha256,omitempty" cbor:"2,keyasint,omitempty"` +} + +type MeasureResponse struct { + Success bool `json:"success" cbor:"0,keyasint"` +} + type TLSSignRequest struct { Id string `json:"id" cbor:"0,keyasint"` Content []byte `json:"content" cbor:"1,keyasint"` @@ -110,8 +120,9 @@ const ( TypeError uint32 = 0 TypeAttest uint32 = 1 TypeVerify uint32 = 2 - TypeTLSSign uint32 = 3 - TypeTLSCert uint32 = 4 + TypeMeasure uint32 = 3 + TypeTLSSign uint32 = 4 + TypeTLSCert uint32 = 5 ) // Converts Protobuf hashtype to crypto.SignerOpts From 6da2a421dab7af976432c02ce30ca2b5ee65ba2e Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:07:46 +0000 Subject: [PATCH 11/26] tpmdriver: add functionality to read measurement lists Required for the prover to provide details on the order of measurements for self-contained attestation reports. Signed-off-by: Simon Ott --- tpmdriver/tpmdriver.go | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/tpmdriver/tpmdriver.go b/tpmdriver/tpmdriver.go index b86d758a..c29efc78 100644 --- a/tpmdriver/tpmdriver.go +++ b/tpmdriver/tpmdriver.go @@ -43,6 +43,7 @@ import ( est "github.com/Fraunhofer-AISEC/cmc/est/estclient" "github.com/Fraunhofer-AISEC/cmc/ima" "github.com/Fraunhofer-AISEC/cmc/internal" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) // Tpm is a structure that implements the Measure method @@ -55,6 +56,10 @@ type Tpm struct { UseIma bool ImaPcr int MeasurementLog bool + UseCtr bool + CtrPcr int + CtrLog string + Serializer ar.Serializer } const ( @@ -176,6 +181,10 @@ func (t *Tpm) Init(c *ar.DriverConfig) error { t.SigningCerts = ikchain t.MeasuringCerts = akchain t.MeasurementLog = c.MeasurementLog + t.Serializer = c.Serializer + t.UseCtr = c.UseCtr + t.CtrLog = c.CtrLog + t.CtrPcr = c.CtrPcr return nil } @@ -211,6 +220,7 @@ func (t *Tpm) Measure(nonce []byte) (ar.Measurement, error) { log.Warnf("failed to read binary bios measurements: %v. Using final PCR values as measurements", err) } + log.Tracef("Collected %v binary bios measurements", len(biosMeasurements)) } hashChain := make([]ar.PcrMeasurement, len(t.Pcrs)) @@ -262,13 +272,55 @@ func (t *Tpm) Measure(nonce []byte) (ar.Measurement, error) { // Find the IMA PCR in the TPM Measurement for i := range hashChain { if hashChain[i].Pcr == t.ImaPcr { - log.Tracef("Adding %v IMA events to PCR%v measurement", len(imaEvents), hashChain[i].Pcr) + log.Tracef("Adding %v IMA events to PCR%v measurement", len(imaEvents), + hashChain[i].Pcr) hashChain[i].Events = imaEvents hashChain[i].Summary = nil hashChain[i].Type = "PCR Eventlog" } } + } + + if t.UseCtr { + log.Tracef("Reading container measurements") + if _, err := os.Stat(t.CtrLog); err == nil { + // If CMC container measurements are used, add the list of executed containers + data, err := os.ReadFile(t.CtrLog) + if err != nil { + return ar.Measurement{}, fmt.Errorf("failed to read container measurements: %w", err) + } + + var measureList []m.MeasureEntry + err = t.Serializer.Unmarshal(data, &measureList) + if err != nil { + return ar.Measurement{}, fmt.Errorf("failed to unmarshal measurement list: %w", err) + } + for i := range hashChain { + if hashChain[i].Pcr == t.CtrPcr { + log.Tracef("Adding %v container events to PCR%v measurement", len(measureList), + hashChain[i].Pcr) + hashChain[i].Summary = nil + hashChain[i].Type = "PCR Eventlog" + for _, ml := range measureList { + log.Tracef("Adding %v container measurement", ml.Name) + event := ar.PcrEvent{ + Sha256: ml.TemplateSha256, + EventName: ml.Name, + CtrData: &ar.CtrData{ + ConfigSha256: ml.ConfigSha256, + RootfsSha256: ml.RootfsSha256, + }, + } + hashChain[i].Events = append(hashChain[i].Events, event) + } + } + } + } else { + log.Trace("No container measurements to read") + } + } else { + log.Trace("Container measurements omitted") } tm := ar.Measurement{ From e2387dc1ca10406dec705a79f6d35a39e502aaa8 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:08:35 +0000 Subject: [PATCH 12/26] testtool: Add functionality to test container measurements Signed-off-by: Simon Ott --- testtool/coap.go | 75 ++++++++++++++++++++++++++++++++++++++++++++ testtool/commands.go | 4 +++ testtool/config.go | 32 ++++++++++++++++--- testtool/grpc.go | 48 ++++++++++++++++++++++++++++ testtool/libapi.go | 40 +++++++++++++++++++++++ testtool/main.go | 3 +- testtool/socket.go | 71 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 268 insertions(+), 5 deletions(-) diff --git a/testtool/coap.go b/testtool/coap.go index beb3b662..a6d43f0a 100644 --- a/testtool/coap.go +++ b/testtool/coap.go @@ -34,6 +34,7 @@ import ( "github.com/Fraunhofer-AISEC/cmc/api" "github.com/Fraunhofer-AISEC/cmc/attestedtls" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) type CoapApi struct{} @@ -140,6 +141,37 @@ func (a CoapApi) verify(c *config) { } } +func (a CoapApi) measure(c *config) { + + ctrConfig, err := os.ReadFile(c.CtrConfig) + if err != nil { + log.Fatalf("Failed to read config: %v", err) + } + + configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + if err != nil { + log.Fatalf("Failed to measure config: %v", err) + } + + rootfsHash, err := m.GetRootfsMeasurement(c.CtrRootfs) + if err != nil { + log.Fatalf("Failed to measure rootfs: %v", err) + } + + req := &api.MeasureRequest{ + Name: c.CtrName, + ConfigSha256: configHash, + RootfsSha256: rootfsHash, + } + + resp, err := measureInternal(c.CmcAddr, req) + if err != nil { + log.Fatalf("Failed to verify: %v", err) + } + + log.Infof("Measure Result: %v", resp.Success) +} + func (a CoapApi) dial(c *config) { dialInternal(c, attestedtls.CmcApi_COAP, nil) } @@ -210,3 +242,46 @@ func verifyInternal(addr string, req *api.VerificationRequest, return verifyResp, nil } + +func measureInternal(addr string, req *api.MeasureRequest, +) (*api.MeasureResponse, error) { + + log.Tracef("Connecting via CoAP to %v", addr) + + // Establish connection + conn, err := udp.Dial(addr) + if err != nil { + return nil, fmt.Errorf("error dialing: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + path := "/Measure" + + // Marshal CoAP payload + payload, err := cbor.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal payload: %v", err) + } + + // Send CoAP POST request + resp, err := conn.Post(ctx, path, message.AppCBOR, bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + + // Read CoAP reply body + payload, err = resp.ReadBody() + if err != nil { + return nil, fmt.Errorf("failed to read body: %v", err) + } + + // Unmarshal attestation response + measureResp := new(api.MeasureResponse) + err = cbor.Unmarshal(payload, measureResp) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response") + } + + return measureResp, nil +} diff --git a/testtool/commands.go b/testtool/commands.go index 626c3761..6500634a 100644 --- a/testtool/commands.go +++ b/testtool/commands.go @@ -27,6 +27,10 @@ func verify(c *config) { c.api.verify(c) } +func measure(c *config) { + c.api.measure(c) +} + func dial(c *config) { c.api.dial(c) } diff --git a/testtool/config.go b/testtool/config.go index 25749f00..001f66a9 100644 --- a/testtool/config.go +++ b/testtool/config.go @@ -52,14 +52,15 @@ var ( ) type Api interface { + cacerts(c *config) generate(c *config) verify(c *config) + measure(c *config) dial(c *config) listen(c *config) - iothub(c *config) - cacerts(c *config) request(c *config) serve(c *config) + iothub(c *config) } type config struct { @@ -91,6 +92,11 @@ type config struct { MeasurementLog bool `json:"measurementLog"` UseIma bool `json:"useIma"` ImaPcr int `json:"imaPcr"` + // Only container measurements + CtrAlgo string `json:"ctrAlgo"` + CtrName string `json:"ctrName"` + CtrRootfs string `json:"ctrRootfs"` + CtrConfig string `json:"ctrConfig"` ca []byte policies []byte @@ -122,12 +128,16 @@ const ( driversFlag = "drivers" storageFlag = "storage" cacheFlag = "cache" - measurementLogFlag = "measurementLog" + measurementLogFlag = "measurementlog" headerFlag = "header" methodFlag = "method" dataFlag = "data" imaFlag = "ima" imaPcrFlag = "pcr" + // Only container image measure flags + ctrNameFlag = "ctrname" + ctrRootfsFlag = "ctrrootfs" + ctrConfigFlag = "ctrconfig" ) func getConfig() *config { @@ -175,6 +185,10 @@ func getConfig() *config { ima := flag.Bool(imaFlag, false, "Indicates whether to use Integrity Measurement Architecture (IMA)") pcr := flag.Int(imaPcrFlag, 0, "IMA PCR") + // Container measurement flags + ctrName := flag.String(ctrNameFlag, "", "Specifies name of container to be measured") + ctrRootfs := flag.String(ctrRootfsFlag, "", "Specifies rootfs path of the container to be measured") + ctrConfig := flag.String(ctrConfigFlag, "", "Specifies config path of the container to be measured") flag.Parse() @@ -286,6 +300,16 @@ func getConfig() *config { if internal.FlagPassed(imaPcrFlag) { c.ImaPcr = *pcr } + // Container measurements + if internal.FlagPassed(ctrNameFlag) { + c.CtrName = *ctrName + } + if internal.FlagPassed(ctrRootfsFlag) { + c.CtrRootfs = *ctrRootfs + } + if internal.FlagPassed(ctrConfigFlag) { + c.CtrConfig = *ctrConfig + } intervalDuration, err := time.ParseDuration(c.IntervalStr) if err != nil { @@ -308,7 +332,7 @@ func getConfig() *config { printConfig(c) // Get root CA certificate in PEM format if specified - if c.Mode != "generate" && c.Mode != "cacerts" { + if c.Mode != "generate" && c.Mode != "cacerts" && c.Mode != "measure" { if c.CaFile != "" { c.ca, err = os.ReadFile(c.CaFile) if err != nil { diff --git a/testtool/grpc.go b/testtool/grpc.go index 567c0716..2e69de0e 100644 --- a/testtool/grpc.go +++ b/testtool/grpc.go @@ -31,6 +31,7 @@ import ( "github.com/Fraunhofer-AISEC/cmc/attestedtls" api "github.com/Fraunhofer-AISEC/cmc/grpcapi" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) type GrpcApi struct{} @@ -137,6 +138,53 @@ func (a GrpcApi) verify(c *config) { log.Debug("Finished verify") } +func (a GrpcApi) measure(c *config) { + + // Establish connection + ctx, cancel := context.WithTimeout(context.Background(), timeoutSec*time.Second) + defer cancel() + + log.Tracef("Connecting via gRPC to %v", c.CmcAddr) + + conn, err := grpc.DialContext(ctx, c.CmcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) + if err != nil { + log.Fatalf("Failed to connect to cmcd: %v", err) + } + defer conn.Close() + client := api.NewCMCServiceClient(conn) + + ctrConfig, err := os.ReadFile(c.CtrConfig) + if err != nil { + log.Fatalf("Failed to read container config: %v", err) + } + + configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + if err != nil { + log.Fatalf("Failed to measure config: %v", err) + } + + rootfsHash, err := m.GetRootfsMeasurement(c.CtrRootfs) + if err != nil { + log.Fatalf("Failed to measure rootfs: %v", err) + } + + req := &api.MeasureRequest{ + Name: c.CtrName, + ConfigSha256: configHash, + RootfsSha256: rootfsHash, + } + + response, err := client.Measure(ctx, req) + if err != nil { + log.Fatalf("GRPC Measure Call failed: %v", err) + } + if response.GetStatus() != api.Status_OK { + log.Warnf("Failed to record measurement. Status %v", response.GetStatus()) + } + + log.Debug("Finished measure") +} + func (a GrpcApi) dial(c *config) { dialInternal(c, attestedtls.CmcApi_GRPC, nil) } diff --git a/testtool/libapi.go b/testtool/libapi.go index e5fae9d7..172d30d4 100644 --- a/testtool/libapi.go +++ b/testtool/libapi.go @@ -31,6 +31,7 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/attestedtls" "github.com/Fraunhofer-AISEC/cmc/cmc" + m "github.com/Fraunhofer-AISEC/cmc/measure" ) type LibApi struct { @@ -130,6 +131,45 @@ func (a LibApi) verify(c *config) { } } +func (a LibApi) measure(c *config) { + if a.cmc == nil { + cmc, err := initialize(c) + if err != nil { + log.Errorf("failed to initialize CMC: %v", err) + return + } + a.cmc = cmc + } + + ctrConfig, err := os.ReadFile(c.CtrConfig) + if err != nil { + log.Fatalf("Failed to read config: %v", err) + } + + configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + if err != nil { + log.Fatalf("Failed to measure config: %v", err) + } + + rootfsHash, err := m.GetRootfsMeasurement(c.CtrRootfs) + if err != nil { + log.Fatalf("Failed to measure rootfs: %v", err) + } + + err = m.Measure(c.CtrName, configHash, rootfsHash, + &m.MeasureConfig{ + Serializer: a.cmc.Serializer, + Pcr: a.cmc.CtrPcr, + LogFile: a.cmc.CtrLog, + Driver: a.cmc.CtrDriver, + }) + if err != nil { + log.Fatalf("Failed to record measurement: %v", err) + } + + log.Debug("Measure: Recorded measurement") +} + func (a LibApi) cacerts(c *config) { getCaCertsInternal(c) } diff --git a/testtool/main.go b/testtool/main.go index 5d370d06..3002df1b 100644 --- a/testtool/main.go +++ b/testtool/main.go @@ -23,13 +23,14 @@ import ( var ( cmds = map[string]func(*config){ + "cacerts": getCaCerts, // Retrieve CA certs from EST server "generate": generate, // Generate an attestation report "verify": verify, // Verify an attestation report + "measure": measure, // Record measurements "dial": dial, // Act as client to establish an attested TLS connection "listen": listen, // Act as server in etsblishing attested TLS connections "request": request, // Perform an attested HTTPS request "serve": serve, // Establish an attested HTTPS server - "cacerts": getCaCerts, // Retrieve CA certs from EST server "iothub": iothub, // Simulate an IoT hub for Cortex-M IAS Attestation Demo } ) diff --git a/testtool/socket.go b/testtool/socket.go index 5f969266..d56ca0f4 100644 --- a/testtool/socket.go +++ b/testtool/socket.go @@ -27,6 +27,7 @@ import ( "github.com/fxamacker/cbor/v2" // local modules + m "github.com/Fraunhofer-AISEC/cmc/measure" "github.com/Fraunhofer-AISEC/cmc/api" "github.com/Fraunhofer-AISEC/cmc/attestedtls" @@ -132,6 +133,37 @@ func (a SocketApi) verify(c *config) { } } +func (a SocketApi) measure(c *config) { + + ctrConfig, err := os.ReadFile(c.CtrConfig) + if err != nil { + log.Fatalf("Failed to read config: %v", err) + } + + configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + if err != nil { + log.Fatalf("Failed to measure config: %v", err) + } + + rootfsHash, err := m.GetRootfsMeasurement(c.CtrRootfs) + if err != nil { + log.Fatalf("Failed to measure rootfs: %v", err) + } + + req := &api.MeasureRequest{ + Name: c.CtrName, + ConfigSha256: configHash, + RootfsSha256: rootfsHash, + } + + resp, err := measureSocketRequest(c.Network, c.CmcAddr, req) + if err != nil { + log.Fatalf("Failed to measure: %v", err) + } + + log.Debugf("Recorded measurement. Result: %v", resp.Success) +} + func (a SocketApi) dial(c *config) { dialInternal(c, attestedtls.CmcApi_Socket, nil) } @@ -194,3 +226,42 @@ func verifySocketRequest(network, addr string, req *api.VerificationRequest, return verifyResp, nil } + +func measureSocketRequest(network, addr string, req *api.MeasureRequest, +) (*api.MeasureResponse, error) { + + log.Tracef("Connecting via %v socket to %v", network, addr) + + // Establish connection + conn, err := net.Dial(network, addr) + if err != nil { + return nil, fmt.Errorf("error dialing: %v", err) + } + + // Marshal payload + payload, err := cbor.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal payload: %v", err) + } + + // Send request + err = api.Send(conn, payload, api.TypeMeasure) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + + // Read reply + payload, _, err = api.Receive(conn) + if err != nil { + log.Fatalf("failed to receive: %v", err) + } + + // Unmarshal attestation response + measureResp := new(api.MeasureResponse) + err = cbor.Unmarshal(payload, measureResp) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response") + } + + return measureResp, nil +} From ad62fa44c99bc53900bab3cfc067eddf0b141be7 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:11:09 +0000 Subject: [PATCH 13/26] attestationreport/attestationreport.go: add container config The container config is required for the drivers. Signed-off-by: Simon Ott --- attestationreport/attestationreport.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/attestationreport/attestationreport.go b/attestationreport/attestationreport.go index 1d6ff9c9..0cdaedad 100644 --- a/attestationreport/attestationreport.go +++ b/attestationreport/attestationreport.go @@ -62,6 +62,9 @@ type DriverConfig struct { ImaPcr int Serializer Serializer MeasurementLog bool + UseCtr bool + CtrPcr int + CtrLog string } // Serializer is a generic interface providing methods for data serialization and @@ -118,6 +121,12 @@ type PcrEvent struct { Sha256 HexByte `json:"sha256" cbor:"2,keyasint"` EventName string `json:"eventname,omitempty" cbor:"4,keyasint,omitempty"` EventData *EventData `json:"eventdata,omitempty" cbor:"5,keyasint,omitempty"` + CtrData *CtrData `json:"ctrData,omitempty" cbor:"6,keyasint,omitempty"` +} + +type CtrData struct { + ConfigSha256 HexByte `json:"configSha256" cbor:"0,keyasint"` + RootfsSha256 HexByte `json:"rootfsSha256" cbor:"1,keyasint"` } // TpmMeasurement represents the attestation report From 5d04f078ba9000f9faab4bbc2363633afc230504 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:11:54 +0000 Subject: [PATCH 14/26] attestationreport/attestationreport.go: add support for environment variables Signed-off-by: Simon Ott --- attestationreport/attestationreport.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/attestationreport/attestationreport.go b/attestationreport/attestationreport.go index 0cdaedad..fbc5f2d5 100644 --- a/attestationreport/attestationreport.go +++ b/attestationreport/attestationreport.go @@ -270,6 +270,7 @@ type AppDescription struct { MetaInfo AppManifest string `json:"appManifest" cbor:"3,keyasint"` // Links to App Manifest.Name External []ExternalInterface `json:"externalConnections,omitempty" cbor:"4,keyasint,omitempty"` + Environment []Environment `json:"environment,omitempty" cbor:"5,keyasint,omitempty"` } // InternalConnection represents the attestation report @@ -291,6 +292,13 @@ type ExternalInterface struct { Port int `json:"port" cbor:"3,keyasint"` // Links to App Manifest.Endpoint } +// Environment represents environment variables +// for apps +type Environment struct { + Key string `json:"key" cbor:"0,keyasint"` + Value string `json:"value" cbor:"1,keyasint"` +} + // AppManifest represents the attestation report // element of type 'App Manifest' type AppManifest struct { From c3aeec7d641ebd8c171cef40a4d4890839730d85 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:13:17 +0000 Subject: [PATCH 15/26] attestationreport/attestationreport.go: add app results to validation report Signed-off-by: Simon Ott --- attestationreport/attestationreport.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/attestationreport/attestationreport.go b/attestationreport/attestationreport.go index fbc5f2d5..79d12946 100644 --- a/attestationreport/attestationreport.go +++ b/attestationreport/attestationreport.go @@ -980,6 +980,16 @@ func verifyMetadata(ar *AttestationReport, cas []*x509.Certificate, s Serializer result.DevDescResult.Description = metadata.DeviceDescription.Description result.DevDescResult.Location = metadata.DeviceDescription.Location } + for _, appDesc := range metadata.DeviceDescription.AppDescriptions { + appResult := AppDescResult{ + MetaInfo: appDesc.MetaInfo, + AppManifest: appDesc.AppManifest, + External: appDesc.External, + Environment: appDesc.Environment, + } + result.DevDescResult.AppResults = append(result.DevDescResult.AppResults, + appResult) + } } return metadata, result, success From 9a30897fce6aba5a3df1702a8d486d6fbc2ee6fc Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:14:28 +0000 Subject: [PATCH 16/26] attestationreport/validationreport.go: add support for environment variables Signed-off-by: Simon Ott --- attestationreport/validationreport.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/attestationreport/validationreport.go b/attestationreport/validationreport.go index d67f110f..c9c02602 100644 --- a/attestationreport/validationreport.go +++ b/attestationreport/validationreport.go @@ -81,9 +81,17 @@ type DevDescResult struct { CorrectApps []Result `json:"correctApps"` RtmOsCompatibility Result `json:"rtmOsCompatibility"` OsAppsCompatibility []Result `json:"osAppCompatibility"` + AppResults []AppDescResult `json:"appDescResults"` SignatureCheck []SignatureResult `json:"signatureValidation"` } +type AppDescResult struct { + MetaInfo + AppManifest string `json:"appManifest"` + Environment []Environment `json:"environment,omitempty"` + External []ExternalInterface `json:"external,omitempty"` +} + type MeasurementResult struct { Type string `json:"type"` Summary Result `json:"summary"` From b746ef305d89781891a0ddb98b18780d5110d27f Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:15:20 +0000 Subject: [PATCH 17/26] attestationreport/sw.go: delete obsolete sw module Signed-off-by: Simon Ott --- attestationreport/sw.go | 71 ----------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 attestationreport/sw.go diff --git a/attestationreport/sw.go b/attestationreport/sw.go deleted file mode 100644 index c3137929..00000000 --- a/attestationreport/sw.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2021 Fraunhofer AISEC -// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package attestationreport - -import ( - "bytes" -) - -func VerifySwMeasurements(swMeasurements []Measurement, refVals []ReferenceValue) ([]MeasurementResult, bool) { - - log.Trace("Verifying SW measurements") - - swMeasurementResults := make([]MeasurementResult, 0) - ok := true - - // Check that every reference value is reflected by a measurement - for _, v := range refVals { - result := MeasurementResult{} - result.Summary.Success = false - result.SwResult.VerName = v.Name - found := false - for _, swm := range swMeasurements { - if bytes.Equal(swm.Sha256, v.Sha256) { - result.SwResult.MeasName = swm.Description - result.Summary.Success = true - found = true - break - } - } - if !found { - log.Tracef("no SW Measurement found for SW Reference Value %v (hash: %v)", v.Name, v.Sha256) - result.Summary.SetErr(RefValNoMatch) - ok = false - } - swMeasurementResults = append(swMeasurementResults, result) - } - - // Check that every measurement is reflected by a reference value - for _, swM := range swMeasurements { - found := false - for _, swV := range refVals { - if bytes.Equal(swM.Sha256, swV.Sha256) { - found = true - break - } - } - if !found { - result := MeasurementResult{} - result.SwResult.MeasName = swM.Description - log.Tracef("no SW Reference Value found for SW Measurement: %v", swM.Sha256) - result.Summary.SetErr(MeasurementNoMatch) - swMeasurementResults = append(swMeasurementResults, result) - ok = false - } - } - - return swMeasurementResults, ok -} From 63ca954bc1384b25123f2ebc17cb4587ae16e5b3 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:16:04 +0000 Subject: [PATCH 18/26] attestationreport/tpm.go: more detailed measurement results Signed-off-by: Simon Ott --- attestationreport/tpm.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/attestationreport/tpm.go b/attestationreport/tpm.go index 34dc445c..85504e6b 100644 --- a/attestationreport/tpm.go +++ b/attestationreport/tpm.go @@ -22,6 +22,7 @@ import ( "crypto/x509" "encoding/hex" "sort" + "strings" "github.com/Fraunhofer-AISEC/cmc/internal" "github.com/google/go-tpm/legacy/tpm2" @@ -188,11 +189,16 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) calculatedPcrs[measuredPcr.Pcr] = extendSha256(calculatedPcrs[measuredPcr.Pcr], event.Sha256) + nameInfo := ref.Name + if event.EventName != "" && !strings.EqualFold(ref.Name, event.EventName) { + nameInfo += ": " + event.EventName + } + measResult := DigestResult{ Pcr: &pcrNum, Digest: hex.EncodeToString(event.Sha256), Success: true, - Name: ref.Name, + Name: nameInfo, Description: ref.Description, } detailedResults = append(detailedResults, measResult) @@ -273,9 +279,24 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) // in case of detailed measurement logs for _, ref := range referenceValues { + if ref.Pcr == nil { + result := DigestResult{ + Type: "Reference Value", + Success: false, + Name: ref.Name, + Digest: hex.EncodeToString(ref.Sha256), + Description: ref.Description, + } + detailedResults = append(detailedResults, result) + ok = false + log.Tracef("Reference value %v does not contain PCR", ref.Name) + continue + } + // Check if measurement contains the reference value PCR foundPcr := false for _, measuredPcr := range measurement.Pcrs { + if measuredPcr.Pcr == *ref.Pcr { foundPcr = true } else { From 6f714b0f82a546d460a7f9122c83e0ebe7829d1e Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:16:37 +0000 Subject: [PATCH 19/26] attestationreport/snp_test.go: renaming Signed-off-by: Simon Ott --- attestationreport/snp_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attestationreport/snp_test.go b/attestationreport/snp_test.go index d64d8422..18cd3f28 100644 --- a/attestationreport/snp_test.go +++ b/attestationreport/snp_test.go @@ -37,7 +37,7 @@ func Test_verifySnpMeasurements(t *testing.T) { want bool }{ { - name: "Valid Attestation Report", + name: "ValidAttestationReport", args: args{ snpM: &Measurement{ Type: "SNP Measurement", From 446926b012dbd321c5498df02cbfa33934d5f1c5 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:19:48 +0000 Subject: [PATCH 20/26] doc: update documentation Signed-off-by: Simon Ott --- doc/container-measurements.md | 118 +++ doc/container_measurement.drawio | 1480 ++++++++++++++++++++++++++++++ doc/manual-setup.md | 21 + 3 files changed, 1619 insertions(+) create mode 100644 doc/container-measurements.md create mode 100644 doc/container_measurement.drawio diff --git a/doc/container-measurements.md b/doc/container-measurements.md new file mode 100644 index 00000000..fb31036a --- /dev/null +++ b/doc/container-measurements.md @@ -0,0 +1,118 @@ +# Containerd Container Measurements + +## Prerequisites and Build + +### Containerd Configuration +To measure containers, we require the `containerd-shim-cmc-v1` proxy shim, to be invoked by +containerd instead of the default shim `containerd-shim-runc-v2`. The proxy shim measures the +container and then invokes `containerd-shim-runc-v2`. + +#### Alternative 1 + +Add the following to `/etc/containerd/config.toml` +``` +[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.cmc] + runtime_type = "io.containerd.runtime.v1.linux" + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.cmc.options] + BinaryName = "/usr/bin/containerd-shim-cmc-v1" +``` + +Restart containerd: +```sh +sudo systemctl restart containerd +``` + +The shim can now be invoked with the following `ctr` argument: +`--runtime io.containerd.runtime.v1.linux.cmc` + +#### Alternative 2 + +If an absolute path is used, no containerd configuration changes are required: +`--runtime /path/to/cmc/tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1` + +### Build the CMC + +build all CMC binaries according to the README, especially +`cmc/tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1` (can be built via `go build`). If +*Alternative 1* was chosen, the binary must be copied to `/usr/bin`. + +### Run the CMC + +Make sure the `cmcd` and `estserver` are running (see main README.md) + + +### Create Reference Values for Containers to be Executed + +#### Alternative 1: Perform containerd dry run with good reference container and collect reference values + +**Note**: The +[example-setup/update-container-manifest-live](../../example-setup/update-container-manifest-live) +script can be used to generate a manifest for a specific container. The following manual setup +explains the single steps. + + +To do a "dry run" of the custom shim to generate the reference values for a trusted container, pull the image: +```bash +ctr image pull docker.io/library/ubuntu:22.04 +``` + +And then generate the reference values: +```bash +sudo ctr run --runtime io.containerd.runtime.v1.linux.cmc -env VAL=HELLO -t --rm docker.io/library/ubuntu:22.04 CMC_GENERATE_APP_MANIFEST +``` + +This will create reference values for the container and store it in `/tmp/measure/container-refs` + +Generate CMC metadata as described in the CMC Readme, generate an app manifest and add the reference values. + +Sign this manifest and add it to `cmc-data`: +```sh +cmc-signing-tool -in /tmp/measure/app.manifest.json -out metadata-signed/app.manifest.json -keys pki/signing-cert-key.pem -x5cs pki/signing-cert.pem,pki/ca.pem +``` + +#### Alternative 2: Use tools to convert OCI image to runtime bundle and measure reference values + +This requires `buildah` and `umoci` to be installed: `sudo apt install buildah umoci` + +**Note**: The +[example-setup/update-container-manifest](../../example-setup/update-container-manifest) +script can be used to generate a manifest for a specific container. The following manual setup +explains the single steps. + +```sh +buildah pull +buildah push oci-archive:myimage-oci.tar +``` + +Extract the OCI image: +```sh +tar -xvf myimage-oci.tar +``` + +Unpack the image to a bundle: +```sh +umoci unpack --rootless --image ./ bundle +``` + +Measure runtime bundle: +```sh +./measure-bundle -config bundle/config.json -rootfs bundle/rootfs +``` + +Now create an app manifest and add the reference values as described in *Alternative 1* + +## Run a container + +Now, run the container: +```bash +sudo ctr run --runtime io.containerd.runtime.v1.linux.cmc -env VAL=HELLO -t --rm docker.io/library/ubuntu:22.04 my_container +``` + +The container will always start, but changing e.g. the environment variable will lead to a failed remote attestation. + +## Logging and Troubleshooting + +- If managed by systemd, the containerd logs can be retrieved via: `journalctl -u containerd` +- To get more detailed containerd logs: `containerd -log-level trace` +- The `containerd-shim-cmc-v1` logs are located at: `/tmp/containerd-shim-cmc.log` +- The original shim logs are retrieved by containerd / the proxy shim diff --git a/doc/container_measurement.drawio b/doc/container_measurement.drawio new file mode 100644 index 00000000..27d0dfe8 --- /dev/null +++ b/doc/container_measurement.drawio @@ -0,0 +1,1480 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/manual-setup.md b/doc/manual-setup.md index 65039d7c..a7506caa 100644 --- a/doc/manual-setup.md +++ b/doc/manual-setup.md @@ -135,6 +135,13 @@ sudo parse-ima-pcr ``` Then insert those values into the json `referenceValues` array in an app manifest. +For OCI containers, currently the `containerd` tool `ctr` is supported with the custom cmc +runtime `cmc/tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1`: +```sh +sudo ctr run --runtime ${runtime} -t --rm docker.io/library/ubuntu:22.04 CMC_GENERATE_APP_MANIFEST +``` +the reference values are generated at `/tmp/container-refs` and must be put into an app manifest. + **Calculating the reference values based on software artifacts** This currently only works for QEMU VMs with OVMF and a Linux kernel. it is recommended to use @@ -171,6 +178,20 @@ sudo calculate-ima-pcr -t 10 -i ima-ng -p /usr/bin -p /usr/sbin -p /usr/lib ``` Then insert those values into an app manifest. +For OCI containers, the `buildah` and `umoci` tool can be used in combination with the custom +`cmc/tools/measure-bundle/measure-bundle` tool can be used: + +```sh +buildah pull ubuntu:22.04 +buildah push ubuntu:22.04 oci-archive:myimage-oci.tar:latest + +tar -xvf myimage-oci.tar +(cd image ; umoci unpack --rootless --image ./:latest bundle) + +measure-bundle -config image/bundle/config.json" -rootfs image/bundle/rootfs) +``` +Then, insert those reference values into an App Manifest. + ##### AMD SNP Reference Values tbd From 5da747758c862c70d75947ab4086df477243e2df Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:20:36 +0000 Subject: [PATCH 21/26] example-setup/setup-cmc: allow specifying the container engine Signed-off-by: Simon Ott --- example-setup/setup-cmc | 3 ++- example-setup/utils.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example-setup/setup-cmc b/example-setup/setup-cmc index 94de4c24..cc05dad1 100755 --- a/example-setup/setup-cmc +++ b/example-setup/setup-cmc @@ -15,6 +15,7 @@ fi cmc=$(set -e; abs_path "$1") data=$(set -e; abs_path "$2") ser="${3}" +client="docker" if [[ ! -d "${cmc}" ]]; then echo "CMC directory does not exist. Did you clone the repository? Abort.." @@ -63,4 +64,4 @@ cp -r "${cmc}/example-setup/"* "${data}" "${data}/update-app-manifest-live" "${data}" "${ser}" # Generate container example manifest -"${data}/update-container-manifest-live" docker.io/library/ubuntu:22.04 "${data}" "${cmc}" "${ser}" \ No newline at end of file +"${data}/update-container-manifest-live" docker.io/library/ubuntu:22.04 "${data}" "${cmc}" "${ser}" "${client}" \ No newline at end of file diff --git a/example-setup/utils.sh b/example-setup/utils.sh index f651eba4..dd305699 100644 --- a/example-setup/utils.sh +++ b/example-setup/utils.sh @@ -14,4 +14,4 @@ extendarr() { # Add new value json="$(echo "${json}" | jq ".${key} += [${param}]")" -} \ No newline at end of file +} From b2075238d29a8fa5013f58eb13780ee5cf6e3bd6 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:21:00 +0000 Subject: [PATCH 22/26] example-setup/update-container-manifest: add script Add script to demonstrate how to generate reference values for containers. Signed-off-by: Simon Ott --- example-setup/update-container-manifest | 106 ++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 example-setup/update-container-manifest diff --git a/example-setup/update-container-manifest b/example-setup/update-container-manifest new file mode 100755 index 00000000..be5ecf1b --- /dev/null +++ b/example-setup/update-container-manifest @@ -0,0 +1,106 @@ +#!/bin/bash + +set -euo pipefail + +trap '[ $? -eq 0 ] && exit 0; printf "%s failed\n" "$0"' EXIT +dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +source "${dir}/utils.sh" +export PATH=${PATH}:${HOME}/go/bin + +if [[ "$#" -lt 3 ]]; then + echo "Usage: ./update-container-manifest " + exit 1 +fi + +container="$1" +shift +data=$(set -e; abs_path "$1") +shift +ser="$1" + +input="${data}/metadata-raw" +tmp="${data}/metadata-tmp" +output="${data}/metadata-signed" +container_name=$(echo "${container}-oci" | sed 's:.*/::' | tr : -) + +echo "Creating reference values for container: ${container}" +echo "Using ${data} as directory for local data" + +# Create a temporary directory +tmpdir=$(mktemp -d) +echo "Using temporary directory ${tmpdir} for processing" + +# Pull and push image to OCI-archive format +buildah pull ${container} +buildah push ${container} oci-archive:${tmpdir}/myimage-oci.tar:latest + +# Prepare the image directory within the temp directory +mkdir -p ${tmpdir}/image + +# Extract the OCI image +(cd ${tmpdir}/image ; tar -xvf ${tmpdir}/myimage-oci.tar) + +# Unpack the image to a bundle +(cd ${tmpdir}/image ; umoci unpack --rootless --image ./:latest bundle) + +# Ensure the container's rootfs path is correctly set +container_rootfs="${tmpdir}/image/bundle/rootfs" +config="${tmpdir}/image/bundle/config.json" + +refvals=$(measure-bundle -config "${config}" -rootfs "${container_rootfs}") + +rm -r ${tmpdir} + +# App Manifest: Replace existing reference values with new reference values in the App Manifest +json=$(cat "${input}/app.manifest.json") +json=$(echo "${json}" | jq 'del(.referenceValues[])') +json=$(echo "${json}" | jq --argjson ver "${refvals}" '.referenceValues += $ver') + +# App Manifest: Set name and reference value name +json=$(echo "${json}" | jq ".name = \"${container}\"") +json=$(echo "${json}" | jq ".referenceValues[0].name += \": ${container}\"") + +# App Manifest: Store +echo "Writing ${input}/${container_name}.manifest.json" +printf "%s\n" "${json}" > "${input}/${container_name}.manifest.json" + +# App Description: Create corresponding app description +appdesc=$(cat "${input}/app.description.json") +appdesc=$(echo "${appdesc}" | jq ".name = \"${container}.description\"") +appdesc=$(echo "${appdesc}" | jq ".appManifest = \"${container}\"") + +# Device Description: Add/replace app description to/in device description +devdesc=$(cat "${input}/device.description.json") +exists=$(echo "${devdesc}" | jq "any(.appDescriptions[]; .name == \"${container}.description\")") +if [[ "${exists}" = false ]]; then + echo "Adding app description to device description" +else + echo "Replacing existing app description" + devdesc=$(echo "$devdesc" | jq ".appDescriptions |= map(select(.name != \"${container}.description\"))") +fi +devdesc=$(echo "${devdesc}" | jq --argjson desc "[${appdesc}]" '.appDescriptions += $desc') + +# Device Description: store +echo "Writing ${input}/device.description.json" +printf "%s\n" "${devdesc}" > "${input}/device.description.json" + +# Sign the metadata* +key="${data}/pki/signing-cert-key.pem" +chain="${data}/pki/signing-cert.pem,${data}/pki/ca.pem" + +rm -rf "${tmp}"/${container_name}.manifest.* +rm -rf "${output}"/${container_name}.manifest.* + +# Convert to CBOR if specified +if [[ "${ser,,}" = "json" ]]; then + echo "using json serialization" + cp "${input}/${container_name}.manifest.json" "${tmp}/${container_name}.manifest.json" +elif [[ "${ser,,}" = "cbor" ]]; then + echo "using cbor serialiation" + cmc-converter -in "${input}/${container_name}.manifest.json" -out "${tmp}/${container_name}.manifest.cbor" -outform cbor +else + echo "serialization format ${ser} is not supported" + exit 1 +fi + +cmc-signing-tool -in "${tmp}/${container_name}.manifest.${ser}" -out "${output}/${container_name}.manifest.${ser}" -keys "${key}" -x5cs "${chain}" From fd778d6d9e0e9fa75b2f01c3bf48807a2826baf4 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Fri, 14 Jun 2024 12:21:38 +0000 Subject: [PATCH 23/26] example-setup/update-container-manifest-live: add script Add script to demonstrate how to generate reference values for containers based on dry-running containers. Signed-off-by: Simon Ott --- example-setup/update-container-manifest-live | 141 +++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 example-setup/update-container-manifest-live diff --git a/example-setup/update-container-manifest-live b/example-setup/update-container-manifest-live new file mode 100755 index 00000000..c011e18e --- /dev/null +++ b/example-setup/update-container-manifest-live @@ -0,0 +1,141 @@ +#!/bin/bash + +set -euo pipefail + +trap '[ $? -eq 0 ] && exit 0; printf "%s failed\n" "$0"' EXIT +dir="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +source "${dir}/utils.sh" +export PATH=${PATH}:${HOME}/go/bin + +if [[ "$#" -lt 5 ]]; then + echo "Usage: ./update-container-manifest-live []" + exit 1 +fi + +# Extract the -env parameters +keys=() +values=() +found=false +for arg in "$@"; do + if [[ "${arg}" == "-env" ]]; then + found=true + elif [[ "${found}" == true ]]; then + IFS='=' read -r KEY VAL <<< "$arg" + keys+=("${KEY}") + values+=("${VAL}") + found=false + fi +done + +# Set all parameters +container="$1" +shift +data=$(set -e; abs_path "$1") +shift +cmc=$(set -e; abs_path "$1") +shift +ser="$1" +shift +client="$1" +shift +args="$@" + +input="${data}/metadata-raw" +tmp="${data}/metadata-tmp" +output="${data}/metadata-signed" +runtime="${cmc}/tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1" +container="${container%/}" +container_name=$(echo "${container}-oci" | sed 's:.*/::' | tr : -) + +echo "Creating reference values for container: ${container} with args ${args} and runtime ${runtime}" +echo "Using ${data} as directory for local data" + +# Delete temporary manifests +rm -rf "${tmp}"/${container_name}.manifest.* + +# Calculate the container reference values +sudo rm -f /tmp/container-refs + +echo "Generating reference values for ${client} client" +if [[ "${client}" == "ctr" ]]; then + sudo ctr image pull ${container} + set +e + sudo ctr run --runtime ${runtime} -t --rm ${args} ${container} CMC_GENERATE_APP_MANIFEST + set -e +elif [[ "${client}" == "docker" ]]; then + sudo docker run ${args} ${container} +elif [[ "${client}" == "runc" ]]; then + cd ${container} + sudo runc create references + sudo runc delete references + cd - +else + echo "Client ${client} not supported. Only 'docker', 'ctr' and 'runc' supported for now." + exit +fi + +refvals=$(cat /tmp/container-refs) + +# App Manifest: Replace existing reference values with new reference values in the App Manifest +json=$(cat "${input}/app.manifest.json") +json=$(echo "${json}" | jq 'del(.referenceValues[])') +json=$(echo "${json}" | jq --argjson ver "${refvals}" '.referenceValues += $ver') + +# App Manifest: Extract the reference value and add it to the file name to not overwrite the same +# container with different arguments such as environment variables +refval=$(echo "${json}" | jq -r '.referenceValues[0].sha256') + +# App Manifest: Set name and reference value name +json=$(echo "${json}" | jq ".name = \"${container}-${refval}\"") +json=$(echo "${json}" | jq ".referenceValues[0].name += \": ${container}\"") + +# App Manifest: Store +echo "Writing ${input}/${container_name}-${refval}.manifest.json" +printf "%s\n" "${json}" > "${input}/${container_name}-${refval}.manifest.json" + +# App Description: Create corresponding app description +appdesc=$(cat "${input}/app.description.json") +appdesc=$(echo "${appdesc}" | jq ".name = \"${container}-${refval}.description\"") +appdesc=$(echo "${appdesc}" | jq ".appManifest = \"${container}-${refval}\"") +echo "Adding environment variables" +for i in "${!keys[@]}"; do + envs="{\"key\": \"${keys[$i]}\", \"value\": \"${values[$i]}\"}" + echo "Adding $envs" + appdesc=$(echo "${appdesc}" | jq --argjson envs "${envs}" '.environment += [$envs]') +done + +# Device Description: Add/replace app description to/in device description +devdesc=$(cat "${input}/device.description.json") +exists=$(echo "${devdesc}" | jq "any(.appDescriptions[]; .name == \"${container}-${refval}.description\")") +if [[ "${exists}" = false ]]; then + echo "Adding app description to device description" +else + echo "Replacing existing app description" + devdesc=$(echo "$devdesc" | jq ".appDescriptions |= map(select(.name != \"${container}-${refval}.description\"))") +fi +devdesc=$(echo "${devdesc}" | jq --argjson desc "[${appdesc}]" '.appDescriptions += $desc') + +# Device Description: Store +echo "Writing ${input}/device.description.json" +printf "%s\n" "${devdesc}" > "${input}/device.description.json" + +# Sign the metadata* +key="${data}/pki/signing-cert-key.pem" +chain="${data}/pki/signing-cert.pem,${data}/pki/ca.pem" + +# Convert to CBOR if specified +if [[ "${ser,,}" = "json" ]]; then + echo "using json serialization" + cp "${input}/${container_name}-${refval}.manifest.json" "${tmp}/${container_name}-${refval}.manifest.json" + cp "${input}/device.description.json" "${tmp}/device.description.json" +elif [[ "${ser,,}" = "cbor" ]]; then + echo "using cbor serialiation" + cmc-converter -in "${input}/${container_name}-${refval}.manifest.json" -out "${tmp}/${container_name}-${refval}.manifest.cbor" -outform cbor + cmc-converter -in "${input}/device.description.json" -out "${tmp}/device.description.cbor" -outform cbor +else + echo "serialization format ${ser} is not supported" + exit 1 +fi + +cmc-signing-tool -in "${tmp}/${container_name}-${refval}.manifest.${ser}" -out "${output}/${container_name}-${refval}.manifest.${ser}" -keys "${key}" -x5cs "${chain}" +cmc-signing-tool -in "${tmp}/device.description.${ser}" -out "${output}/device.description.${ser}" -keys "${key}" -x5cs "${chain}" \ No newline at end of file From 452d4b9b5d1ccd8ae25dda00b3ba7bece5911db9 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Mon, 24 Jun 2024 15:31:00 +0000 Subject: [PATCH 24/26] treewide: refactor attestationreport module Signed-off-by: Simon Ott --- attestationreport/attestationreport.go | 744 ------------------ attestationreport/cbor_test.go | 30 + attestedtls/libapi.go | 9 +- cmc/cmc.go | 11 +- cmcd/coap.go | 9 +- cmcd/grpc.go | 9 +- cmcd/socket.go | 13 +- generate/generate.go | 130 +++ sgxdriver/sgxdriver.go | 3 +- snpdriver/snpdriver.go | 3 +- testtool/libapi.go | 9 +- tools/fmspc-retrieval-tool/main.go | 4 +- .../duktapepolicies.go | 5 +- {attestationreport => verify}/iat.go | 47 +- {attestationreport => verify}/iat_test.go | 37 +- .../intel_helpers.go | 129 +-- {attestationreport => verify}/jspolicies.go | 5 +- {attestationreport => verify}/sgx.go | 78 +- {attestationreport => verify}/sgx_test.go | 98 +-- {attestationreport => verify}/snp.go | 91 +-- {attestationreport => verify}/snp_test.go | 92 +-- {attestationreport => verify}/tdx.go | 88 ++- {attestationreport => verify}/tdx_test.go | 168 ++-- {attestationreport => verify}/tpm.go | 55 +- {attestationreport => verify}/tpm_test.go | 78 +- verify/verify.go | 673 ++++++++++++++++ .../verify_test.go | 118 +-- 27 files changed, 1427 insertions(+), 1309 deletions(-) create mode 100644 generate/generate.go rename {attestationreport => verify}/duktapepolicies.go (87%) rename {attestationreport => verify}/iat.go (81%) rename {attestationreport => verify}/iat_test.go (93%) rename {attestationreport => verify}/intel_helpers.go (91%) rename {attestationreport => verify}/jspolicies.go (87%) rename {attestationreport => verify}/sgx.go (89%) rename {attestationreport => verify}/sgx_test.go (98%) rename {attestationreport => verify}/snp.go (88%) rename {attestationreport => verify}/snp_test.go (96%) rename {attestationreport => verify}/tdx.go (90%) rename {attestationreport => verify}/tdx_test.go (96%) rename {attestationreport => verify}/tpm.go (88%) rename {attestationreport => verify}/tpm_test.go (94%) create mode 100644 verify/verify.go rename attestationreport/attestationreport_test.go => verify/verify_test.go (81%) diff --git a/attestationreport/attestationreport.go b/attestationreport/attestationreport.go index 79d12946..0bcd06df 100644 --- a/attestationreport/attestationreport.go +++ b/attestationreport/attestationreport.go @@ -16,22 +16,11 @@ package attestationreport import ( - "bytes" "crypto" - "crypto/sha256" - "crypto/sha512" "crypto/x509" - "encoding/hex" "encoding/json" - "errors" - "fmt" - "strconv" - "github.com/Fraunhofer-AISEC/cmc/internal" - "github.com/fxamacker/cbor/v2" "github.com/sirupsen/logrus" - - "time" ) var log = logrus.WithField("service", "ar") @@ -78,20 +67,6 @@ type Serializer interface { VerifyToken(data []byte, roots []*x509.Certificate) (TokenResult, []byte, bool) } -type PolicyEngineSelect uint32 - -const ( - PolicyEngineSelect_None PolicyEngineSelect = 0 - PolicyEngineSelect_JS PolicyEngineSelect = 1 - PolicyEngineSelect_DukTape PolicyEngineSelect = 2 -) - -var policyEngines = map[PolicyEngineSelect]PolicyValidator{} - -type PolicyValidator interface { - Validate(policies []byte, result VerificationResult) bool -} - // MetaInfo is a helper struct for generic info // present in every metadata object type MetaInfo struct { @@ -411,722 +386,3 @@ type AttestationReport struct { DeviceDescription []byte `json:"deviceDescription" cbor:"6,keyasint"` Nonce []byte `json:"nonce" cbor:"7,keyasint"` } - -// Generate generates an attestation report with the provided -// nonce and manifests and descriptions metadata. The manifests and descriptions -// must be either raw JWS tokens in the JWS JSON full serialization -// format or CBOR COSE tokens. Takes a list of measurers providing a method -// for collecting the measurements from a hardware or software interface -func Generate(nonce []byte, metadata [][]byte, measurers []Driver, s Serializer) ([]byte, error) { - - if s == nil { - return nil, errors.New("serializer not specified") - } - - // Create attestation report object which will be filled with the attestation - // data or sent back incomplete in case errors occur - ar := AttestationReport{ - Type: "Attestation Report", - } - - if len(nonce) > 32 { - return nil, fmt.Errorf("nonce exceeds maximum length of 32 bytes") - } - ar.Nonce = nonce - - log.Debug("Adding manifests and descriptions to Attestation Report..") - - // Retrieve the manifests and descriptions - log.Trace("Parsing ", len(metadata), " meta-data objects..") - numManifests := 0 - for i := 0; i < len(metadata); i++ { - - // Extract plain payload (i.e. the manifest/description itself) - data, err := s.GetPayload(metadata[i]) - if err != nil { - log.Tracef("Failed to parse metadata object %v: %v", i, err) - continue - } - - // Unmarshal the Type field of the JSON file to determine the type for - // later processing - elem := new(MetaInfo) - err = s.Unmarshal(data, elem) - if err != nil { - log.Tracef("Failed to unmarshal data from metadata object %v: %v", i, err) - continue - } - - switch elem.Type { - case "App Manifest": - log.Debug("Adding App Manifest") - ar.AppManifests = append(ar.AppManifests, metadata[i]) - numManifests++ - case "OS Manifest": - log.Debug("Adding OS Manifest") - ar.OsManifest = metadata[i] - numManifests++ - case "RTM Manifest": - log.Debug("Adding RTM Manifest") - ar.RtmManifest = metadata[i] - numManifests++ - case "Device Description": - log.Debug("Adding Device Description") - ar.DeviceDescription = metadata[i] - case "Company Description": - log.Debug("Adding Company Description") - ar.CompanyDescription = metadata[i] - } - } - - if numManifests == 0 { - log.Warn("Did not find any manifests for the attestation report") - } else { - log.Debug("Added ", numManifests, " manifests to attestation report") - } - - log.Tracef("Retrieving measurements from %v measurers", len(measurers)) - for _, measurer := range measurers { - - // This actually collects the measurements. The methods are implemented - // in the respective module (e.g. tpm module) - log.Trace("Getting measurements from measurement interface..") - measurement, err := measurer.Measure(nonce) - if err != nil { - return nil, fmt.Errorf("failed to get measurements: %v", err) - } - - ar.Measurements = append(ar.Measurements, measurement) - log.Tracef("Added %v to attestation report", measurement.Type) - } - - log.Trace("Finished attestation report generation") - - // Marshal data to bytes - data, err := s.Marshal(ar) - if err != nil { - return nil, fmt.Errorf("failed to marshal the Attestation Report: %v", err) - } - - return data, nil -} - -// Sign signs the attestation report with the specified signer 'signer' -func Sign(report []byte, signer Driver, s Serializer) ([]byte, error) { - return s.Sign(report, signer) -} - -// Verify verifies an attestation report in full serialized JWS -// format against the supplied nonce and CA certificate. Verifies the certificate -// chains of all attestation report elements as well as the measurements against -// the reference values and the compatibility of software artefacts. -func Verify(arRaw, nonce, casPem []byte, policies []byte, polEng PolicyEngineSelect, intelCache string) VerificationResult { - result := VerificationResult{ - Type: "Verification Result", - Success: true, - SwCertLevel: 0} - - cas, err := internal.ParseCertsPem(casPem) - if err != nil { - log.Tracef("Failed to parse specified CA certificate(s): %v", err) - result.Success = false - result.ErrorCode = ParseCA - return result - } - - // Detect serialization format - var s Serializer - if json.Valid(arRaw) { - log.Trace("Detected JSON serialization") - s = JsonSerializer{} - } else if err := cbor.Valid(arRaw); err == nil { - log.Trace("Detected CBOR serialization") - s = CborSerializer{} - } else { - log.Trace("Unable to detect AR serialization format") - result.Success = false - result.ErrorCode = UnknownSerialization - return result - } - - // Verify and unpack attestation report - ar, tr, code := verifyAr(arRaw, cas, s) - result.ReportSignature = tr.SignatureCheck - if code != NotSet { - result.ErrorCode = code - result.Success = false - return result - } - - // Verify and unpack metadata from attestation report - metadata, mr, ok := verifyMetadata(ar, cas, s) - if !ok { - result.Success = false - } - result.MetadataResult = *mr - - // Verify nonce - if res := bytes.Compare(ar.Nonce, nonce); res != 0 { - log.Tracef("Nonces mismatch: supplied nonce: %v, report nonce = %v", - hex.EncodeToString(nonce), hex.EncodeToString(ar.Nonce)) - result.FreshnessCheck.Success = false - result.FreshnessCheck.Expected = hex.EncodeToString(ar.Nonce) - result.FreshnessCheck.Got = hex.EncodeToString(nonce) - result.Success = false - } else { - result.FreshnessCheck.Success = true - } - - refVals, err := collectReferenceValues(metadata) - if err != nil { - log.Tracef("Failed to collect reference values: %v", err) - result.Success = false - result.ErrorCode = RefValTypeNotSupported - } - - hwAttest := false - for _, m := range ar.Measurements { - - switch mtype := m.Type; mtype { - - case "TPM Measurement": - r, ok := verifyTpmMeasurements(m, nonce, refVals["TPM Reference Value"], cas) - if !ok { - result.Success = false - } - result.Measurements = append(result.Measurements, *r) - hwAttest = true - - case "SNP Measurement": - r, ok := verifySnpMeasurements(m, nonce, refVals["SNP Reference Value"]) - if !ok { - result.Success = false - } - result.Measurements = append(result.Measurements, *r) - hwAttest = true - - case "TDX Measurement": - r, ok := verifyTdxMeasurements(m, nonce, intelCache, refVals["TDX Reference Value"]) - if !ok { - result.Success = false - } - result.Measurements = append(result.Measurements, *r) - hwAttest = true - - case "SGX Measurement": - r, ok := verifySgxMeasurements(m, nonce, intelCache, refVals["SGX Reference Value"]) - if !ok { - result.Success = false - } - result.Measurements = append(result.Measurements, *r) - hwAttest = true - - case "IAS Measurement": - r, ok := verifyIasMeasurements(m, nonce, - refVals["IAS Reference Value"], cas) - if !ok { - result.Success = false - } - result.Measurements = append(result.Measurements, *r) - hwAttest = true - - default: - log.Tracef("Unsupported measurement type '%v'", mtype) - result.Success = false - result.ErrorCode = MeasurementTypeNotSupported - } - } - - // The lowest certification level of all components determines the certification - // level for the device's software stack - levels := make([]int, 0) - levels = append(levels, metadata.RtmManifest.CertificationLevel) - levels = append(levels, metadata.OsManifest.CertificationLevel) - for _, app := range metadata.AppManifests { - levels = append(levels, app.CertificationLevel) - } - aggCertLevel := levels[0] - for _, l := range levels { - if l < aggCertLevel { - aggCertLevel = l - } - } - // If no hardware trust anchor is present, the maximum certification level is 1 - // If there are reference values with a higher trust level present, the remote attestation - // must fail - if !hwAttest && aggCertLevel > 1 { - log.Tracef("No hardware trust anchor measurements present but claimed certification level is %v, which requires a hardware trust anchor", aggCertLevel) - result.ErrorCode = InvalidCertificationLevel - result.Success = false - } - result.SwCertLevel = aggCertLevel - - // Verify the compatibility of the attestation report through verifying the - // compatibility of all components - - // Check that the OS and RTM Manifest are specified in the Device Description - if metadata.DeviceDescription.RtmManifest == metadata.RtmManifest.Name { - result.DevDescResult.CorrectRtm.Success = true - } else { - log.Tracef("Device Description listed wrong RTM Manifest: %v vs. %v", - metadata.DeviceDescription.RtmManifest, metadata.RtmManifest.Name) - result.DevDescResult.CorrectRtm.Success = false - result.DevDescResult.CorrectRtm.Expected = metadata.DeviceDescription.RtmManifest - result.DevDescResult.CorrectRtm.Got = metadata.RtmManifest.Name - result.DevDescResult.Summary.Success = false - result.Success = false - } - if metadata.DeviceDescription.OsManifest == metadata.OsManifest.Name { - result.DevDescResult.CorrectOs.Success = true - } else { - log.Tracef("Device Description listed wrong OS Manifest: %v vs. %v", - metadata.DeviceDescription.OsManifest, metadata.OsManifest.Name) - result.DevDescResult.CorrectOs.Success = false - result.DevDescResult.CorrectOs.Expected = metadata.DeviceDescription.OsManifest - result.DevDescResult.CorrectOs.Got = metadata.OsManifest.Name - result.DevDescResult.Summary.Success = false - result.Success = false - } - - // Extract app description names - appDescriptions := make([]string, 0) - for _, a := range metadata.DeviceDescription.AppDescriptions { - appDescriptions = append(appDescriptions, a.AppManifest) - } - // Extract app manifest names - appManifestNames := make([]string, 0) - for _, a := range metadata.AppManifests { - appManifestNames = append(appManifestNames, a.Name) - } - - // Check that every AppManifest has a corresponding AppDescription - log.Tracef("Iterating app manifests length %v", len(metadata.AppManifests)) - for _, a := range metadata.AppManifests { - r := Result{ - Success: true, - ExpectedOneOf: appDescriptions, - Got: a.Name, - } - if !contains(a.Name, appDescriptions) { - log.Tracef("Device Description does not list the following App Manifest: %v", a.Name) - r.Success = false - result.DevDescResult.Summary.Success = false - result.Success = false - } - result.DevDescResult.CorrectApps = append(result.DevDescResult.CorrectApps, r) - } - - // Check that every App Description has a corresponding App Manifest - log.Tracef("Iterating app descriptions length %v", len(metadata.DeviceDescription.AppDescriptions)) - for _, desc := range metadata.DeviceDescription.AppDescriptions { - found := false - for _, manifest := range metadata.AppManifests { - if desc.AppManifest == manifest.Name { - found = true - } - } - if !found { - log.Tracef("No app manifest for app description: %v", desc.AppManifest) - r := Result{ - Success: false, - Got: desc.AppManifest, - ExpectedOneOf: appManifestNames, - } - result.DevDescResult.CorrectApps = append(result.DevDescResult.CorrectApps, r) - result.Success = false - } - } - - // Check that the Rtm Manifest is compatible with the OS Manifest - if contains(metadata.RtmManifest.Name, metadata.OsManifest.Rtms) { - result.DevDescResult.RtmOsCompatibility.Success = true - } else { - log.Tracef("RTM Manifest %v is not compatible with OS Manifest %v", - metadata.RtmManifest.Name, metadata.OsManifest.Name) - result.DevDescResult.RtmOsCompatibility.Success = false - result.DevDescResult.RtmOsCompatibility.ExpectedOneOf = metadata.OsManifest.Rtms - result.DevDescResult.RtmOsCompatibility.Got = metadata.RtmManifest.Name - result.DevDescResult.Summary.Success = false - result.Success = false - } - - // Check that the OS Manifest is compatible with all App Manifests - for _, a := range metadata.AppManifests { - r := Result{ - Success: true, - ExpectedOneOf: a.Oss, - Got: metadata.OsManifest.Name, - } - if !contains(metadata.OsManifest.Name, a.Oss) { - log.Tracef("OS Manifest %v is not compatible with App Manifest %v", metadata.OsManifest.Name, a.Name) - r.Success = false - result.DevDescResult.Summary.Success = false - result.Success = false - } - result.DevDescResult.OsAppsCompatibility = - append(result.DevDescResult.OsAppsCompatibility, r) - } - - // Validate policies if specified - result.PolicySuccess = true - if policies != nil { - p, ok := policyEngines[polEng] - if !ok { - log.Tracef("Internal error: policy engine %v not implemented", polEng) - result.Success = false - result.ErrorCode = PolicyEngineNotImplemented - result.PolicySuccess = false - } else { - ok = p.Validate(policies, result) - if !ok { - log.Trace("Custom policy validation failed") - result.Success = false - result.ErrorCode = VerifyPolicies - result.PolicySuccess = false - } - } - } else { - log.Tracef("No custom policies specified") - } - - // Add additional information - result.Prover = metadata.DeviceDescription.Name - if result.Prover == "" { - result.Prover = "Unknown" - } - result.Created = time.Now().Format(time.RFC3339) - - if result.Success { - log.Infof("SUCCESS: Verification for Prover %v (%v)", result.Prover, result.Created) - } else { - log.Infof("FAILED: Verification for Prover %v (%v)", result.Prover, result.Created) - } - - return result -} - -func extendSha256(hash []byte, data []byte) []byte { - concat := append(hash, data...) - h := sha256.Sum256(concat) - ret := make([]byte, 32) - copy(ret, h[:]) - return ret -} - -func extendSha384(hash []byte, data []byte) []byte { - concat := append(hash, data...) - h := sha512.Sum384(concat) - ret := make([]byte, 48) - copy(ret, h[:]) - return ret -} - -func verifyAr(attestationReport []byte, cas []*x509.Certificate, s Serializer, -) (*AttestationReport, TokenResult, ErrorCode) { - - ar := AttestationReport{} - - //Validate Attestation Report signature - result, payload, ok := s.VerifyToken(attestationReport, cas) - if !ok { - log.Trace("Validation of Attestation Report failed") - return nil, result, VerifyAR - } - - err := s.Unmarshal(payload, &ar) - if err != nil { - log.Tracef("Parsing of Attestation Report failed: %v", err) - return nil, result, ParseAR - } - - return &ar, result, NotSet -} - -func verifyMetadata(ar *AttestationReport, cas []*x509.Certificate, s Serializer, -) (*Metadata, *MetadataResult, bool) { - - metadata := &Metadata{} - result := &MetadataResult{} - success := true - - // Validate and unpack Rtm Manifest - tokenRes, payload, ok := s.VerifyToken(ar.RtmManifest, cas) - result.RtmResult.Summary = tokenRes.Summary - result.RtmResult.SignatureCheck = tokenRes.SignatureCheck - if !ok { - log.Trace("Validation of RTM Manifest failed") - success = false - } else { - err := s.Unmarshal(payload, &metadata.RtmManifest) - if err != nil { - log.Tracef("Unpacking of RTM Manifest failed: %v", err) - result.RtmResult.Summary.Success = false - result.RtmResult.Summary.ErrorCode = ParseRTMManifest - success = false - } else { - result.RtmResult.MetaInfo = metadata.RtmManifest.MetaInfo - result.RtmResult.ValidityCheck = checkValidity(metadata.RtmManifest.Validity) - result.RtmResult.Details = metadata.RtmManifest.Details - if !result.RtmResult.ValidityCheck.Success { - result.RtmResult.Summary.Success = false - success = false - } - } - } - - // Validate and unpack OS Manifest - tokenRes, payload, ok = s.VerifyToken(ar.OsManifest, cas) - result.OsResult.Summary = tokenRes.Summary - result.OsResult.SignatureCheck = tokenRes.SignatureCheck - if !ok { - log.Trace("Validation of OS Manifest failed") - success = false - } else { - err := s.Unmarshal(payload, &metadata.OsManifest) - if err != nil { - log.Tracef("Unpacking of OS Manifest failed: %v", err) - result.OsResult.Summary.Success = false - result.OsResult.Summary.ErrorCode = ParseOSManifest - success = false - } else { - result.OsResult.MetaInfo = metadata.OsManifest.MetaInfo - result.OsResult.ValidityCheck = checkValidity(metadata.OsManifest.Validity) - result.OsResult.Details = metadata.OsManifest.Details - if !result.OsResult.ValidityCheck.Success { - result.OsResult.Summary.Success = false - success = false - } - } - } - - // Validate and unpack App Manifests - for i, amSigned := range ar.AppManifests { - result.AppResults = append(result.AppResults, ManifestResult{}) - - tokenRes, payload, ok = s.VerifyToken(amSigned, cas) - result.AppResults[i].Summary = tokenRes.Summary - result.AppResults[i].SignatureCheck = tokenRes.SignatureCheck - if !ok { - log.Trace("Validation of App Manifest failed") - success = false - } else { - var am AppManifest - err := s.Unmarshal(payload, &am) - if err != nil { - log.Tracef("Unpacking of App Manifest failed: %v", err) - result.AppResults[i].Summary.Success = false - result.AppResults[i].Summary.ErrorCode = Parse - success = false - } else { - metadata.AppManifests = append(metadata.AppManifests, am) - result.AppResults[i].MetaInfo = am.MetaInfo - result.AppResults[i].ValidityCheck = checkValidity(am.Validity) - if !result.AppResults[i].ValidityCheck.Success { - log.Tracef("App Manifest %v validity check failed", am.Name) - result.AppResults[i].Summary.Success = false - success = false - - } - } - } - } - - // Validate and unpack Company Description if present - if ar.CompanyDescription != nil { - tokenRes, payload, ok = s.VerifyToken(ar.CompanyDescription, cas) - result.CompDescResult = &CompDescResult{} - result.CompDescResult.Summary = tokenRes.Summary - result.CompDescResult.SignatureCheck = tokenRes.SignatureCheck - if !ok { - log.Trace("Validation of Company Description Signatures failed") - success = false - } else { - err := s.Unmarshal(payload, &metadata.CompanyDescription) - if err != nil { - log.Tracef("Unpacking of Company Description failed: %v", err) - result.CompDescResult.Summary.Success = false - result.CompDescResult.Summary.ErrorCode = Parse - success = false - } else { - result.CompDescResult.MetaInfo = metadata.CompanyDescription.MetaInfo - result.CompDescResult.CompCertLevel = metadata.CompanyDescription.CertificationLevel - - result.CompDescResult.ValidityCheck = checkValidity(metadata.CompanyDescription.Validity) - if !result.CompDescResult.ValidityCheck.Success { - log.Trace("Company Description invalid") - result.CompDescResult.Summary.Success = false - success = false - } - } - } - } - - // Validate and unpack Device Description - tokenRes, payload, ok = s.VerifyToken(ar.DeviceDescription, cas) - result.DevDescResult.Summary = tokenRes.Summary - result.DevDescResult.SignatureCheck = tokenRes.SignatureCheck - if !ok { - log.Trace("Validation of Device Description failed") - success = false - } else { - err := s.Unmarshal(payload, &metadata.DeviceDescription) - if err != nil { - log.Tracef("Unpacking of Device Description failed: %v", err) - result.DevDescResult.Summary.Success = false - result.DevDescResult.Summary.ErrorCode = Parse - success = false - } else { - result.DevDescResult.MetaInfo = metadata.DeviceDescription.MetaInfo - result.DevDescResult.Description = metadata.DeviceDescription.Description - result.DevDescResult.Location = metadata.DeviceDescription.Location - } - for _, appDesc := range metadata.DeviceDescription.AppDescriptions { - appResult := AppDescResult{ - MetaInfo: appDesc.MetaInfo, - AppManifest: appDesc.AppManifest, - External: appDesc.External, - Environment: appDesc.Environment, - } - result.DevDescResult.AppResults = append(result.DevDescResult.AppResults, - appResult) - } - } - - return metadata, result, success -} - -func checkValidity(val Validity) Result { - result := Result{} - result.Success = true - - notBefore, err := time.Parse(time.RFC3339, val.NotBefore) - if err != nil { - log.Tracef("Failed to parse NotBefore time. Time.Parse returned %v", err) - result.Success = false - result.ErrorCode = ParseTime - return result - } - notAfter, err := time.Parse(time.RFC3339, val.NotAfter) - if err != nil { - log.Tracef("Failed to parse NotAfter time. Time.Parse returned %v", err) - result.Success = false - result.ErrorCode = ParseTime - return result - } - currentTime := time.Now() - - if notBefore.After(currentTime) { - log.Trace("Validity check failed: Artifact is not valid yet") - result.Success = false - result.ErrorCode = NotYetValid - } - - if currentTime.After(notAfter) { - log.Trace("Validity check failed: Artifact validity has expired") - result.Success = false - result.ErrorCode = Expired - } - - return result -} - -func collectReferenceValues(metadata *Metadata) (map[string][]ReferenceValue, error) { - // Gather a list of all reference values independent of the type - verList := append(metadata.RtmManifest.ReferenceValues, metadata.OsManifest.ReferenceValues...) - for _, appManifest := range metadata.AppManifests { - verList = append(verList, appManifest.ReferenceValues...) - } - - verMap := make(map[string][]ReferenceValue) - - // Iterate through the reference values and sort them into the different types - for _, v := range verList { - if v.Type != "SNP Reference Value" && v.Type != "SW Reference Value" && v.Type != "TPM Reference Value" && v.Type != "TDX Reference Value" && v.Type != "SGX Reference Value" { - return nil, fmt.Errorf("reference value of type %v is not supported", v.Type) - } - verMap[v.Type] = append(verMap[v.Type], v) - } - return verMap, nil -} - -func checkExtensionUint8(cert *x509.Certificate, oid string, value uint8) (Result, bool) { - - for _, ext := range cert.Extensions { - - if ext.Id.String() == oid { - if len(ext.Value) != 3 && len(ext.Value) != 4 { - log.Tracef("extension %v value unexpected length %v (expected 3 or 4)", - oid, len(ext.Value)) - return Result{Success: false, ErrorCode: OidLength}, false - } - if ext.Value[0] != 0x2 { - log.Tracef("extension %v value[0]: %v does not match expected value 2 (tag Integer)", - oid, ext.Value[0]) - return Result{Success: false, ErrorCode: OidTag}, false - } - if ext.Value[1] == 0x1 { - if ext.Value[2] != value { - log.Tracef("extension %v value[2]: %v does not match expected value %v", - oid, ext.Value[2], value) - return Result{ - Success: false, - Expected: strconv.FormatUint(uint64(value), 10), - Got: strconv.FormatUint(uint64(ext.Value[2]), 10), - }, false - } - } else if ext.Value[1] == 0x2 { - // Due to openssl, the sign bit must remain zero for positive integers - // even though this field is defined as unsigned int in the AMD spec - // Thus, if the most significant bit is required, one byte of additional 0x00 padding is added - if ext.Value[2] != 0x00 || ext.Value[3] != value { - log.Tracef("extension %v value = %v%v does not match expected value %v", - oid, ext.Value[2], ext.Value[3], value) - return Result{ - Success: false, - Expected: strconv.FormatUint(uint64(value), 10), - Got: strconv.FormatUint(uint64(ext.Value[3]), 10), - }, false - } - } else { - log.Tracef("extension %v value[1]: %v does not match expected value 1 or 2 (length of integer)", - oid, ext.Value[1]) - return Result{Success: false, ErrorCode: OidLength}, false - } - return Result{Success: true}, true - } - } - - log.Tracef("extension %v not present in certificate", oid) - return Result{Success: false, ErrorCode: OidNotPresent}, false -} - -func checkExtensionBuf(cert *x509.Certificate, oid string, buf []byte) (Result, bool) { - - for _, ext := range cert.Extensions { - - if ext.Id.String() == oid { - if cmp := bytes.Compare(ext.Value, buf); cmp != 0 { - log.Tracef("extension %v value %v does not match expected value %v", - oid, hex.EncodeToString(ext.Value), hex.EncodeToString(buf)) - return Result{ - Success: false, - Expected: hex.EncodeToString(buf), - Got: hex.EncodeToString(ext.Value), - }, false - } - return Result{Success: true}, true - } - } - - log.Tracef("extension %v not present in certificate", oid) - return Result{Success: false, ErrorCode: OidNotPresent}, false -} - -func contains(elem string, list []string) bool { - for _, s := range list { - if s == elem { - return true - } - } - return false -} diff --git a/attestationreport/cbor_test.go b/attestationreport/cbor_test.go index c7f957d1..63de0956 100644 --- a/attestationreport/cbor_test.go +++ b/attestationreport/cbor_test.go @@ -16,6 +16,7 @@ package attestationreport import ( + "crypto" "crypto/ecdsa" "crypto/x509" "encoding/pem" @@ -25,6 +26,35 @@ import ( "github.com/sirupsen/logrus" ) +type SwSigner struct { + certChain []*x509.Certificate + priv crypto.PrivateKey +} + +func (s *SwSigner) Init(c *DriverConfig) error { + return nil +} + +func (s *SwSigner) Measure(nonce []byte) (Measurement, error) { + return Measurement{}, nil +} + +func (s *SwSigner) Lock() error { + return nil +} + +func (s *SwSigner) Unlock() error { + return nil +} + +func (s *SwSigner) GetSigningKeys() (crypto.PrivateKey, crypto.PublicKey, error) { + return s.priv, &s.priv.(*ecdsa.PrivateKey).PublicKey, nil +} + +func (s *SwSigner) GetCertChain() ([]*x509.Certificate, error) { + return s.certChain, nil +} + func TestVerifyCbor(t *testing.T) { type args struct { ar AttestationReport diff --git a/attestedtls/libapi.go b/attestedtls/libapi.go index 959a4e0b..7fe5e3c8 100644 --- a/attestedtls/libapi.go +++ b/attestedtls/libapi.go @@ -24,8 +24,9 @@ import ( "errors" "fmt" - ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/Fraunhofer-AISEC/cmc/generate" "github.com/Fraunhofer-AISEC/cmc/internal" + "github.com/Fraunhofer-AISEC/cmc/verify" ) type LibApi struct{} @@ -48,13 +49,13 @@ func (a LibApi) obtainAR(cc CmcConfig, chbindings []byte) ([]byte, error) { log.Debug("Prover: Generating Attestation Report with nonce: ", hex.EncodeToString(chbindings)) - report, err := ar.Generate(chbindings, cc.Cmc.Metadata, cc.Cmc.Drivers, cc.Cmc.Serializer) + report, err := generate.Generate(chbindings, cc.Cmc.Metadata, cc.Cmc.Drivers, cc.Cmc.Serializer) if err != nil { return nil, fmt.Errorf("failed to generate attestation report: %w", err) } log.Debug("Prover: Signing Attestation Report") - signedReport, err := ar.Sign(report, cc.Cmc.Drivers[0], cc.Cmc.Serializer) + signedReport, err := generate.Sign(report, cc.Cmc.Drivers[0], cc.Cmc.Serializer) if err != nil { return nil, errors.New("prover: failed to sign Attestion Report ") } @@ -66,7 +67,7 @@ func (a LibApi) obtainAR(cc CmcConfig, chbindings []byte) ([]byte, error) { func (a LibApi) verifyAR(chbindings, report []byte, cc CmcConfig) error { log.Debug("Verifier: Verifying Attestation Report") - result := ar.Verify(report, chbindings, cc.Ca, nil, cc.Cmc.PolicyEngineSelect, cc.Cmc.IntelStorage) + result := verify.Verify(report, chbindings, cc.Ca, nil, cc.Cmc.PolicyEngineSelect, cc.Cmc.IntelStorage) // Return attestation result via callback if specified if cc.ResultCb != nil { diff --git a/cmc/cmc.go b/cmc/cmc.go index 08d41262..803a743e 100644 --- a/cmc/cmc.go +++ b/cmc/cmc.go @@ -23,14 +23,15 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" + verify "github.com/Fraunhofer-AISEC/cmc/verify" ) var ( log = logrus.WithField("service", "cmc") - policyEngines = map[string]ar.PolicyEngineSelect{ - "js": ar.PolicyEngineSelect_JS, - "duktape": ar.PolicyEngineSelect_DukTape, + policyEngines = map[string]verify.PolicyEngineSelect{ + "js": verify.PolicyEngineSelect_JS, + "duktape": verify.PolicyEngineSelect_DukTape, } drivers = map[string]ar.Driver{} @@ -60,7 +61,7 @@ type Config struct { type Cmc struct { Metadata [][]byte - PolicyEngineSelect ar.PolicyEngineSelect + PolicyEngineSelect verify.PolicyEngineSelect Drivers []ar.Driver Serializer ar.Serializer Network string @@ -75,7 +76,7 @@ func GetDrivers() map[string]ar.Driver { return drivers } -func GetPolicyEngines() map[string]ar.PolicyEngineSelect { +func GetPolicyEngines() map[string]verify.PolicyEngineSelect { return policyEngines } diff --git a/cmcd/coap.go b/cmcd/coap.go index cf672862..940a8248 100644 --- a/cmcd/coap.go +++ b/cmcd/coap.go @@ -34,10 +34,11 @@ import ( // local modules "github.com/Fraunhofer-AISEC/cmc/api" - ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/cmc" + "github.com/Fraunhofer-AISEC/cmc/generate" "github.com/Fraunhofer-AISEC/cmc/internal" m "github.com/Fraunhofer-AISEC/cmc/measure" + "github.com/Fraunhofer-AISEC/cmc/verify" ) // CoapServer is the CoAP server structure @@ -128,7 +129,7 @@ func Attest(w mux.ResponseWriter, r *mux.Message) { log.Debug("Prover: Generating Attestation Report with nonce: ", hex.EncodeToString(req.Nonce)) - report, err := ar.Generate(req.Nonce, Cmc.Metadata, Cmc.Drivers, Cmc.Serializer) + report, err := generate.Generate(req.Nonce, Cmc.Metadata, Cmc.Drivers, Cmc.Serializer) if err != nil { sendCoapError(w, r, codes.InternalServerError, "failed to generate attestation report: %v", err) @@ -136,7 +137,7 @@ func Attest(w mux.ResponseWriter, r *mux.Message) { } log.Debug("Prover: Signing Attestation Report") - data, err := ar.Sign(report, Cmc.Drivers[0], Cmc.Serializer) + data, err := generate.Sign(report, Cmc.Drivers[0], Cmc.Serializer) if err != nil { sendCoapError(w, r, codes.InternalServerError, "Failed to sign attestation report: %v", err) @@ -172,7 +173,7 @@ func Verify(w mux.ResponseWriter, r *mux.Message) { } log.Debug("Verifier: Verifying Attestation Report") - result := ar.Verify(req.AttestationReport, req.Nonce, req.Ca, req.Policies, + result := verify.Verify(req.AttestationReport, req.Nonce, req.Ca, req.Policies, Cmc.PolicyEngineSelect, Cmc.IntelStorage) log.Debug("Verifier: Marshaling Attestation Result") diff --git a/cmcd/grpc.go b/cmcd/grpc.go index e9295584..9a5aefed 100644 --- a/cmcd/grpc.go +++ b/cmcd/grpc.go @@ -34,11 +34,12 @@ import ( // local modules - ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/cmc" + "github.com/Fraunhofer-AISEC/cmc/generate" api "github.com/Fraunhofer-AISEC/cmc/grpcapi" "github.com/Fraunhofer-AISEC/cmc/internal" m "github.com/Fraunhofer-AISEC/cmc/measure" + "github.com/Fraunhofer-AISEC/cmc/verify" ) type GrpcServerWrapper struct{} @@ -97,7 +98,7 @@ func (s *GrpcServer) Attest(ctx context.Context, in *api.AttestationRequest) (*a log.Info("Prover: Generating Attestation Report with nonce: ", hex.EncodeToString(in.Nonce)) - report, err := ar.Generate(in.Nonce, s.cmc.Metadata, s.cmc.Drivers, s.cmc.Serializer) + report, err := generate.Generate(in.Nonce, s.cmc.Metadata, s.cmc.Drivers, s.cmc.Serializer) if err != nil { return &api.AttestationResponse{ Status: api.Status_FAIL, @@ -105,7 +106,7 @@ func (s *GrpcServer) Attest(ctx context.Context, in *api.AttestationRequest) (*a } log.Info("Prover: Signing Attestation Report") - data, err := ar.Sign(report, s.cmc.Drivers[0], s.cmc.Serializer) + data, err := generate.Sign(report, s.cmc.Drivers[0], s.cmc.Serializer) if err != nil { return &api.AttestationResponse{ Status: api.Status_FAIL, @@ -129,7 +130,7 @@ func (s *GrpcServer) Verify(ctx context.Context, in *api.VerificationRequest) (* log.Info("Received Connection Request Type 'Verification Request'") log.Info("Verifier: Verifying Attestation Report") - result := ar.Verify(in.AttestationReport, in.Nonce, in.Ca, in.Policies, + result := verify.Verify(in.AttestationReport, in.Nonce, in.Ca, in.Policies, s.cmc.PolicyEngineSelect, s.cmc.IntelStorage) log.Info("Verifier: Marshaling Attestation Result") diff --git a/cmcd/socket.go b/cmcd/socket.go index dc6556b6..4440472e 100644 --- a/cmcd/socket.go +++ b/cmcd/socket.go @@ -33,10 +33,11 @@ import ( // local modules "github.com/Fraunhofer-AISEC/cmc/api" - ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/cmc" + "github.com/Fraunhofer-AISEC/cmc/generate" "github.com/Fraunhofer-AISEC/cmc/internal" m "github.com/Fraunhofer-AISEC/cmc/measure" + "github.com/Fraunhofer-AISEC/cmc/verify" ) // Server is the server structure @@ -89,7 +90,7 @@ func handleIncoming(conn net.Conn, cmc *cmc.Cmc) { case api.TypeAttest: attest(conn, payload, cmc) case api.TypeVerify: - verify(conn, payload, cmc) + validate(conn, payload, cmc) case api.TypeMeasure: measure(conn, payload, cmc) case api.TypeTLSCert: @@ -123,14 +124,14 @@ func attest(conn net.Conn, payload []byte, cmc *cmc.Cmc) { log.Debugf("Prover: Generating Attestation Report with nonce: %v", hex.EncodeToString(req.Nonce)) - report, err := ar.Generate(req.Nonce, cmc.Metadata, cmc.Drivers, cmc.Serializer) + report, err := generate.Generate(req.Nonce, cmc.Metadata, cmc.Drivers, cmc.Serializer) if err != nil { api.SendError(conn, "failed to generate attestation report: %v", err) return } log.Debug("Prover: Signing Attestation Report") - r, err := ar.Sign(report, cmc.Drivers[0], cmc.Serializer) + r, err := generate.Sign(report, cmc.Drivers[0], cmc.Serializer) if err != nil { api.SendError(conn, "Failed to sign attestation report: %v", err) return @@ -154,7 +155,7 @@ func attest(conn net.Conn, payload []byte, cmc *cmc.Cmc) { log.Debug("Prover: Finished") } -func verify(conn net.Conn, payload []byte, cmc *cmc.Cmc) { +func validate(conn net.Conn, payload []byte, cmc *cmc.Cmc) { log.Debug("Received Connection Request Type 'Verification Request'") @@ -166,7 +167,7 @@ func verify(conn net.Conn, payload []byte, cmc *cmc.Cmc) { } log.Debug("Verifier: Verifying Attestation Report") - result := ar.Verify(req.AttestationReport, req.Nonce, req.Ca, req.Policies, + result := verify.Verify(req.AttestationReport, req.Nonce, req.Ca, req.Policies, cmc.PolicyEngineSelect, cmc.IntelStorage) log.Debug("Verifier: Marshaling Attestation Result") diff --git a/generate/generate.go b/generate/generate.go new file mode 100644 index 00000000..1181e389 --- /dev/null +++ b/generate/generate.go @@ -0,0 +1,130 @@ +// Copyright (c) 2021 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generate + +import ( + "errors" + "fmt" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/sirupsen/logrus" +) + +var log = logrus.WithField("service", "ar") + +// Generate generates an attestation report with the provided +// nonce and manifests and descriptions metadata. The manifests and descriptions +// must be either raw JWS tokens in the JWS JSON full serialization +// format or CBOR COSE tokens. Takes a list of measurers providing a method +// for collecting the measurements from a hardware or software interface +func Generate(nonce []byte, metadata [][]byte, measurers []ar.Driver, s ar.Serializer) ([]byte, error) { + + if s == nil { + return nil, errors.New("serializer not specified") + } + + // Create attestation report object which will be filled with the attestation + // data or sent back incomplete in case errors occur + report := ar.AttestationReport{ + Type: "Attestation Report", + } + + if len(nonce) > 32 { + return nil, fmt.Errorf("nonce exceeds maximum length of 32 bytes") + } + report.Nonce = nonce + + log.Debug("Adding manifests and descriptions to Attestation Report..") + + // Retrieve the manifests and descriptions + log.Trace("Parsing ", len(metadata), " meta-data objects..") + numManifests := 0 + for i := 0; i < len(metadata); i++ { + + // Extract plain payload (i.e. the manifest/description itself) + data, err := s.GetPayload(metadata[i]) + if err != nil { + log.Tracef("Failed to parse metadata object %v: %v", i, err) + continue + } + + // Unmarshal the Type field of the JSON file to determine the type for + // later processing + elem := new(ar.MetaInfo) + err = s.Unmarshal(data, elem) + if err != nil { + log.Tracef("Failed to unmarshal data from metadata object %v: %v", i, err) + continue + } + + switch elem.Type { + case "App Manifest": + log.Debug("Adding App Manifest") + report.AppManifests = append(report.AppManifests, metadata[i]) + numManifests++ + case "OS Manifest": + log.Debug("Adding OS Manifest") + report.OsManifest = metadata[i] + numManifests++ + case "RTM Manifest": + log.Debug("Adding RTM Manifest") + report.RtmManifest = metadata[i] + numManifests++ + case "Device Description": + log.Debug("Adding Device Description") + report.DeviceDescription = metadata[i] + case "Company Description": + log.Debug("Adding Company Description") + report.CompanyDescription = metadata[i] + } + } + + if numManifests == 0 { + log.Warn("Did not find any manifests for the attestation report") + } else { + log.Debug("Added ", numManifests, " manifests to attestation report") + } + + log.Tracef("Retrieving measurements from %v measurers", len(measurers)) + for _, measurer := range measurers { + + // This actually collects the measurements. The methods are implemented + // in the respective module (e.g. tpm module) + log.Trace("Getting measurements from measurement interface..") + measurement, err := measurer.Measure(nonce) + if err != nil { + return nil, fmt.Errorf("failed to get measurements: %v", err) + } + + report.Measurements = append(report.Measurements, measurement) + log.Tracef("Added %v to attestation report", measurement.Type) + } + + log.Trace("Finished attestation report generation") + + // Marshal data to bytes + data, err := s.Marshal(report) + if err != nil { + return nil, fmt.Errorf("failed to marshal the Attestation Report: %v", err) + } + + return data, nil +} + +// Sign signs the attestation report with the specified signer 'signer' +func Sign(report []byte, signer ar.Driver, s ar.Serializer) ([]byte, error) { + return s.Sign(report, signer) +} diff --git a/sgxdriver/sgxdriver.go b/sgxdriver/sgxdriver.go index cc972294..f1676395 100644 --- a/sgxdriver/sgxdriver.go +++ b/sgxdriver/sgxdriver.go @@ -36,6 +36,7 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" est "github.com/Fraunhofer-AISEC/cmc/est/estclient" "github.com/Fraunhofer-AISEC/cmc/internal" + verify "github.com/Fraunhofer-AISEC/cmc/verify" "github.com/edgelesssys/ego/enclave" "github.com/sirupsen/logrus" @@ -352,7 +353,7 @@ func downloadSgxCertChain(c *ar.DriverConfig) ([]*x509.Certificate, error) { resp.Body.Close() // Extract FMSPC from PCK certificate SGX Extensions - sgxExtensions, err := ar.ParseSGXExtensions(pckCert.Extensions[ar.SGX_EXTENSION_INDEX].Value[4:]) + sgxExtensions, err := verify.ParseSGXExtensions(pckCert.Extensions[verify.SGX_EXTENSION_INDEX].Value[4:]) if err != nil { return certificates, err } diff --git a/snpdriver/snpdriver.go b/snpdriver/snpdriver.go index 672777d4..3d8e0632 100644 --- a/snpdriver/snpdriver.go +++ b/snpdriver/snpdriver.go @@ -42,6 +42,7 @@ import ( ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" est "github.com/Fraunhofer-AISEC/cmc/est/estclient" "github.com/Fraunhofer-AISEC/cmc/internal" + "github.com/Fraunhofer-AISEC/cmc/verify" "github.com/sirupsen/logrus" ) @@ -276,7 +277,7 @@ func getSnpCertChain(addr string) ([]*x509.Certificate, error) { if err != nil { return nil, fmt.Errorf("failed to get SNP report: %w", err) } - s, err := ar.DecodeSnpReport(arRaw) + s, err := verify.DecodeSnpReport(arRaw) if err != nil { return nil, fmt.Errorf("failed to decode SNP report: %w", err) } diff --git a/testtool/libapi.go b/testtool/libapi.go index 172d30d4..260fffaf 100644 --- a/testtool/libapi.go +++ b/testtool/libapi.go @@ -28,10 +28,11 @@ import ( "fmt" "os" - ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/attestedtls" "github.com/Fraunhofer-AISEC/cmc/cmc" + g "github.com/Fraunhofer-AISEC/cmc/generate" m "github.com/Fraunhofer-AISEC/cmc/measure" + v "github.com/Fraunhofer-AISEC/cmc/verify" ) type LibApi struct { @@ -65,7 +66,7 @@ func (a LibApi) generate(c *config) { } // Generate attestation report - report, err := ar.Generate(nonce, a.cmc.Metadata, a.cmc.Drivers, a.cmc.Serializer) + report, err := g.Generate(nonce, a.cmc.Metadata, a.cmc.Drivers, a.cmc.Serializer) if err != nil { log.Errorf("Failed to generate attestation report: %v", err) return @@ -73,7 +74,7 @@ func (a LibApi) generate(c *config) { // Sign attestation report log.Debug("Prover: Signing Attestation Report") - r, err := ar.Sign(report, a.cmc.Drivers[0], a.cmc.Serializer) + r, err := g.Sign(report, a.cmc.Drivers[0], a.cmc.Serializer) if err != nil { log.Errorf("Failed to sign attestation report: %v", err) return @@ -116,7 +117,7 @@ func (a LibApi) verify(c *config) { } // Verify the attestation report - result := ar.Verify(report, nonce, c.ca, c.policies, a.cmc.PolicyEngineSelect, a.cmc.IntelStorage) + result := v.Verify(report, nonce, c.ca, c.policies, a.cmc.PolicyEngineSelect, a.cmc.IntelStorage) log.Debug("Verifier: Marshaling Attestation Result") r, err := json.Marshal(result) diff --git a/tools/fmspc-retrieval-tool/main.go b/tools/fmspc-retrieval-tool/main.go index 5183b872..821a1361 100644 --- a/tools/fmspc-retrieval-tool/main.go +++ b/tools/fmspc-retrieval-tool/main.go @@ -7,8 +7,8 @@ import ( "io" "net/http" - "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" + "github.com/Fraunhofer-AISEC/cmc/verify" ) var ( @@ -47,7 +47,7 @@ func main() { return } - sgxExtensions, err := attestationreport.ParseSGXExtensions(cert[0].Extensions[attestationreport.SGX_EXTENSION_INDEX].Value[4:]) + sgxExtensions, err := verify.ParseSGXExtensions(cert[0].Extensions[verify.SGX_EXTENSION_INDEX].Value[4:]) if err != nil { fmt.Println("failed to parse SGX extensions") return diff --git a/attestationreport/duktapepolicies.go b/verify/duktapepolicies.go similarity index 87% rename from attestationreport/duktapepolicies.go rename to verify/duktapepolicies.go index 4678511d..a25dd00a 100644 --- a/attestationreport/duktapepolicies.go +++ b/verify/duktapepolicies.go @@ -15,12 +15,13 @@ //go:build !nodefaults || duktapepolicies -package attestationreport +package verify import ( "encoding/json" "github.com/Fraunhofer-AISEC/cmc/attestationpolicies/duktape" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) type DukTapePolicyEngine struct{} @@ -29,7 +30,7 @@ func init() { policyEngines[PolicyEngineSelect_DukTape] = DukTapePolicyEngine{} } -func (p DukTapePolicyEngine) Validate(policies []byte, result VerificationResult) bool { +func (p DukTapePolicyEngine) Validate(policies []byte, result ar.VerificationResult) bool { vr, err := json.Marshal(result) if err != nil { log.Errorf("Failed to marshal verification result: %v", err) diff --git a/attestationreport/iat.go b/verify/iat.go similarity index 81% rename from attestationreport/iat.go rename to verify/iat.go index 48fd5a02..ccb59413 100644 --- a/attestationreport/iat.go +++ b/verify/iat.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" @@ -21,6 +21,7 @@ import ( "crypto/x509" "encoding/hex" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" "github.com/veraison/go-cose" ) @@ -47,11 +48,11 @@ type Iat struct { Vsi string `cbor:"-75010,keyasint,omitempty"` } -func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []ReferenceValue, +func verifyIasMeasurements(iasM ar.Measurement, nonce []byte, referenceValues []ar.ReferenceValue, cas []*x509.Certificate, -) (*MeasurementResult, bool) { +) (*ar.MeasurementResult, bool) { - result := &MeasurementResult{ + result := &ar.MeasurementResult{ Type: "IAT Result", } ok := true @@ -60,23 +61,23 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref certs, err := internal.ParseCertsDer(iasM.Certs) if err != nil { log.Tracef("failed to parse IAS certificates: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } if len(referenceValues) == 0 { log.Tracef("Could not find IAS Reference Value") - result.Summary.SetErr(RefValNotPresent) + result.Summary.SetErr(ar.RefValNotPresent) return result, false } - s := CborSerializer{} + s := ar.CborSerializer{} log.Trace("Verifying CBOR IAT") iatresult, payload, ok := verifyIat(iasM.Evidence, certs[0]) if !ok { log.Tracef("IAS signature verification failed") - result.Summary.SetErr(VerifySignature) + result.Summary.SetErr(ar.VerifySignature) return result, false } result.Signature.SignCheck = iatresult @@ -87,7 +88,7 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref err = s.Unmarshal(payload, iat) if err != nil { log.Tracef("Failed to unmarshal IAT: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } @@ -110,7 +111,7 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref x509Chains, err := internal.VerifyCertChain(certs, cas) if err != nil { log.Tracef("Failed to verify certificate chain: %v", err) - result.Signature.CertChainCheck.SetErr(VerifyCertChain) + result.Signature.CertChainCheck.SetErr(ar.VerifyCertChain) ok = false } else { result.Signature.CertChainCheck.Success = true @@ -118,9 +119,9 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref //Store details from (all) validated certificate chain(s) in the report for _, chain := range x509Chains { - chainExtracted := []X509CertExtracted{} + chainExtracted := []ar.X509CertExtracted{} for _, cert := range chain { - chainExtracted = append(chainExtracted, ExtractX509Infos(cert)) + chainExtracted = append(chainExtracted, ar.ExtractX509Infos(cert)) } result.Signature.ValidatedCerts = append(result.Signature.ValidatedCerts, chainExtracted) } @@ -134,14 +135,14 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref log.Tracef("Found reference value %v: %v", ver.Name, hex.EncodeToString(ver.Sha256)) if ver.Type != "IAS Reference Value" { log.Tracef("IAS Reference Value invalid type %v", ver.Type) - result.Summary.SetErr(RefValType) + result.Summary.SetErr(ar.RefValType) return result, false } found := false for _, swc := range iat.SwComponents { if bytes.Equal(ver.Sha256, swc.MeasurementValue) { result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: ver.Name, Digest: hex.EncodeToString(ver.Sha256), Success: true, @@ -152,7 +153,7 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref if !found { ok = false result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: ver.Name, Digest: hex.EncodeToString(ver.Sha256), Success: false, @@ -168,7 +169,7 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref found := false for _, ver := range referenceValues { if bytes.Equal(ver.Sha256, swc.MeasurementValue) { - result.Artifacts = append(result.Artifacts, DigestResult{ + result.Artifacts = append(result.Artifacts, ar.DigestResult{ Name: ver.Name, Digest: hex.EncodeToString(swc.MeasurementValue), Success: true, @@ -178,7 +179,7 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref } if !found { ok = false - result.Artifacts = append(result.Artifacts, DigestResult{ + result.Artifacts = append(result.Artifacts, ar.DigestResult{ Name: swc.MeasurementDescription, Digest: hex.EncodeToString(swc.MeasurementValue), Success: false, @@ -190,34 +191,34 @@ func verifyIasMeasurements(iasM Measurement, nonce []byte, referenceValues []Ref return result, ok } -func verifyIat(data []byte, cert *x509.Certificate) (Result, []byte, bool) { +func verifyIat(data []byte, cert *x509.Certificate) (ar.Result, []byte, bool) { // create a Sign1Message from a raw COSE_Sign payload var msgToVerify cose.Sign1Message err := msgToVerify.UnmarshalCBOR(data) if err != nil { log.Tracef("error unmarshalling cose: %v", err) - return Result{Success: false, ErrorCode: ParseEvidence}, nil, false + return ar.Result{Success: false, ErrorCode: ar.ParseEvidence}, nil, false } publicKey, okKey := cert.PublicKey.(*ecdsa.PublicKey) if !okKey { log.Tracef("Failed to extract public key from certificate: %v", err) - return Result{Success: false, ErrorCode: ExtractPubKey}, nil, false + return ar.Result{Success: false, ErrorCode: ar.ExtractPubKey}, nil, false } // create a verifier from a trusted private key verifier, err := cose.NewVerifier(cose.AlgorithmES256, publicKey) if err != nil { log.Tracef("Failed to create verifier: %v", err) - return Result{Success: false, ErrorCode: Internal}, nil, false + return ar.Result{Success: false, ErrorCode: ar.Internal}, nil, false } err = msgToVerify.Verify(nil, verifier) if err != nil { log.Tracef("Failed to verify COSE token: %v", err) - return Result{Success: false, ErrorCode: VerifySignature}, nil, false + return ar.Result{Success: false, ErrorCode: ar.VerifySignature}, nil, false } - return Result{Success: true}, msgToVerify.Payload, true + return ar.Result{Success: true}, msgToVerify.Payload, true } diff --git a/attestationreport/iat_test.go b/verify/iat_test.go similarity index 93% rename from attestationreport/iat_test.go rename to verify/iat_test.go index 83a4261d..0d9f5361 100644 --- a/attestationreport/iat_test.go +++ b/verify/iat_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "crypto/x509" @@ -21,14 +21,15 @@ import ( "reflect" "testing" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/sirupsen/logrus" ) func Test_verifyIasMeasurements(t *testing.T) { type args struct { - IasM *Measurement + IasM *ar.Measurement nonce []byte - referenceValues []ReferenceValue + referenceValues []ar.ReferenceValue ca *x509.Certificate } tests := []struct { @@ -39,13 +40,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid IAT", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "IAS Measurement", Evidence: invalidIat, Certs: [][]byte{validIasCert.Raw, validIasCa.Raw}, }, nonce: validIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ validSpeReferenceValue, validNspeReferenceValue, }, @@ -56,13 +57,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid Cert", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "IAS Measurement", Evidence: validIat, Certs: [][]byte{invalidIasCert.Raw, validIasCa.Raw}, }, nonce: validIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ validSpeReferenceValue, validNspeReferenceValue, }, @@ -73,13 +74,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid CA", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "IAS Measurement", Evidence: validIat, Certs: [][]byte{validIasCert.Raw, validIasCa.Raw}, }, nonce: validIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ validSpeReferenceValue, validNspeReferenceValue, }, @@ -90,13 +91,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid Nonce", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "IAS Measurement", Evidence: validIat, Certs: [][]byte{validIasCert.Raw, validIasCa.Raw}, }, nonce: invalidIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ validSpeReferenceValue, validNspeReferenceValue, }, @@ -107,13 +108,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid Reference Value", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "IAS Measurement", Evidence: validIat, Certs: [][]byte{validIasCert.Raw, validIasCa.Raw}, }, nonce: validIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ invalidSpeReferenceValue, validNspeReferenceValue, }, @@ -124,13 +125,13 @@ func Test_verifyIasMeasurements(t *testing.T) { { name: "Invalid Measurement", args: args{ - IasM: &Measurement{ + IasM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validIat, Certs: [][]byte{validIasCert.Raw, validIasCa.Raw}, }, nonce: validIasNonce, - referenceValues: []ReferenceValue{ + referenceValues: []ar.ReferenceValue{ invalidSpeReferenceValue, validNspeReferenceValue, }, @@ -178,19 +179,19 @@ var ( validNspeMeasurement, _ = hex.DecodeString("73deb833155c9c317d838b5368b6a63bd38706fa874e874c1b5affe4571b348b") - validSpeReferenceValue = ReferenceValue{ + validSpeReferenceValue = ar.ReferenceValue{ Type: "IAS Reference Value", Name: "SPE Measurement", Sha256: validSpeMeasurement, } - invalidSpeReferenceValue = ReferenceValue{ + invalidSpeReferenceValue = ar.ReferenceValue{ Type: "IAS Reference Value", Name: "SPE Measurement", Sha256: invalidSpeMeasurement, } - validNspeReferenceValue = ReferenceValue{ + validNspeReferenceValue = ar.ReferenceValue{ Type: "IAS Reference Value", Name: "NSPE Measurement", Sha256: validNspeMeasurement, diff --git a/attestationreport/intel_helpers.go b/verify/intel_helpers.go similarity index 91% rename from attestationreport/intel_helpers.go rename to verify/intel_helpers.go index 7973a0fc..14f056dc 100644 --- a/attestationreport/intel_helpers.go +++ b/verify/intel_helpers.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" @@ -35,6 +35,7 @@ import ( "strconv" "time" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" ) @@ -118,7 +119,7 @@ type ECDSA256QuoteSignatureDataStructure struct { type TcbInfo struct { TcbInfo TcbInfoBody `json:"tcbInfo"` - Signature HexByte `json:"signature"` + Signature ar.HexByte `json:"signature"` } type TcbInfoBody struct { @@ -126,8 +127,8 @@ type TcbInfoBody struct { Version uint32 `json:"version"` IssueDate time.Time `json:"issueDate"` NextUpdate time.Time `json:"nextUpdate"` - Fmspc HexByte `json:"fmspc"` - PceId HexByte `json:"pceId"` + Fmspc ar.HexByte `json:"fmspc"` + PceId ar.HexByte `json:"pceId"` TcbType uint32 `json:"tcbType"` TcbEvaluationDataNumber uint32 `json:"tcbEvaluationDataNumber"` TcbLevels []TcbLevel `json:"tcbLevels"` @@ -154,9 +155,9 @@ type TcbComponent struct { } type TdxModule struct { - Mrsigner HexByte `json:"mrsigner"` - Attributes HexByte `json:"attributes"` - AttributesMask HexByte `json:"attributesMask"` + Mrsigner ar.HexByte `json:"mrsigner"` + Attributes ar.HexByte `json:"attributes"` + AttributesMask ar.HexByte `json:"attributesMask"` } type SgxCertificates struct { @@ -168,7 +169,7 @@ type SgxCertificates struct { type QEIdentity struct { EnclaveIdentity QEIdentityBody `json:"enclaveIdentity"` - Signature HexByte `json:"signature"` + Signature ar.HexByte `json:"signature"` } type QEIdentityBody struct { @@ -177,11 +178,11 @@ type QEIdentityBody struct { IssueDate time.Time `json:"issueDate"` NextUpdate time.Time `json:"nextUpdate"` TcbEvaluationDataNumber uint32 `json:"tcbEvaluationDataNumber"` - Miscselect HexByte `json:"miscselect"` - MiscselectMask HexByte `json:"miscselectMask"` - Attributes HexByte `json:"attributes"` - AttributesMask HexByte `json:"attributesMask"` - Mrsigner HexByte `json:"mrsigner"` + Miscselect ar.HexByte `json:"miscselect"` + MiscselectMask ar.HexByte `json:"miscselectMask"` + Attributes ar.HexByte `json:"attributes"` + AttributesMask ar.HexByte `json:"attributesMask"` + Mrsigner ar.HexByte `json:"mrsigner"` IsvProdId uint32 `json:"isvprodid"` TcbLevels []TcbLevelEnclaveId `json:"tcbLevels"` } @@ -478,8 +479,8 @@ func parseECDSASignature(buf *bytes.Buffer, sig *ECDSA256QuoteSignatureDataStruc // Params: QuoteType = 0x00 (SGX) or 0x81 (TDX) func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, quoteSignatureSize uint32, quoteSignatureType int, certs SgxCertificates, - fingerprint string, intelCache string, quoteType uint32) (SignatureResult, bool) { - result := SignatureResult{} + fingerprint string, intelCache string, quoteType uint32) (ar.SignatureResult, bool) { + result := ar.SignatureResult{} var digest [32]byte var ak_pub [64]byte // attestation public key (generated by the QE) var reportData [64]byte @@ -492,26 +493,26 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, if uint32(len(reportRaw)-SGX_QUOTE_SIGNATURE_OFFSET) != quoteSignatureSize { log.Tracef("parsed QuoteSignatureData size doesn't match QuoteSignatureDataLen. expected: %v, got: %v\n", quoteSignatureSize, uint32(len(reportRaw)-signature_offset)) - result.SignCheck.SetErr(SignatureLength) + result.SignCheck.SetErr(ar.SignatureLength) return result, false } case TDX_QUOTE_TYPE: if uint32(len(reportRaw)-TDX_QUOTE_SIGNATURE_OFFSET) != quoteSignatureSize { log.Tracef("parsed QuoteSignatureData size doesn't match QuoteSignatureDataLen. expected: %v, got: %v\n", quoteSignatureSize, uint32(len(reportRaw)-signature_offset)) - result.SignCheck.SetErr(SignatureLength) + result.SignCheck.SetErr(ar.SignatureLength) return result, false } default: log.Tracef("Quote Type not supported %v", quoteType) - result.SignCheck.SetErr(EvidenceType) + result.SignCheck.SetErr(ar.EvidenceType) return result, false } // for now only ECDSA_P_256 support if quoteSignatureType != ECDSA_P_256 { log.Tracef("Signature Algorithm %v not supported", quoteSignatureType) - result.SignCheck.SetErr(UnsupportedAlgorithm) + result.SignCheck.SetErr(ar.UnsupportedAlgorithm) return result, false } @@ -535,7 +536,7 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, if len(ak_pub) == 0 { log.Tracef("Failed to extract ECDSA public key from certificate") - result.SignCheck.SetErr(ExtractPubKey) + result.SignCheck.SetErr(ar.ExtractPubKey) return result, false } @@ -555,7 +556,7 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, ok := ecdsa.Verify(ecdsa_ak_pub, digest[:], r, s) if !ok { log.Tracef("Failed to verify ISV Enclave report signature") - result.SignCheck.SetErr(VerifySignature) + result.SignCheck.SetErr(ar.VerifySignature) return result, false } log.Trace("Successfully verified ISV Enclave report signature") @@ -581,7 +582,7 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, pck_pub, ok := certs.PCKCert.PublicKey.(*ecdsa.PublicKey) if pck_pub == nil || !ok { log.Tracef("Failed to extract PCK public key from certificate") - result.SignCheck.SetErr(ExtractPubKey) + result.SignCheck.SetErr(ar.ExtractPubKey) return result, false } @@ -589,7 +590,7 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, ok = ecdsa.Verify(pck_pub, digest[:], r, s) if !ok { log.Tracef("Failed to verify QE report signature") - result.SignCheck.SetErr(VerifySignature) + result.SignCheck.SetErr(ar.VerifySignature) return result, false } @@ -601,20 +602,20 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, if !bytes.Equal(reportData[:], reportDataRef[:]) { log.Tracef("invalid SHA256(ECDSA Attestation Key || QE Authentication Data) || 32*0x00) in QEReport.ReportData. expected: %v, got: %v\n", reportDataRef, reportData) - result.CertChainCheck.SetErr(VerifyCertChain) + result.CertChainCheck.SetErr(ar.VerifyCertChain) return result, false } // Step 4: Parse and verify the entire PCK certificate chain var x509Chains [][]*x509.Certificate - var code ErrorCode + var code ar.ErrorCode switch quoteType { case SGX_QUOTE_TYPE: x509Chains, code = VerifyIntelCertChainFull(certs, CA_PROCESSOR, intelCache) case TDX_QUOTE_TYPE: x509Chains, code = VerifyIntelCertChainFull(certs, CA_PLATFORM, intelCache) } - if code != NotSet { + if code != ar.NotSet { log.Tracef("Failed to verify certificate chain: %v", err) result.CertChainCheck.SetErr(code) return result, false @@ -624,23 +625,23 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, refFingerprint, err := hex.DecodeString(fingerprint) if err != nil { log.Tracef("Failed to decode CA fingerprint %v: %v", fingerprint, err) - result.CertChainCheck.SetErr(ParseCAFingerprint) + result.CertChainCheck.SetErr(ar.ParseCAFingerprint) return result, false } caFingerprint := sha256.Sum256(certs.RootCACert.Raw) if !bytes.Equal(refFingerprint, caFingerprint[:]) { log.Tracef("CA fingerprint %v does not match measurement CA fingerprint %v", fingerprint, hex.EncodeToString(caFingerprint[:])) - result.CertChainCheck.SetErr(CaFingerprint) + result.CertChainCheck.SetErr(ar.CaFingerprint) return result, false } result.CertChainCheck.Success = true // Step 6: Store details from (all) validated certificate chain(s) in the report for _, chain := range x509Chains { - chainExtracted := []X509CertExtracted{} + chainExtracted := []ar.X509CertExtracted{} for _, cert := range chain { - chainExtracted = append(chainExtracted, ExtractX509Infos(cert)) + chainExtracted = append(chainExtracted, ar.ExtractX509Infos(cert)) } result.ValidatedCerts = append(result.ValidatedCerts, chainExtracted) } @@ -650,12 +651,12 @@ func VerifyIntelQuoteSignature(reportRaw []byte, quoteSignature any, // teeTcbSvn is only required for TDX (from TdxReportBody) func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Certificate, - sgxExtensions SGXExtensionsValue, teeTcbSvn [16]byte, quoteType uint32) TcbLevelResult { - var result TcbLevelResult + sgxExtensions SGXExtensionsValue, teeTcbSvn [16]byte, quoteType uint32) ar.TcbLevelResult { + var result ar.TcbLevelResult if tcbInfo == nil || tcbKeyCert == nil { log.Tracef("invalid function parameter (null pointer exception)") - result.Summary.SetErr(Internal) + result.Summary.SetErr(ar.Internal) return result } @@ -679,7 +680,7 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer ok := ecdsa.Verify(pub_key, digest[:], r, s) if !ok { log.Tracef("failed to verify tcbInfo signature") - result.Summary.SetErr(VerifyTcbInfo) + result.Summary.SetErr(ar.VerifyTcbInfo) return result } @@ -687,14 +688,14 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer if now.After(tcbInfo.TcbInfo.NextUpdate) { log.Tracef("tcbInfo has expired since: %v", tcbInfo.TcbInfo.NextUpdate) - result.Summary.SetErr(TcbInfoExpired) + result.Summary.SetErr(ar.TcbInfoExpired) return result } if !bytes.Equal([]byte(tcbInfo.TcbInfo.Fmspc), sgxExtensions.Fmspc.Value) { log.Tracef("FMSPC value from TcbInfo (%v) and FMSPC value from SGX Extensions in PCK Cert (%v) do not match", tcbInfo.TcbInfo.Fmspc, sgxExtensions.Fmspc.Value) - result.Summary.SetErr(SgxFmpcMismatch) + result.Summary.SetErr(ar.SgxFmpcMismatch) return result } @@ -702,7 +703,7 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer if !bytes.Equal([]byte(tcbInfo.TcbInfo.PceId), sgxExtensions.PceId.Value) { log.Tracef("PCEID value from TcbInfo (%v) and PCEID value from SGX Extensions in PCK Cert (%v) do not match", tcbInfo.TcbInfo.PceId, sgxExtensions.PceId.Value) - result.Summary.SetErr(SgxPceidMismatch) + result.Summary.SetErr(ar.SgxPceidMismatch) return result } @@ -728,7 +729,7 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer // Only TDX: Compare TEE TCB SVNs from TDX Report with TCB Level if !compareTeeTcbSvns(teeTcbSvn, tcbLevel) || tcbLevel.Tcb.TdxTcbComponents[1].Svn != teeTcbSvn[1] { - result.Summary.SetErr(TcbLevelUnsupported) + result.Summary.SetErr(ar.TcbLevelUnsupported) return result } @@ -736,7 +737,7 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer if tcbLevel.TcbStatus == string(Revoked) { result.TcbLevelStatus = string(Revoked) result.TcbLevelDate = tcbLevel.TcbDate - result.Summary.SetErr(TcbLevelRevoked) + result.Summary.SetErr(ar.TcbLevelRevoked) return result } @@ -746,7 +747,7 @@ func verifyTcbInfo(tcbInfo *TcbInfo, tcbInfoBodyRaw string, tcbKeyCert *x509.Cer return result } - result.Summary.SetErr(TcbLevelUnsupported) + result.Summary.SetErr(ar.TcbLevelUnsupported) return result } @@ -776,8 +777,8 @@ func compareTeeTcbSvns(teeTcbSvn [16]byte, tcbLevel TcbLevel) bool { } // verify QE Identity and compare the values to the QE (SGX/TDX) -func VerifyQEIdentity(qeReportBody *EnclaveReportBody, qeIdentity *QEIdentity, qeIdentityBodyRaw string, tcbKeyCert *x509.Certificate, teeType uint32) (TcbLevelResult, error) { - result := TcbLevelResult{} +func VerifyQEIdentity(qeReportBody *EnclaveReportBody, qeIdentity *QEIdentity, qeIdentityBodyRaw string, tcbKeyCert *x509.Certificate, teeType uint32) (ar.TcbLevelResult, error) { + result := ar.TcbLevelResult{} if qeReportBody == nil || qeIdentity == nil || tcbKeyCert == nil { return result, fmt.Errorf("invalid function parameter (null pointer exception)") } @@ -832,27 +833,27 @@ func VerifyQEIdentity(qeReportBody *EnclaveReportBody, qeIdentity *QEIdentity, q log.Tracef("MRSIGNER mismatch. Expected: %v, Got: %v", qeIdentity.EnclaveIdentity.Mrsigner, qeReportBody.MRSIGNER) result.Summary.Success = false - result.MrSigner = Result{ + result.MrSigner = ar.Result{ Success: false, Expected: hex.EncodeToString(qeIdentity.EnclaveIdentity.Mrsigner), Got: hex.EncodeToString(qeReportBody.MRSIGNER[:]), } return result, nil } - result.MrSigner = Result{Success: true} + result.MrSigner = ar.Result{Success: true} // check isvProdId if qeReportBody.ISVProdID != uint16(qeIdentity.EnclaveIdentity.IsvProdId) { log.Tracef("IsvProdId mismatch. Expected: %v, Got: %v", qeIdentity.EnclaveIdentity.IsvProdId, qeReportBody.ISVProdID) result.Summary.Success = false - result.IsvProdId = Result{ + result.IsvProdId = ar.Result{ Success: false, Expected: strconv.FormatUint(uint64(qeIdentity.EnclaveIdentity.IsvProdId), 10), Got: strconv.FormatUint(uint64(qeReportBody.ISVProdID), 10), } return result, nil } - result.IsvProdId = Result{Success: true} + result.IsvProdId = ar.Result{Success: true} // check miscselect miscselectMask := binary.LittleEndian.Uint32(qeIdentity.EnclaveIdentity.MiscselectMask) @@ -862,14 +863,14 @@ func VerifyQEIdentity(qeReportBody *EnclaveReportBody, qeIdentity *QEIdentity, q log.Tracef("miscSelect value from QEIdentity: %v does not match miscSelect value from QE Report: %v", qeIdentity.EnclaveIdentity.Miscselect, (qeReportBody.MISCSELECT & miscselectMask)) result.Summary.Success = false - result.MrSigner = Result{ + result.MrSigner = ar.Result{ Success: false, Expected: strconv.FormatUint(uint64(refMiscSelect), 10), Got: strconv.FormatUint(uint64(reportMiscSelect), 10), } return result, nil } - result.MrSigner = Result{Success: true} + result.MrSigner = ar.Result{Success: true} // check attributes attributes_quote := qeReportBody.Attributes @@ -882,14 +883,14 @@ func VerifyQEIdentity(qeReportBody *EnclaveReportBody, qeIdentity *QEIdentity, q if !bytes.Equal([]byte(qeIdentity.EnclaveIdentity.Attributes), attributes_quote[:]) { log.Tracef("attributes mismatch. Expected: %v, Got: %v", qeIdentity.EnclaveIdentity.Attributes, attributes_quote) result.Summary.Success = false - result.Attributes = Result{ + result.Attributes = ar.Result{ Success: false, Expected: hex.EncodeToString(qeIdentity.EnclaveIdentity.Attributes), Got: hex.EncodeToString(attributes_quote[:]), } return result, nil } - result.Attributes = Result{Success: true} + result.Attributes = ar.Result{Success: true} tcbStatus, tcbDate := getTcbStatusAndDateQE(qeIdentity, qeReportBody) log.Tracef("TcbStatus for Enclave's Identity tcbLevel (isvSvn: %v): '%v'", qeReportBody.ISVSVN, tcbStatus) @@ -1052,74 +1053,74 @@ func downloadCRL(uri string) (*x509.RevocationList, error) { } // Verifies a given SGX certificate chain, fetches CRLs and checks if the certs are outdated -func VerifyIntelCertChainFull(quoteCerts SgxCertificates, ca string, intelCache string) ([][]*x509.Certificate, ErrorCode) { +func VerifyIntelCertChainFull(quoteCerts SgxCertificates, ca string, intelCache string) ([][]*x509.Certificate, ar.ErrorCode) { // verify PCK certificate chain x509CertChains, err := internal.VerifyCertChain( []*x509.Certificate{quoteCerts.PCKCert, quoteCerts.IntermediateCert}, []*x509.Certificate{quoteCerts.RootCACert}) if err != nil { log.Tracef("Failed to verify pck certificate chain: %v", err) - return nil, VerifyPCKChain + return nil, ar.VerifyPCKChain } // download CRLs from PCS root_ca_crl, err := fetchCRL(PCS_ROOT_CA_CRL_URI, ROOT_CA_CRL_NAME, "", intelCache) if err != nil { log.Tracef("downloading Root CA CRL from PCS failed: %v", err) - return nil, DownloadRootCRL + return nil, ar.DownloadRootCRL } pck_crl_uri := fmt.Sprintf(PCS_PCK_CERT_CRL_URI, ca) pck_crl, err := fetchCRL(pck_crl_uri, PCK_CERT_CRL_NAME, ca, intelCache) if err != nil { log.Tracef("downloading PCK Cert CRL from PCS failed: %v", err) - return nil, DownloadPCKCRL + return nil, ar.DownloadPCKCRL } // perform CRL checks (signature + values) res, err := CrlCheck(root_ca_crl, quoteCerts.RootCACert, quoteCerts.RootCACert) if !res || err != nil { log.Tracef("CRL check on rootCert failed: %v", err) - return nil, CRLCheckRoot + return nil, ar.CRLCheckRoot } res, err = CrlCheck(pck_crl, quoteCerts.PCKCert, quoteCerts.IntermediateCert) if !res || err != nil { log.Tracef("CRL check on pckCert failed: %v", err) - return nil, CRLCheckPCK + return nil, ar.CRLCheckPCK } - return x509CertChains, NotSet + return x509CertChains, ar.NotSet } // Verifies the TCB signing cert chain -func VerifyTCBSigningCertChain(quoteCerts SgxCertificates, intelCache string) ([][]*x509.Certificate, ErrorCode) { +func VerifyTCBSigningCertChain(quoteCerts SgxCertificates, intelCache string) ([][]*x509.Certificate, ar.ErrorCode) { tcbSigningCertChain, err := internal.VerifyCertChain( []*x509.Certificate{quoteCerts.TCBSigningCert}, []*x509.Certificate{quoteCerts.RootCACert}) if err != nil { log.Tracef("Failed to verify TCB Signing certificate chain: %v", err) - return nil, VerifyTCBChain + return nil, ar.VerifyTCBChain } root_ca_crl, err := fetchCRL(PCS_ROOT_CA_CRL_URI, ROOT_CA_CRL_NAME, "", intelCache) if err != nil { log.Tracef("downloading Root CA CRL from PCS failed: %v", err) - return nil, DownloadRootCRL + return nil, ar.DownloadRootCRL } // perform CRL checks (signature + values) res, err := CrlCheck(root_ca_crl, quoteCerts.TCBSigningCert, quoteCerts.RootCACert) if !res || err != nil { log.Tracef("CRL check on TcbSigningCert failed: %v", err) - return nil, CRLCheckSigningCert + return nil, ar.CRLCheckSigningCert } - return tcbSigningCertChain, NotSet + return tcbSigningCertChain, ar.NotSet } -func verifyQuoteVersion(quote QuoteHeader, version uint16) (Result, bool) { - r := Result{} +func verifyQuoteVersion(quote QuoteHeader, version uint16) (ar.Result, bool) { + r := ar.Result{} ok := quote.Version == version if !ok { log.Tracef("Quote version mismatch: Report = %v, supplied = %v", quote.Version, version) diff --git a/attestationreport/jspolicies.go b/verify/jspolicies.go similarity index 87% rename from attestationreport/jspolicies.go rename to verify/jspolicies.go index ce8c4e26..565e4fcc 100644 --- a/attestationreport/jspolicies.go +++ b/verify/jspolicies.go @@ -15,12 +15,13 @@ //go:build !nodefaults || jspolicies -package attestationreport +package verify import ( "encoding/json" "github.com/Fraunhofer-AISEC/cmc/attestationpolicies/jspolicies" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) type JsPolicyEngine struct{} @@ -29,7 +30,7 @@ func init() { policyEngines[PolicyEngineSelect_JS] = JsPolicyEngine{} } -func (p JsPolicyEngine) Validate(policies []byte, result VerificationResult) bool { +func (p JsPolicyEngine) Validate(policies []byte, result ar.VerificationResult) bool { vr, err := json.Marshal(result) if err != nil { log.Errorf("Failed to marshal verification result: %v", err) diff --git a/attestationreport/sgx.go b/verify/sgx.go similarity index 89% rename from attestationreport/sgx.go rename to verify/sgx.go index f208e431..89c06991 100644 --- a/attestationreport/sgx.go +++ b/verify/sgx.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" @@ -23,6 +23,8 @@ import ( "fmt" "strconv" "strings" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) // Overall structure: table 2 from https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf @@ -76,12 +78,12 @@ func DecodeSgxReport(report []byte) (SgxReport, error) { return reportStruct, nil } -func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, referenceValues []ReferenceValue) (*MeasurementResult, bool) { +func verifySgxMeasurements(sgxM ar.Measurement, nonce []byte, intelCache string, referenceValues []ar.ReferenceValue) (*ar.MeasurementResult, bool) { var err error - result := &MeasurementResult{ + result := &ar.MeasurementResult{ Type: "SGX Result", - SgxResult: &SgxResult{}, + SgxResult: &ar.SgxResult{}, } ok := true @@ -89,12 +91,12 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re if len(referenceValues) == 0 { log.Tracef("Could not find SGX Reference Value") - result.Summary.SetErr(RefValNotPresent) + result.Summary.SetErr(ar.RefValNotPresent) return result, false } else if len(referenceValues) > 1 { log.Tracef("Report contains %v reference values. Currently, only one SGX Reference Value is supported", len(referenceValues)) - result.Summary.SetErr(RefValMultiple) + result.Summary.SetErr(ar.RefValMultiple) return result, false } sgxReferenceValue := referenceValues[0] @@ -102,19 +104,19 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re // Validate Parameters: if sgxM.Evidence == nil || len(sgxM.Evidence) < SGX_QUOTE_MIN_SIZE { log.Tracef("Invalid SGX Report") - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } if sgxReferenceValue.Type != "SGX Reference Value" { log.Tracef("SGX Reference Value invalid type %v", sgxReferenceValue.Type) - result.Summary.SetErr(RefValType) + result.Summary.SetErr(ar.RefValType) return result, false } if sgxReferenceValue.Sgx == nil { log.Tracef("SGX Reference Value details not present") - result.Summary.SetErr(DetailsNotPresent) + result.Summary.SetErr(ar.DetailsNotPresent) return result, false } @@ -125,7 +127,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re sgxQuote, err = DecodeSgxReport(sgxM.Evidence) if err != nil { log.Tracef("Failed to decode SGX report: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } } else { @@ -155,7 +157,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re referenceCerts, err := parseCertificates(sgxM.Certs, true) if err != nil { log.Tracef("Failed to parse reference certificates: %v", err) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } @@ -165,19 +167,19 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re quoteCerts, err = parseCertificates(sgxQuote.QuoteSignatureData.QECertData, true) if err != nil { log.Tracef("Failed to parse certificate chain from QECertData: %v", err) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } } else { log.Tracef("QECertDataType not supported: %v", sgxQuote.QuoteSignatureData.QECertDataType) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } // Extract root CA from PCK cert chain in quote -> compare nullptr if quoteCerts.RootCACert == nil { log.Tracef("root cert is null") - result.Summary.SetErr(ParseCA) + result.Summary.SetErr(ar.ParseCA) return result, false } @@ -194,7 +196,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re if !bytes.Equal(quotePublicKeyBytes, referencePublicKeyBytes) { log.Tracef("root cert public key didn't match") - result.Summary.SetErr(CaFingerprint) + result.Summary.SetErr(ar.CaFingerprint) return result, false } @@ -202,7 +204,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re sgxExtensions, err := ParseSGXExtensions(quoteCerts.PCKCert.Extensions[SGX_EXTENSION_INDEX].Value[4:]) // skip the first value (not relevant) if err != nil { log.Tracef("failed to parse SGX Extensions from PCK Certificate: %v", err) - result.Summary.SetErr(ParseExtensions) + result.Summary.SetErr(ar.ParseExtensions) return result, false } @@ -210,7 +212,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re tcbInfo, err := parseTcbInfo(sgxReferenceValue.Sgx.Collateral.TcbInfo) if err != nil { log.Tracef("Failed to parse tcbInfo: %v", err) - result.Summary.SetErr(ParseTcbInfo) + result.Summary.SetErr(ar.ParseTcbInfo) return result, false } @@ -219,7 +221,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re referenceCerts.TCBSigningCert, sgxExtensions, [16]byte{}, SGX_QUOTE_TYPE) if !result.SgxResult.TcbInfoCheck.Summary.Success { log.Tracef("Failed to verify TCB info structure: %v", err) - result.Summary.SetErr(VerifyTcbInfo) + result.Summary.SetErr(ar.VerifyTcbInfo) return result, false } @@ -227,7 +229,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re qeIdentity, err := parseQEIdentity(sgxReferenceValue.Sgx.Collateral.QeIdentity) if err != nil { log.Tracef("Failed to parse QE identity: %v", err) - result.Summary.SetErr(ParseQEIdentity) + result.Summary.SetErr(ar.ParseQEIdentity) return result, false } @@ -235,7 +237,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re string(sgxReferenceValue.Sgx.Collateral.QeIdentity), referenceCerts.TCBSigningCert, SGX_QUOTE_TYPE) if err != nil { log.Tracef("Failed to verify QE Identity structure: %v", err) - result.Summary.SetErr(VerifyQEIdentityErr) + result.Summary.SetErr(ar.VerifyQEIdentityErr) return result, false } result.SgxResult.QeIdentityCheck = qeIdentityResult @@ -253,7 +255,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re err = VerifySgxQuoteBody(&sgxQuote.ISVEnclaveReport, &tcbInfo, &sgxExtensions, &sgxReferenceValue, result) if err != nil { log.Tracef("Failed to verify SGX Report Body: %v", err) - result.Summary.SetErr(VerifySignature) + result.Summary.SetErr(ar.VerifySignature) result.Summary.Success = false return result, false } @@ -270,7 +272,7 @@ func verifySgxMeasurements(sgxM Measurement, nonce []byte, intelCache string, re } func VerifySgxQuoteBody(body *EnclaveReportBody, tcbInfo *TcbInfo, - sgxExtensions *SGXExtensionsValue, sgxReferenceValue *ReferenceValue, result *MeasurementResult) error { + sgxExtensions *SGXExtensionsValue, sgxReferenceValue *ar.ReferenceValue, result *ar.MeasurementResult) error { if body == nil || tcbInfo == nil || sgxExtensions == nil || sgxReferenceValue == nil || result == nil { return fmt.Errorf("invalid function parameter (null pointer exception)") } @@ -278,14 +280,14 @@ func VerifySgxQuoteBody(body *EnclaveReportBody, tcbInfo *TcbInfo, // check MRENCLAVE reference value if !bytes.Equal(body.MRENCLAVE[:], []byte(sgxReferenceValue.Sha256)) { result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: sgxReferenceValue.Name, Digest: hex.EncodeToString(sgxReferenceValue.Sha256[:]), Success: false, Type: "Reference Value", }) result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: sgxReferenceValue.Name, Digest: hex.EncodeToString(body.MRENCLAVE[:]), Success: false, @@ -294,7 +296,7 @@ func VerifySgxQuoteBody(body *EnclaveReportBody, tcbInfo *TcbInfo, return fmt.Errorf("MRENCLAVE mismatch. Expected: %v, Got. %v", sgxReferenceValue.Sha256, body.MRENCLAVE) } else { result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: sgxReferenceValue.Name, Digest: hex.EncodeToString(body.MRENCLAVE[:]), Success: true, @@ -303,25 +305,25 @@ func VerifySgxQuoteBody(body *EnclaveReportBody, tcbInfo *TcbInfo, } result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: "MrSigner", Digest: hex.EncodeToString(body.MRSIGNER[:]), Success: strings.EqualFold(sgxReferenceValue.Sgx.MrSigner, hex.EncodeToString(body.MRSIGNER[:])), Type: "Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "CpuSvn", Digest: hex.EncodeToString(body.CPUSVN[:]), Success: bytes.Compare(sgxExtensions.Tcb.Value.CpuSvn.Value, body.CPUSVN[:]) <= 0, Type: "Security Version", }, - DigestResult{ + ar.DigestResult{ Name: "IsvProdId", Digest: strconv.Itoa(int(body.ISVProdID)), Success: sgxReferenceValue.Sgx.IsvProdId == body.ISVProdID, Type: "Product ID", }, - DigestResult{ + ar.DigestResult{ Name: "IsvSvn", Digest: strconv.Itoa(int(body.ISVSVN)), Success: sgxReferenceValue.Sgx.IsvSvn == body.ISVSVN, @@ -335,43 +337,43 @@ func VerifySgxQuoteBody(body *EnclaveReportBody, tcbInfo *TcbInfo, } } - result.SgxResult.SgxAttributesCheck = SgxAttributesCheck{ - Initted: BooleanMatch{ + result.SgxResult.SgxAttributesCheck = ar.SgxAttributesCheck{ + Initted: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 0) == sgxReferenceValue.Sgx.Attributes.Initted, Claimed: sgxReferenceValue.Sgx.Attributes.Initted, Measured: getBit(body.Attributes[:], 0), }, - Debug: BooleanMatch{ + Debug: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 1) == sgxReferenceValue.Sgx.Attributes.Debug, Claimed: sgxReferenceValue.Sgx.Attributes.Debug, Measured: getBit(body.Attributes[:], 1), }, - Mode64Bit: BooleanMatch{ + Mode64Bit: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 2) == sgxReferenceValue.Sgx.Attributes.Mode64Bit, Claimed: sgxReferenceValue.Sgx.Attributes.Mode64Bit, Measured: getBit(body.Attributes[:], 2), }, - ProvisionKey: BooleanMatch{ + ProvisionKey: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 5) == sgxReferenceValue.Sgx.Attributes.ProvisionKey, Claimed: sgxReferenceValue.Sgx.Attributes.ProvisionKey, Measured: getBit(body.Attributes[:], 5), }, - EInitToken: BooleanMatch{ + EInitToken: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 6) == sgxReferenceValue.Sgx.Attributes.EInitToken, Claimed: sgxReferenceValue.Sgx.Attributes.EInitToken, Measured: getBit(body.Attributes[:], 6), }, - Kss: BooleanMatch{ + Kss: ar.BooleanMatch{ Success: getBit(body.Attributes[:], 8) == sgxReferenceValue.Sgx.Attributes.Kss, Claimed: sgxReferenceValue.Sgx.Attributes.Kss, Measured: getBit(body.Attributes[:], 8), }, - Legacy: BooleanMatch{ + Legacy: ar.BooleanMatch{ Success: (body.Attributes[8] == 3) == sgxReferenceValue.Sgx.Attributes.Legacy, Claimed: sgxReferenceValue.Sgx.Attributes.Legacy, Measured: body.Attributes[8] == 3, }, - Avx: BooleanMatch{ + Avx: ar.BooleanMatch{ Success: (body.Attributes[8] == 6) == sgxReferenceValue.Sgx.Attributes.Avx, Claimed: sgxReferenceValue.Sgx.Attributes.Avx, Measured: body.Attributes[8] == 6, diff --git a/attestationreport/sgx_test.go b/verify/sgx_test.go similarity index 98% rename from attestationreport/sgx_test.go rename to verify/sgx_test.go index f6da6638..a47b3774 100644 --- a/attestationreport/sgx_test.go +++ b/verify/sgx_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "encoding/hex" @@ -22,12 +22,14 @@ import ( "testing" "github.com/sirupsen/logrus" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) func Test_verifySgxMeasurements(t *testing.T) { type args struct { - sgxM *Measurement - sgxV []ReferenceValue + sgxM *ar.Measurement + sgxV []ar.ReferenceValue nonce []byte } tests := []struct { @@ -38,18 +40,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Valid Attestation Report", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -57,7 +59,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -75,18 +77,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Certificate Chain", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: invalidSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -94,7 +96,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -112,18 +114,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Report Signature", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: invalidSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -131,7 +133,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -149,18 +151,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Tcb Info", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: tcb_info_old, TcbInfoSize: tcb_info_size_old, @@ -168,7 +170,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -186,18 +188,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid QE Identity", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -205,7 +207,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: qe_identity_size_old, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -223,18 +225,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Measurement", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: []byte{}, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -242,7 +244,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -260,18 +262,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Attributes", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -279,7 +281,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Debug: true, Mode64Bit: true, @@ -297,18 +299,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid Nonce", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -316,7 +318,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, @@ -334,18 +336,18 @@ func Test_verifySgxMeasurements(t *testing.T) { { name: "Invalid MRSIGNER", args: args{ - sgxM: &Measurement{ + sgxM: &ar.Measurement{ Type: "SGX Measurement", Evidence: validSGXQuote, Certs: validSGXCertChain, }, - sgxV: []ReferenceValue{ + sgxV: []ar.ReferenceValue{ { Type: "SGX Reference Value", Sha256: validSGXMeasurement, - Sgx: &SGXDetails{ + Sgx: &ar.SGXDetails{ Version: validSGXVersion, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_sgx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -353,7 +355,7 @@ func Test_verifySgxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: rootCACertFingerprint, - Attributes: SGXAttributes{ + Attributes: ar.SGXAttributes{ Initted: true, Mode64Bit: true, Legacy: true, diff --git a/attestationreport/snp.go b/verify/snp.go similarity index 88% rename from attestationreport/snp.go rename to verify/snp.go index 20602f99..85b81175 100644 --- a/attestationreport/snp.go +++ b/verify/snp.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" @@ -27,6 +27,7 @@ import ( "math/big" "strconv" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" ) @@ -84,37 +85,37 @@ const ( signature_offset = 0x2A0 ) -func verifySnpMeasurements(snpM Measurement, nonce []byte, referenceValues []ReferenceValue, -) (*MeasurementResult, bool) { +func verifySnpMeasurements(snpM ar.Measurement, nonce []byte, referenceValues []ar.ReferenceValue, +) (*ar.MeasurementResult, bool) { log.Trace("Verifying SNP measurements") - result := &MeasurementResult{ + result := &ar.MeasurementResult{ Type: "SNP Result", - SnpResult: &SnpResult{}, + SnpResult: &ar.SnpResult{}, } ok := true if len(referenceValues) == 0 { log.Trace("Could not find SNP Reference Value") - result.Summary.SetErr(RefValNotPresent) + result.Summary.SetErr(ar.RefValNotPresent) return result, false } else if len(referenceValues) > 1 { log.Tracef("Report contains %v reference values. Currently, only 1 SNP Reference Value is supported", len(referenceValues)) - result.Summary.SetErr(RefValMultiple) + result.Summary.SetErr(ar.RefValMultiple) return result, false } snpReferenceValue := referenceValues[0] if snpReferenceValue.Type != "SNP Reference Value" { log.Tracef("SNP Reference Value invalid type %v", snpReferenceValue.Type) - result.Summary.SetErr(RefValType) + result.Summary.SetErr(ar.RefValType) return result, false } if snpReferenceValue.Snp == nil { log.Trace("SNP Reference Value does not contain policy") - result.Summary.SetErr(DetailsNotPresent) + result.Summary.SetErr(ar.DetailsNotPresent) return result, false } @@ -122,7 +123,7 @@ func verifySnpMeasurements(snpM Measurement, nonce []byte, referenceValues []Ref s, err := DecodeSnpReport(snpM.Evidence) if err != nil { log.Tracef("Failed to decode SNP report: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } @@ -143,7 +144,7 @@ func verifySnpMeasurements(snpM Measurement, nonce []byte, referenceValues []Ref certs, err := internal.ParseCertsDer(snpM.Certs) if err != nil { log.Tracef("Failed to parse certificates: %v", err) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } @@ -158,14 +159,14 @@ func verifySnpMeasurements(snpM Measurement, nonce []byte, referenceValues []Ref if cmp := bytes.Compare(s.Measurement[:], snpReferenceValue.Sha384); cmp != 0 { log.Trace("Failed to verify SNP reference value") result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: snpReferenceValue.Name, Digest: hex.EncodeToString(snpReferenceValue.Sha384), Success: false, Type: "Reference Value", }) result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: snpReferenceValue.Name, Digest: hex.EncodeToString(s.Measurement[:]), Success: false, @@ -178,7 +179,7 @@ func verifySnpMeasurements(snpM Measurement, nonce []byte, referenceValues []Ref // As we previously checked, that the attestation report contains exactly one // SNP Reference Value, we can set this here: result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: snpReferenceValue.Name, Digest: hex.EncodeToString(s.Measurement[:]), Success: true, @@ -218,8 +219,8 @@ func DecodeSnpReport(report []byte) (snpreport, error) { return s, nil } -func verifySnpVersion(s snpreport, version uint32) (Result, bool) { - r := Result{} +func verifySnpVersion(s snpreport, version uint32) (ar.Result, bool) { + r := ar.Result{} ok := s.Version == version if !ok { log.Tracef("SNP report version mismatch: Report = %v, supplied = %v", s.Version, version) @@ -232,7 +233,7 @@ func verifySnpVersion(s snpreport, version uint32) (Result, bool) { return r, ok } -func verifySnpPolicy(s snpreport, v SnpPolicy) (PolicyCheck, bool) { +func verifySnpPolicy(s snpreport, v ar.SnpPolicy) (ar.PolicyCheck, bool) { abiMajor := uint8(s.Policy & 0xFF) abiMinor := uint8((s.Policy >> 8) & 0xFF) @@ -242,28 +243,28 @@ func verifySnpPolicy(s snpreport, v SnpPolicy) (PolicyCheck, bool) { singleSocket := (s.Policy & (1 << 20)) != 0 // Convert to int, as json.Marshal otherwise interprets the values as strings - r := PolicyCheck{ - Abi: VersionCheck{ + r := ar.PolicyCheck{ + Abi: ar.VersionCheck{ Success: checkMinVersion([]uint8{abiMajor, abiMinor}, []uint8{v.AbiMajor, v.AbiMinor}), Claimed: []int{int(v.AbiMajor), int(v.AbiMinor)}, Measured: []int{int(abiMajor), int(abiMinor)}, }, - Smt: BooleanMatch{ + Smt: ar.BooleanMatch{ Success: smt == v.Smt, Claimed: v.Smt, Measured: smt, }, - Migration: BooleanMatch{ + Migration: ar.BooleanMatch{ Success: migration == v.Migration, Claimed: v.Migration, Measured: migration, }, - Debug: BooleanMatch{ + Debug: ar.BooleanMatch{ Success: debug == v.Debug, Claimed: v.Debug, Measured: debug, }, - SingleSocket: BooleanMatch{ + SingleSocket: ar.BooleanMatch{ Success: singleSocket == v.SingleSocket, Claimed: v.SingleSocket, Measured: singleSocket, @@ -283,7 +284,7 @@ func verifySnpPolicy(s snpreport, v SnpPolicy) (PolicyCheck, bool) { return r, ok } -func verifySnpFw(s snpreport, v SnpFw) (VersionCheck, bool) { +func verifySnpFw(s snpreport, v ar.SnpFw) (ar.VersionCheck, bool) { build := min([]uint8{s.CurrentBuild, s.CommittedBuild}) major := min([]uint8{s.CurrentMajor, s.CommittedMajor}) @@ -296,7 +297,7 @@ func verifySnpFw(s snpreport, v SnpFw) (VersionCheck, bool) { } // Convert to int, as json.Marshal otherwise interprets the values as strings - r := VersionCheck{ + r := ar.VersionCheck{ Success: ok, Claimed: []int{int(v.Major), int(v.Minor), int(v.Build)}, Measured: []int{int(major), int(minor), int(build)}, @@ -304,7 +305,7 @@ func verifySnpFw(s snpreport, v SnpFw) (VersionCheck, bool) { return r, ok } -func verifySnpTcb(s snpreport, v SnpTcb) (TcbCheck, bool) { +func verifySnpTcb(s snpreport, v ar.SnpTcb) (ar.TcbCheck, bool) { currBl := uint8(s.CurrentTcb & 0xFF) commBl := uint8(s.CommittedTcb & 0xFF) @@ -329,23 +330,23 @@ func verifySnpTcb(s snpreport, v SnpTcb) (TcbCheck, bool) { ucode := min([]uint8{currUcode, commUcode, launUcode, repoUcode}) // Convert to int, as json.Marshal otherwise interprets the values as strings - r := TcbCheck{ - Bl: VersionCheck{ + r := ar.TcbCheck{ + Bl: ar.VersionCheck{ Success: bl >= v.Bl, Claimed: []int{int(v.Bl)}, Measured: []int{int(bl)}, }, - Tee: VersionCheck{ + Tee: ar.VersionCheck{ Success: tee >= v.Tee, Claimed: []int{int(v.Tee)}, Measured: []int{int(tee)}, }, - Snp: VersionCheck{ + Snp: ar.VersionCheck{ Success: snp >= v.Snp, Claimed: []int{int(v.Snp)}, Measured: []int{int(snp)}, }, - Ucode: VersionCheck{ + Ucode: ar.VersionCheck{ Success: ucode >= v.Ucode, Claimed: []int{int(v.Ucode)}, Measured: []int{int(ucode)}, @@ -364,13 +365,13 @@ func verifySnpTcb(s snpreport, v SnpTcb) (TcbCheck, bool) { func verifySnpSignature( reportRaw []byte, report snpreport, certs []*x509.Certificate, fingerprint string, -) (SignatureResult, bool) { +) (ar.SignatureResult, bool) { - result := SignatureResult{} + result := ar.SignatureResult{} if len(reportRaw) < (header_offset + signature_offset) { log.Warn("Internal Error: Report buffer too small") - result.SignCheck.SetErr(Internal) + result.SignCheck.SetErr(ar.Internal) return result, false } @@ -404,7 +405,7 @@ func verifySnpSignature( // Check that the algorithm is supported if report.SignatureAlgo != ecdsa384_with_sha384 { log.Tracef("Signature Algorithm %v not supported", report.SignatureAlgo) - result.SignCheck.SetErr(UnsupportedAlgorithm) + result.SignCheck.SetErr(ar.UnsupportedAlgorithm) return result, false } @@ -412,7 +413,7 @@ func verifySnpSignature( pub, ok := certs[0].PublicKey.(*ecdsa.PublicKey) if !ok { log.Trace("Failed to extract ECDSA public key from certificate") - result.SignCheck.SetErr(ExtractPubKey) + result.SignCheck.SetErr(ar.ExtractPubKey) return result, false } @@ -420,7 +421,7 @@ func verifySnpSignature( ok = ecdsa.Verify(pub, digest[:], r, s) if !ok { log.Trace("Failed to verify SNP report signature") - result.SignCheck.SetErr(VerifySignature) + result.SignCheck.SetErr(ar.VerifySignature) return result, false } log.Trace("Successfully verified SNP report signature") @@ -431,19 +432,19 @@ func verifySnpSignature( x509Chains, err := internal.VerifyCertChain(certs[:len(certs)-1], []*x509.Certificate{ca}) if err != nil { log.Tracef("Failed to verify certificate chain: %v", err) - result.CertChainCheck.SetErr(VerifyCertChain) + result.CertChainCheck.SetErr(ar.VerifyCertChain) return result, false } // Verify that the reference value fingerprint matches the certificate fingerprint if fingerprint == "" { log.Trace("Reference value SNP CA fingerprint not present") - result.CertChainCheck.SetErr(NotPresent) + result.CertChainCheck.SetErr(ar.NotPresent) return result, false } refFingerprint, err := hex.DecodeString(fingerprint) if err != nil { log.Tracef("Failed to decode CA fingerprint %v: %v", fingerprint, err) - result.CertChainCheck.SetErr(ParseCAFingerprint) + result.CertChainCheck.SetErr(ar.ParseCAFingerprint) return result, false } caFingerprint := sha256.Sum256(ca.Raw) @@ -459,9 +460,9 @@ func verifySnpSignature( //Store details from (all) validated certificate chain(s) in the report for _, chain := range x509Chains { - chainExtracted := []X509CertExtracted{} + chainExtracted := []ar.X509CertExtracted{} for _, cert := range chain { - chainExtracted = append(chainExtracted, ExtractX509Infos(cert)) + chainExtracted = append(chainExtracted, ar.ExtractX509Infos(cert)) } result.ValidatedCerts = append(result.ValidatedCerts, chainExtracted) } @@ -469,13 +470,13 @@ func verifySnpSignature( return result, true } -func verifySnpExtensions(cert *x509.Certificate, report *snpreport) ([]Result, bool) { +func verifySnpExtensions(cert *x509.Certificate, report *snpreport) ([]ar.Result, bool) { success := true var ok bool - var r Result + var r ar.Result tcb := report.CurrentTcb - results := make([]Result, 0) + results := make([]ar.Result, 0) if r, ok = checkExtensionUint8(cert, "1.3.6.1.4.1.3704.1.3.1", uint8(tcb)); !ok { log.Tracef("SEV BL Extension Check failed:") diff --git a/attestationreport/snp_test.go b/verify/snp_test.go similarity index 96% rename from attestationreport/snp_test.go rename to verify/snp_test.go index 18cd3f28..e20e9851 100644 --- a/attestationreport/snp_test.go +++ b/verify/snp_test.go @@ -13,13 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "encoding/hex" "testing" "github.com/sirupsen/logrus" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) func Test_verifySnpMeasurements(t *testing.T) { @@ -27,8 +29,8 @@ func Test_verifySnpMeasurements(t *testing.T) { logrus.SetLevel(logrus.TraceLevel) type args struct { - snpM *Measurement - snpV []ReferenceValue + snpM *ar.Measurement + snpV []ar.ReferenceValue nonce []byte } tests := []struct { @@ -39,16 +41,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "ValidAttestationReport", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -64,16 +66,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Signature", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: invalidReportSignature, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -89,16 +91,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Certificate Chain", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: invalidCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -114,16 +116,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid VCEK Certificate", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: invalidLeafCert, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -139,16 +141,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Report", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: invalidReportData, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -164,16 +166,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Measurement", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: invalidMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -189,16 +191,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Policy Parameter Debug", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: invalidSnpPolicy, @@ -214,16 +216,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Nonce", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -239,12 +241,12 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Reference Value Type", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "TPM Reference Value", Sha256: validMeasurement, @@ -257,7 +259,7 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "No Reference Values Present", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, @@ -269,16 +271,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid Firmware", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -294,16 +296,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid TCB", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: validFingerprint, Policy: validSnpPolicy, @@ -319,16 +321,16 @@ func Test_verifySnpMeasurements(t *testing.T) { { name: "Invalid CA KeyID", args: args{ - snpM: &Measurement{ + snpM: &ar.Measurement{ Type: "SNP Measurement", Evidence: validReport, Certs: validCertChain, }, - snpV: []ReferenceValue{ + snpV: []ar.ReferenceValue{ { Type: "SNP Reference Value", Sha384: validMeasurement, - Snp: &SnpDetails{ + Snp: &ar.SnpDetails{ Version: validVersion, CaFingerprint: invalidFingerprint, Policy: validSnpPolicy, @@ -840,7 +842,7 @@ AFZEAwoKCQ== invalidLeafCert = [][]byte{invalidVcek.Raw, askMilan.Raw, arkMilan.Raw} - validSnpPolicy = SnpPolicy{ + validSnpPolicy = ar.SnpPolicy{ Type: "SNP Policy", SingleSocket: false, Debug: false, @@ -850,7 +852,7 @@ AFZEAwoKCQ== AbiMinor: 0, } - invalidSnpPolicy = SnpPolicy{ + invalidSnpPolicy = ar.SnpPolicy{ Type: "SNP Policy", SingleSocket: false, Debug: true, @@ -862,26 +864,26 @@ AFZEAwoKCQ== validVersion = uint32(2) - validFw = SnpFw{ + validFw = ar.SnpFw{ Build: 3, Major: 1, Minor: 51, } - invalidFw = SnpFw{ + invalidFw = ar.SnpFw{ Build: 4, Major: 1, Minor: 51, } - validTcb = SnpTcb{ + validTcb = ar.SnpTcb{ Bl: 2, Tee: 0, Snp: 6, Ucode: 67, } - invalidTcb = SnpTcb{ + invalidTcb = ar.SnpTcb{ Bl: 2, Tee: 0, Snp: 6, diff --git a/attestationreport/tdx.go b/verify/tdx.go similarity index 90% rename from attestationreport/tdx.go rename to verify/tdx.go index f240338e..c455d115 100644 --- a/attestationreport/tdx.go +++ b/verify/tdx.go @@ -13,13 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" "encoding/binary" "encoding/hex" "fmt" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) // TDX Report V4 @@ -192,14 +194,14 @@ func parseECDSASignatureV4(buf *bytes.Buffer, sig *ECDSA256QuoteSignatureDataStr return nil } -func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, referenceValues []ReferenceValue) (*MeasurementResult, bool) { +func verifyTdxMeasurements(tdxM ar.Measurement, nonce []byte, intelCache string, referenceValues []ar.ReferenceValue) (*ar.MeasurementResult, bool) { log.Trace("Verifying TDX measurements") var err error - result := &MeasurementResult{ + result := &ar.MeasurementResult{ Type: "TDX Result", - TdxResult: &TdxResult{}, + TdxResult: &ar.TdxResult{}, } ok := true @@ -207,24 +209,24 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re if len(referenceValues) == 0 { log.Tracef("Could not find TDX Reference Value") - result.Summary.SetErr(RefValNotPresent) + result.Summary.SetErr(ar.RefValNotPresent) return result, false } else if len(referenceValues) > 1 { log.Tracef("Report contains %v reference values. Currently, only one TDX Reference Value is supported", len(referenceValues)) - result.Summary.SetErr(RefValMultiple) + result.Summary.SetErr(ar.RefValMultiple) return result, false } tdxReferenceValue := referenceValues[0] if tdxReferenceValue.Type != "TDX Reference Value" { log.Tracef("TDX Reference Value invalid type %v", tdxReferenceValue.Type) - result.Summary.SetErr(RefValType) + result.Summary.SetErr(ar.RefValType) return result, false } if tdxReferenceValue.Tdx == nil { log.Tracef("TDX Reference Value does not contain details") - result.Summary.SetErr(DetailsNotPresent) + result.Summary.SetErr(ar.DetailsNotPresent) return result, false } @@ -232,7 +234,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re tdxQuote, err := decodeTdxReportV4(tdxM.Evidence) if err != nil { log.Tracef("Failed to decode TDX report: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } @@ -255,7 +257,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re if quoteCerts.RootCACert == nil || quoteCerts.IntermediateCert == nil || quoteCerts.PCKCert == nil { log.Tracef("incomplete certificate chain") - result.Summary.SetErr(VerifyCertChain) + result.Summary.SetErr(ar.VerifyCertChain) return result, false } @@ -263,12 +265,12 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re referenceCerts, err := parseCertificates(tdxM.Certs, true) if err != nil || referenceCerts.TCBSigningCert == nil || referenceCerts.RootCACert == nil { log.Tracef("Failed to parse reference certificates (TCBSigningCert + IntelRootCACert): %v", err) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } _, code := VerifyTCBSigningCertChain(referenceCerts, intelCache) - if code != NotSet { + if code != ar.NotSet { log.Tracef("%v", err.Error()) result.Summary.SetErr(code) return result, false @@ -278,7 +280,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re sgxExtensions, err := ParseSGXExtensions(quoteCerts.PCKCert.Extensions[SGX_EXTENSION_INDEX].Value[4:]) // skip the first value (not relevant) if err != nil { log.Tracef("failed to parse SGX Extensions from PCK Certificate: %v", err) - result.Summary.SetErr(ParseCert) + result.Summary.SetErr(ar.ParseCert) return result, false } @@ -286,7 +288,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re tcbInfo, err := parseTcbInfo(tdxReferenceValue.Tdx.Collateral.TcbInfo) if err != nil { log.Tracef("Failed to parse tcbInfo: %v", err) - result.Summary.SetErr(ParseTcbInfo) + result.Summary.SetErr(ar.ParseTcbInfo) return result, false } @@ -295,7 +297,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re sgxExtensions, tdxQuote.QuoteBody.TeeTcbSvn, TDX_QUOTE_TYPE) if !result.TdxResult.TcbInfoCheck.Summary.Success { log.Tracef("Failed to verify TCB info structure: %v", err) - result.Summary.SetErr(VerifyTcbInfo) + result.Summary.SetErr(ar.VerifyTcbInfo) return result, false } @@ -303,7 +305,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re qeIdentity, err := parseQEIdentity(tdxReferenceValue.Tdx.Collateral.QeIdentity) if err != nil { log.Tracef("Failed to parse QE Identity: %v", err) - result.Summary.SetErr(ParseQEIdentity) + result.Summary.SetErr(ar.ParseQEIdentity) return result, false } @@ -312,7 +314,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re result.TdxResult.QeIdentityCheck = qeIdentityResult if err != nil { log.Tracef("Failed to verify QE Identity structure: %v", err) - result.Summary.SetErr(VerifyQEIdentityErr) + result.Summary.SetErr(ar.VerifyQEIdentityErr) return result, false } @@ -329,7 +331,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re err = verifyTdxQuoteBody(&tdxQuote.QuoteBody, &tcbInfo, "eCerts, &tdxReferenceValue, result) if err != nil { log.Tracef("Failed to verify TDX Report Body: %v", err) - result.Summary.SetErr(VerifySignature) + result.Summary.SetErr(ar.VerifySignature) result.Summary.Success = false return result, false } @@ -344,7 +346,7 @@ func verifyTdxMeasurements(tdxM Measurement, nonce []byte, intelCache string, re } func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, - certs *SgxCertificates, tdxReferenceValue *ReferenceValue, result *MeasurementResult) error { + certs *SgxCertificates, tdxReferenceValue *ar.ReferenceValue, result *ar.MeasurementResult) error { if body == nil || tcbInfo == nil || certs == nil || tdxReferenceValue == nil || result == nil { return fmt.Errorf("invalid function parameter (null pointer exception)") } @@ -352,14 +354,14 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, // check MrTd reference value (measurement of the initial contents of the TD) if !bytes.Equal(body.MrTd[:], []byte(tdxReferenceValue.Sha256)) { result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: tdxReferenceValue.Name, Digest: hex.EncodeToString(tdxReferenceValue.Sha256), Success: false, Type: "Reference Value", }) result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: tdxReferenceValue.Name, Digest: hex.EncodeToString(body.MrTd[:]), Success: false, @@ -368,7 +370,7 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, return fmt.Errorf("MrTd mismatch. Expected: %v, Got. %v", tdxReferenceValue.Sha256, body.MrTd) } else { result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: tdxReferenceValue.Name, Digest: hex.EncodeToString(body.MrTd[:]), Success: true, @@ -376,55 +378,55 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, } result.Artifacts = append(result.Artifacts, - DigestResult{ + ar.DigestResult{ Name: "MrSignerSeam", Digest: hex.EncodeToString(body.MrSignerSeam[:]), Success: bytes.Equal(tcbInfo.TcbInfo.TdxModule.Mrsigner[:], body.MrSignerSeam[:]), Type: "Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "MrSeam", Digest: hex.EncodeToString(body.MrSeam[:]), Success: bytes.Equal(body.MrSeam[:], tdxReferenceValue.Tdx.TdMeas.MrSeam), Type: "Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "RtMr0", Digest: hex.EncodeToString(body.RtMr0[:]), Success: recalculateRtMr(body.RtMr0[:], tdxReferenceValue.Tdx.TdMeas.RtMr0), Type: "Firmware Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "RtMr1", Digest: hex.EncodeToString(body.RtMr1[:]), Success: recalculateRtMr(body.RtMr1[:], tdxReferenceValue.Tdx.TdMeas.RtMr1), Type: "BIOS Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "RtMr2", Digest: hex.EncodeToString(body.RtMr2[:]), Success: recalculateRtMr(body.RtMr2[:], tdxReferenceValue.Tdx.TdMeas.RtMr2), Type: "OS Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "RtMr3", Digest: hex.EncodeToString(body.RtMr3[:]), Success: recalculateRtMr(body.RtMr3[:], tdxReferenceValue.Tdx.TdMeas.RtMr3), Type: "Runtime Measurement", }, - DigestResult{ + ar.DigestResult{ Name: "MrOwner", Digest: hex.EncodeToString(body.MrOwner[:]), Success: bytes.Equal(tdxReferenceValue.Tdx.TdId.MrOwner[:], body.MrOwner[:]), Type: "Software-defined ID", }, - DigestResult{ + ar.DigestResult{ Name: "MrOwnerConfig", Digest: hex.EncodeToString(body.MrOwnerConfig[:]), Success: bytes.Equal(tdxReferenceValue.Tdx.TdId.MrOwnerConfig[:], body.MrOwnerConfig[:]), Type: "Software-defined ID", }, - DigestResult{ + ar.DigestResult{ Name: "MrConfigId", Digest: hex.EncodeToString(body.MrConfigId[:]), Success: bytes.Equal(tdxReferenceValue.Tdx.TdId.MrConfigId[:], body.MrConfigId[:]), @@ -440,23 +442,23 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, } seamAttributesResult := bytes.Equal(tcbInfo.TcbInfo.TdxModule.Attributes, seamAttributesQuote[:]) - result.TdxResult.TdAttributesCheck = TdAttributesCheck{ - Debug: BooleanMatch{ + result.TdxResult.TdAttributesCheck = ar.TdAttributesCheck{ + Debug: ar.BooleanMatch{ Success: tdxReferenceValue.Tdx.TdAttributes.Debug == (body.TdAttributes[0] > 0), Claimed: tdxReferenceValue.Tdx.TdAttributes.Debug, Measured: body.TdAttributes[0] > 0, }, - SeptVEDisable: BooleanMatch{ + SeptVEDisable: ar.BooleanMatch{ Success: tdxReferenceValue.Tdx.TdAttributes.SeptVEDisable == getBit(body.TdAttributes[:], 28), Claimed: tdxReferenceValue.Tdx.TdAttributes.SeptVEDisable, Measured: getBit(body.TdAttributes[:], 28), }, - Pks: BooleanMatch{ + Pks: ar.BooleanMatch{ Success: tdxReferenceValue.Tdx.TdAttributes.Pks == getBit(body.TdAttributes[:], 30), Claimed: tdxReferenceValue.Tdx.TdAttributes.Pks, Measured: getBit(body.TdAttributes[:], 30), }, - Kl: BooleanMatch{ + Kl: ar.BooleanMatch{ Success: tdxReferenceValue.Tdx.TdAttributes.Kl == getBit(body.TdAttributes[:], 31), Claimed: tdxReferenceValue.Tdx.TdAttributes.Kl, Measured: getBit(body.TdAttributes[:], 31), @@ -473,20 +475,20 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, result.TdxResult.TdAttributesCheck.Pks, result.TdxResult.TdAttributesCheck.Kl) } - result.TdxResult.SeamAttributesCheck = AttributesCheck{ + result.TdxResult.SeamAttributesCheck = ar.AttributesCheck{ Success: seamAttributesResult, - Claimed: HexByte(tcbInfo.TcbInfo.TdxModule.Attributes), - Measured: HexByte(seamAttributesQuote[:]), + Claimed: ar.HexByte(tcbInfo.TcbInfo.TdxModule.Attributes), + Measured: ar.HexByte(seamAttributesQuote[:]), } if !result.TdxResult.SeamAttributesCheck.Success { return fmt.Errorf("SeamAttributesCheck failed: Expected: %v, Measured: %v", result.TdxResult.SeamAttributesCheck.Claimed, result.TdxResult.SeamAttributesCheck.Measured) } - result.TdxResult.XfamCheck = AttributesCheck{ + result.TdxResult.XfamCheck = ar.AttributesCheck{ Success: seamAttributesResult, - Claimed: HexByte(tcbInfo.TcbInfo.TdxModule.Attributes), - Measured: HexByte(seamAttributesQuote[:]), + Claimed: ar.HexByte(tcbInfo.TcbInfo.TdxModule.Attributes), + Measured: ar.HexByte(seamAttributesQuote[:]), } if !result.TdxResult.SeamAttributesCheck.Success { return fmt.Errorf("XfamCheck failed: Expected: %v, Measured: %v", @@ -504,7 +506,7 @@ func verifyTdxQuoteBody(body *TdxReportBody, tcbInfo *TcbInfo, // calculation of rtmrs according to Intel TDX Module 1.5 base architecture specification: // chapter 12.1.2. RTMR: Run-Time Measurement Registers -func recalculateRtMr(rmtr HexByte, refHashes RtMrHashChainElem) bool { +func recalculateRtMr(rmtr ar.HexByte, refHashes ar.RtMrHashChainElem) bool { if len(refHashes.Hashes) == 0 { log.Tracef("no reference hash values found") return false diff --git a/attestationreport/tdx_test.go b/verify/tdx_test.go similarity index 96% rename from attestationreport/tdx_test.go rename to verify/tdx_test.go index c976b294..abeb5f05 100644 --- a/attestationreport/tdx_test.go +++ b/verify/tdx_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "encoding/hex" @@ -23,6 +23,8 @@ import ( "testing" "github.com/sirupsen/logrus" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) func fetchLatestTcbInfo(tdx bool, fmspc string) ([]byte, error) { @@ -77,31 +79,31 @@ func fetchLatestQeIdentity(tdx bool) ([]byte, error) { func Test_verifyTdxMeasurements(t *testing.T) { type args struct { - tdxM *Measurement - tdxV []ReferenceValue + tdxM *ar.Measurement + tdxV []ar.ReferenceValue nonce []byte } tests := []struct { name string args args - want *MeasurementResult + want *ar.MeasurementResult want1 bool }{ { name: "Valid Attestation Report", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -109,19 +111,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -135,18 +137,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid Report Signature", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: invalidTdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -154,19 +156,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -180,18 +182,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid Nonce", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -199,19 +201,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -225,18 +227,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid Certificate Chain", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: [][]byte{}, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -244,19 +246,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -270,18 +272,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid Measurement", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: []byte("12345"), - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -289,19 +291,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -315,18 +317,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid Attributes", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -334,19 +336,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ Debug: true, }, Xfam: validXFAM, @@ -360,18 +362,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid TcbInfo", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte("0"), TcbInfoSize: 1, @@ -379,19 +381,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -405,18 +407,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid QEIdentity", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -424,19 +426,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 1, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, MrSeam: dec(validMrSeam), }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -450,18 +452,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Invalid MrSeam", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -469,19 +471,19 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{ + TdMeas: ar.TDMeasurements{ RtMr0: rtMr0HashChain, RtMr1: rtMr1HashChain, RtMr2: rtMr2HashChain, RtMr3: rtMr3HashChain, - MrSeam: HexByte{0x01}, + MrSeam: ar.HexByte{0x01}, }, - TdAttributes: TDAttributes{ + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -495,18 +497,18 @@ func Test_verifyTdxMeasurements(t *testing.T) { { name: "Missing HashChain Values", args: args{ - tdxM: &Measurement{ + tdxM: &ar.Measurement{ Type: "TDX Measurement", Evidence: tdxQuote, Certs: validTDXCertChain, }, - tdxV: []ReferenceValue{ + tdxV: []ar.ReferenceValue{ { Type: "TDX Reference Value", Sha256: validTDXMeasurement, - Tdx: &TDXDetails{ + Tdx: &ar.TDXDetails{ Version: 0x04, - Collateral: IntelCollateral{ + Collateral: ar.IntelCollateral{ TeeType: tee_type_tdx, TcbInfo: []byte{}, TcbInfoSize: 0, @@ -514,13 +516,13 @@ func Test_verifyTdxMeasurements(t *testing.T) { QeIdentitySize: 0, }, CaFingerprint: tdxRootCAFingerprint, - TdId: TDId{ + TdId: ar.TDId{ MrOwner: mrOwner, MrOwnerConfig: mrOwnerConfig, MrConfigId: mrConfigId, }, - TdMeas: TDMeasurements{}, - TdAttributes: TDAttributes{ + TdMeas: ar.TDMeasurements{}, + TdAttributes: ar.TDAttributes{ SeptVEDisable: true, }, Xfam: validXFAM, @@ -1412,28 +1414,28 @@ var ( mrOwner = [48]byte{} // 0x00s mrOwnerConfig = [48]byte{} // 0x00s mrConfigId = [48]byte{} // 0x00s - rtMr0HashChain = RtMrHashChainElem{ + rtMr0HashChain = ar.RtMrHashChainElem{ Type: "Runtime Measurement", Name: "RtMr0", - Hashes: []HexByte{dec(rtMr0)}, + Hashes: []ar.HexByte{dec(rtMr0)}, Summary: true, } - rtMr1HashChain = RtMrHashChainElem{ + rtMr1HashChain = ar.RtMrHashChainElem{ Type: "Runtime Measurement", Name: "RtMr1", - Hashes: []HexByte{dec(rtMr1)}, + Hashes: []ar.HexByte{dec(rtMr1)}, Summary: true, } - rtMr2HashChain = RtMrHashChainElem{ + rtMr2HashChain = ar.RtMrHashChainElem{ Type: "Runtime Measurement", Name: "RtMr2", - Hashes: []HexByte{dec(rtMr2)}, + Hashes: []ar.HexByte{dec(rtMr2)}, Summary: true, } - rtMr3HashChain = RtMrHashChainElem{ + rtMr3HashChain = ar.RtMrHashChainElem{ Type: "Runtime Measurement", Name: "RtMr3", - Hashes: []HexByte{dec(rtMr3)}, + Hashes: []ar.HexByte{dec(rtMr3)}, Summary: true, } rtMr0 = "e1af75e61927410e42b54b39f6681cf9b0bfbae512b15e870e4c8d9d5a5cb385571b0e1dc2f70bf9ccef08560f0a2b58" diff --git a/attestationreport/tpm.go b/verify/tpm.go similarity index 88% rename from attestationreport/tpm.go rename to verify/tpm.go index 85504e6b..fbc8dcd6 100644 --- a/attestationreport/tpm.go +++ b/verify/tpm.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "bytes" @@ -24,15 +24,16 @@ import ( "sort" "strings" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" "github.com/Fraunhofer-AISEC/cmc/internal" "github.com/google/go-tpm/legacy/tpm2" ) -func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []ReferenceValue, cas []*x509.Certificate) (*MeasurementResult, bool) { +func verifyTpmMeasurements(tpmM ar.Measurement, nonce []byte, referenceValues []ar.ReferenceValue, cas []*x509.Certificate) (*ar.MeasurementResult, bool) { - result := &MeasurementResult{ + result := &ar.MeasurementResult{ Type: "TPM Result", - TpmResult: &TpmResult{}, + TpmResult: &ar.TpmResult{}, } log.Trace("Verifying TPM measurements") @@ -51,7 +52,7 @@ func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []Ref tpmsAttest, err := tpm2.DecodeAttestationData(tpmM.Evidence) if err != nil { log.Tracef("Failed to decode TPM attestation data: %v", err) - result.Summary.SetErr(ParseEvidence) + result.Summary.SetErr(ar.ParseEvidence) return result, false } @@ -95,7 +96,7 @@ func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []Ref mCerts, err := internal.ParseCertsDer(tpmM.Certs) if err != nil { log.Tracef("Failed to parse measurement certs: %v", err) - result.Signature.CertChainCheck.SetErr(ParseCert) + result.Signature.CertChainCheck.SetErr(ar.ParseCert) result.Summary.Success = false return result, false } @@ -109,7 +110,7 @@ func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []Ref x509Chains, err := internal.VerifyCertChain(mCerts, cas) if err != nil { log.Tracef("Failed to verify certificate chain: %v", err) - result.Signature.CertChainCheck.SetErr(VerifyCertChain) + result.Signature.CertChainCheck.SetErr(ar.VerifyCertChain) ok = false } else { result.Signature.CertChainCheck.Success = true @@ -118,9 +119,9 @@ func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []Ref //Store details from (all) validated certificate chain(s) in the report for _, chain := range x509Chains { - chainExtracted := []X509CertExtracted{} + chainExtracted := []ar.X509CertExtracted{} for _, cert := range chain { - chainExtracted = append(chainExtracted, ExtractX509Infos(cert)) + chainExtracted = append(chainExtracted, ar.ExtractX509Infos(cert)) } result.Signature.ValidatedCerts = append(result.Signature.ValidatedCerts, chainExtracted) } @@ -130,16 +131,16 @@ func verifyTpmMeasurements(tpmM Measurement, nonce []byte, referenceValues []Ref return result, ok } -func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) (map[int][]byte, []PcrResult, []DigestResult, bool) { +func recalculatePcrs(measurement ar.Measurement, referenceValues []ar.ReferenceValue) (map[int][]byte, []ar.PcrResult, []ar.DigestResult, bool) { ok := true - pcrResults := make([]PcrResult, 0) - detailedResults := make([]DigestResult, 0) + pcrResults := make([]ar.PcrResult, 0) + detailedResults := make([]ar.DigestResult, 0) calculatedPcrs := make(map[int][]byte) // Iterate over the provided measurement for _, measuredPcr := range measurement.Pcrs { - pcrResult := PcrResult{ + pcrResult := ar.PcrResult{ Pcr: measuredPcr.Pcr, Success: true, } @@ -169,7 +170,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) // Check for every event that a corresponding reference value exists ref := getReferenceValue(event.Sha256, measuredPcr.Pcr, referenceValues) if ref == nil { - measResult := DigestResult{ + measResult := ar.DigestResult{ Type: "Measurement", Pcr: &pcrNum, Digest: hex.EncodeToString(event.Sha256), @@ -194,7 +195,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) nameInfo += ": " + event.EventName } - measResult := DigestResult{ + measResult := ar.DigestResult{ Pcr: &pcrNum, Digest: hex.EncodeToString(event.Sha256), Success: true, @@ -226,7 +227,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) // As we only have the PCR summary, we will later set all reference values // to true/false depending on whether the calculation matches the PCR summary - measResult := DigestResult{ + measResult := ar.DigestResult{ Pcr: &pcrNum, Digest: hex.EncodeToString(ref.Sha256), Name: ref.Name, @@ -280,7 +281,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) for _, ref := range referenceValues { if ref.Pcr == nil { - result := DigestResult{ + result := ar.DigestResult{ Type: "Reference Value", Success: false, Name: ref.Name, @@ -314,7 +315,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) } } if !foundEvent { - result := DigestResult{ + result := ar.DigestResult{ Type: "Reference Value", Pcr: ref.Pcr, Success: false, @@ -331,7 +332,7 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) } } if !foundPcr { - result := DigestResult{ + result := ar.DigestResult{ Type: "Reference Value", Pcr: ref.Pcr, Success: false, @@ -348,31 +349,31 @@ func recalculatePcrs(measurement Measurement, referenceValues []ReferenceValue) return calculatedPcrs, pcrResults, detailedResults, ok } -func verifyTpmQuoteSignature(quote, sig []byte, cert *x509.Certificate) Result { +func verifyTpmQuoteSignature(quote, sig []byte, cert *x509.Certificate) ar.Result { buf := new(bytes.Buffer) buf.Write((sig)) tpmtSig, err := tpm2.DecodeSignature(buf) if err != nil { log.Tracef("Failed to decode TPM signature: %v", err) - return Result{Success: false, ErrorCode: Parse} + return ar.Result{Success: false, ErrorCode: ar.Parse} } if tpmtSig.Alg != tpm2.AlgRSASSA { log.Tracef("Hash algorithm %v not supported", tpmtSig.Alg) - return Result{Success: false, ErrorCode: UnsupportedAlgorithm} + return ar.Result{Success: false, ErrorCode: ar.UnsupportedAlgorithm} } // Extract public key from x509 certificate pubKey, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { log.Tracef("Failed to extract public key from certificate") - return Result{Success: false, ErrorCode: ExtractPubKey} + return ar.Result{Success: false, ErrorCode: ar.ExtractPubKey} } hashAlg, err := tpmtSig.RSA.HashAlg.Hash() if err != nil { log.Tracef("Hash algorithm not supported") - return Result{Success: false, ErrorCode: UnsupportedAlgorithm} + return ar.Result{Success: false, ErrorCode: ar.UnsupportedAlgorithm} } // Hash the quote and Verify the TPM Quote signature @@ -380,13 +381,13 @@ func verifyTpmQuoteSignature(quote, sig []byte, cert *x509.Certificate) Result { err = rsa.VerifyPKCS1v15(pubKey, hashAlg, hashed[:], tpmtSig.RSA.Signature) if err != nil { log.Tracef("Failed to verify TPM quote signature: %v", err) - return Result{Success: false, ErrorCode: VerifySignature} + return ar.Result{Success: false, ErrorCode: ar.VerifySignature} } - return Result{Success: true} + return ar.Result{Success: true} } // Searches for a specific hash value in the reference values for RTM and OS -func getReferenceValue(hash []byte, pcr int, refVals []ReferenceValue) *ReferenceValue { +func getReferenceValue(hash []byte, pcr int, refVals []ar.ReferenceValue) *ar.ReferenceValue { for _, ref := range refVals { if *ref.Pcr == pcr && bytes.Equal(ref.Sha256, hash) { return &ref diff --git a/attestationreport/tpm_test.go b/verify/tpm_test.go similarity index 94% rename from attestationreport/tpm_test.go rename to verify/tpm_test.go index 4f0bff6a..bdb1ad86 100644 --- a/attestationreport/tpm_test.go +++ b/verify/tpm_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "crypto/x509" @@ -24,25 +24,27 @@ import ( "testing" "github.com/sirupsen/logrus" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" ) func Test_verifyTpmMeasurements(t *testing.T) { type args struct { - tpmM *Measurement + tpmM *ar.Measurement nonce []byte - referenceValues []ReferenceValue + referenceValues []ar.ReferenceValue cas []*x509.Certificate } tests := []struct { name string args args - want *MeasurementResult + want *ar.MeasurementResult want1 bool }{ { name: "Valid TPM Measurement Summary", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -59,7 +61,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Valid TPM Measurement", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -76,7 +78,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid Nonce", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -93,7 +95,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid Signature", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: invalidSignature, @@ -110,7 +112,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid HashChain Summary", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -127,7 +129,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid HashChain", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -144,7 +146,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid Reference Values", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: invalidSignature, @@ -161,7 +163,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid CA SubjectKeyId", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -178,7 +180,7 @@ func Test_verifyTpmMeasurements(t *testing.T) { { name: "Invalid Cert Chain", args: args{ - tpmM: &Measurement{ + tpmM: &ar.Measurement{ Type: "TPM Measurement", Evidence: validQuote, Signature: validSignature, @@ -247,7 +249,7 @@ var ( invalidSignature, _ = hex.DecodeString("0014000b0100740e077a77ff6ac21754d036f751f5f8ec5ec59448aab05bb5fd2b5d81df58bde3550d855ecf16cd25e36b5688122cfaac1a86ab94954b81d49a1b7fc7648ad26b8b808ce846fe7fd49355d2461d049904e97aa687749d55510f09b7c8610b95b6d557ebdaa25a19bfa1663f236419a1a8d974dd05b14de7f28fbce0a54c3ac428a9cf7f0752cc290580ff8d63e33050c0f53582ae24fe4d30792da71d5ef93581e3371147ed4732a0c0c0461489b1b64b1f28dd5153dbc674f04a21e279833433eabec1642cd386fdca6e52b583b2c914ebcd3c7a334214dc5e7c02880b033e321cb261ed6044785e70599d269511f83a20ee45034f0803d623763d461ce763") - validSummaryHashChain = []PcrMeasurement{ + validSummaryHashChain = []ar.PcrMeasurement{ { Type: "PCR Summary", Pcr: 1, @@ -260,7 +262,7 @@ var ( }, } - invalidSummaryHashChain = []PcrMeasurement{ + invalidSummaryHashChain = []ar.PcrMeasurement{ { Type: "PCR Summary", Pcr: 1, @@ -273,11 +275,11 @@ var ( }, } - validHashChain = []PcrMeasurement{ + validHashChain = []ar.PcrMeasurement{ { Type: "PCR Eventlog", Pcr: 1, - Events: []PcrEvent{ + Events: []ar.PcrEvent{ {Sha256: dec("ef5631c7bbb8d98ad220e211933fcde16aac6154cf229fea3c728fb0f2c27e39")}, {Sha256: dec("131462b45df65ac00834c7e73356c246037456959674acd24b08357690a03845")}, {Sha256: dec("8574d91b49f1c9a6ecc8b1e8565bd668f819ea8ed73c5f682948141587aecd3b")}, @@ -292,7 +294,7 @@ var ( { Type: "PCR Eventlog", Pcr: 4, - Events: []PcrEvent{ + Events: []ar.PcrEvent{ {Sha256: dec("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba")}, {Sha256: dec("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, {Sha256: dec("dbffd70a2c43fd2c1931f18b8f8c08c5181db15f996f747dfed34def52fad036")}, @@ -302,11 +304,11 @@ var ( }, } - invalidHashChain = []PcrMeasurement{ + invalidHashChain = []ar.PcrMeasurement{ { Type: "PCR Eventlog", Pcr: 1, - Events: []PcrEvent{ + Events: []ar.PcrEvent{ {Sha256: dec("ff5631c7bbb8d98ad220e211933fcde16aac6154cf229fea3c728fb0f2c27e39")}, {Sha256: dec("131462b45df65ac00834c7e73356c246037456959674acd24b08357690a03845")}, {Sha256: dec("8574d91b49f1c9a6ecc8b1e8565bd668f819ea8ed73c5f682948141587aecd3b")}, @@ -321,7 +323,7 @@ var ( { Type: "PCR Eventlog", Pcr: 4, - Events: []PcrEvent{ + Events: []ar.PcrEvent{ {Sha256: dec("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba")}, {Sha256: dec("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119")}, {Sha256: dec("dbffd70a2c43fd2c1931f18b8f8c08c5181db15f996f747dfed34def52fad036")}, @@ -346,7 +348,7 @@ var ( pcrs = [23]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22} - validReferenceValues = []ReferenceValue{ + validReferenceValues = []ar.ReferenceValue{ { Type: "TPM Reference Value", Sha256: dec("ef5631c7bbb8d98ad220e211933fcde16aac6154cf229fea3c728fb0f2c27e39"), @@ -433,7 +435,7 @@ var ( }, } - invalidReferenceValues = []ReferenceValue{ + invalidReferenceValues = []ar.ReferenceValue{ { Type: "TPM Reference Value", Sha256: dec("1310b2b63dc1222516e5c12cedc1cc48e338f85430849b5a5b5256467e2cd0f0"), @@ -442,11 +444,11 @@ var ( }, } - validResult = Result{ + validResult = ar.Result{ Success: true, } - validArtifacts = []DigestResult{ + validArtifacts = []ar.DigestResult{ { Digest: "ef5631c7bbb8d98ad220e211933fcde16aac6154cf229fea3c728fb0f2c27e39", Name: "EV_CPU_MICROCODE", @@ -536,20 +538,20 @@ var ( deviceCertserial, _ = new(big.Int).SetString("1", 10) caCertserial, _ = new(big.Int).SetString("634815014411613577372985193537194269253199433648", 10) - validSignatureResult = SignatureResult{ - ValidatedCerts: [][]X509CertExtracted{ + validSignatureResult = ar.SignatureResult{ + ValidatedCerts: [][]ar.X509CertExtracted{ { { Version: 3, SerialNumber: deviceCertserial, - Issuer: X509Name{ + Issuer: ar.X509Name{ Country: []string{"DE"}, Organization: []string{"Test Company"}, OrganizationalUnit: []string{"Root CA"}, Locality: []string{"Test City"}, CommonName: "Test Root CA", }, - Subject: X509Name{ + Subject: ar.X509Name{ Country: []string{"DE"}, Organization: []string{"Test Organization"}, OrganizationalUnit: []string{"device"}, @@ -559,7 +561,7 @@ var ( PostalCode: []string{"85748"}, CommonName: "de.test.ak.device0", }, - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2022-11-07 08:51:49 +0000 UTC", NotAfter: "2027-10-12 08:51:49 +0000 UTC", }, @@ -567,7 +569,7 @@ var ( SignatureAlgorithm: "ECDSA-SHA256", PublicKeyAlgorithm: "RSA", PublicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAszEASVD7GIFw2t+k813q\nSyah+290gDAEdpcle1rQlw3bxq94gvHaBa4CWGkH3SHQieBakRNRyTGYAn8q9O2w\n4fWWFFB5bBcuYVyJdzaUoPc2T/922notV3tZonPHwy00yWytiAb22mMaCAhJNDIU\nAJBieZqluq9/w5LRQ/3e9RN4CcIoFEMA4pPcWOlSXrLrpu4CdTW7Uj03tcFR6bqd\nBKXD0rKauBdIbQe9ul7t/pxaY0kLZ0k53HGk3nbcWr0j2Iwg6L4rYNd+zDZKHG0i\nPQ0RbH7+Ao2Gx1WWfkSdhqtJ5/s3Xa0dNJmeXqhNa2dMYxpKY59rVuQSleC4z3FX\n1wIDAQAB\n-----END PUBLIC KEY-----\n", - Extensions: []PkixExtension{ + Extensions: []ar.PkixExtension{ { Id: "2.5.29.15", Critical: true, @@ -598,21 +600,21 @@ var ( { Version: 3, SerialNumber: caCertserial, - Issuer: X509Name{ + Issuer: ar.X509Name{ Country: []string{"DE"}, Organization: []string{"Test Company"}, OrganizationalUnit: []string{"Root CA"}, Locality: []string{"Test City"}, CommonName: "Test Root CA", }, - Subject: X509Name{ + Subject: ar.X509Name{ Country: []string{"DE"}, Organization: []string{"Test Company"}, OrganizationalUnit: []string{"Root CA"}, Locality: []string{"Test City"}, CommonName: "Test Root CA", }, - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2022-10-23 17:01:00 +0000 UTC", NotAfter: "2027-10-22 17:01:00 +0000 UTC", }, @@ -620,7 +622,7 @@ var ( SignatureAlgorithm: "ECDSA-SHA256", PublicKeyAlgorithm: "ECDSA", PublicKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESpo2j3WJNJJtz0EvWIhAhWlkt3zx\nEvkt8fXlJW6AnLjd3SN4T4oe2lADwWkC/FdAcmbfXPlXr6gsbgxB9Uc38Q==\n-----END PUBLIC KEY-----\n", - Extensions: []PkixExtension{ + Extensions: []ar.PkixExtension{ { Id: "2.5.29.15", Critical: true, @@ -651,14 +653,14 @@ var ( ExtensionsCheck: nil, } - validTpmMeasurementResult = MeasurementResult{ + validTpmMeasurementResult = ar.MeasurementResult{ Type: "TPM Result", Summary: validResult, Freshness: validResult, Signature: validSignatureResult, Artifacts: validArtifacts, - TpmResult: &TpmResult{ - PcrMatch: []PcrResult{ + TpmResult: &ar.TpmResult{ + PcrMatch: []ar.PcrResult{ { Pcr: 1, Calculated: "5f96aec0a6b390185495c35bc76dceb9fa6addb4e59b6fc1b3e1992eeb08a5c6", diff --git a/verify/verify.go b/verify/verify.go new file mode 100644 index 00000000..57efaa79 --- /dev/null +++ b/verify/verify.go @@ -0,0 +1,673 @@ +// Copyright (c) 2021 Fraunhofer AISEC +// Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package verify + +import ( + "bytes" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/Fraunhofer-AISEC/cmc/internal" + "github.com/fxamacker/cbor/v2" + "github.com/sirupsen/logrus" + + "time" +) + +type PolicyEngineSelect uint32 + +const ( + PolicyEngineSelect_None PolicyEngineSelect = 0 + PolicyEngineSelect_JS PolicyEngineSelect = 1 + PolicyEngineSelect_DukTape PolicyEngineSelect = 2 +) + +type PolicyValidator interface { + Validate(policies []byte, result ar.VerificationResult) bool +} + +var ( + log = logrus.WithField("service", "ar") + + policyEngines = map[PolicyEngineSelect]PolicyValidator{} +) + +// Verify verifies an attestation report in full serialized JWS +// format against the supplied nonce and CA certificate. Verifies the certificate +// chains of all attestation report elements as well as the measurements against +// the reference values and the compatibility of software artefacts. +func Verify(arRaw, nonce, casPem []byte, policies []byte, polEng PolicyEngineSelect, intelCache string) ar.VerificationResult { + result := ar.VerificationResult{ + Type: "Verification Result", + Success: true, + SwCertLevel: 0} + + cas, err := internal.ParseCertsPem(casPem) + if err != nil { + log.Tracef("Failed to parse specified CA certificate(s): %v", err) + result.Success = false + result.ErrorCode = ar.ParseCA + return result + } + + // Detect serialization format + var s ar.Serializer + if json.Valid(arRaw) { + log.Trace("Detected JSON serialization") + s = ar.JsonSerializer{} + } else if err := cbor.Valid(arRaw); err == nil { + log.Trace("Detected CBOR serialization") + s = ar.CborSerializer{} + } else { + log.Trace("Unable to detect AR serialization format") + result.Success = false + result.ErrorCode = ar.UnknownSerialization + return result + } + + // Verify and unpack attestation report + report, tr, code := verifyAr(arRaw, cas, s) + result.ReportSignature = tr.SignatureCheck + if code != ar.NotSet { + result.ErrorCode = code + result.Success = false + return result + } + + // Verify and unpack metadata from attestation report + metadata, mr, ok := verifyMetadata(report, cas, s) + if !ok { + result.Success = false + } + result.MetadataResult = *mr + + // Verify nonce + if res := bytes.Compare(report.Nonce, nonce); res != 0 { + log.Tracef("Nonces mismatch: supplied nonce: %v, report nonce = %v", + hex.EncodeToString(nonce), hex.EncodeToString(report.Nonce)) + result.FreshnessCheck.Success = false + result.FreshnessCheck.Expected = hex.EncodeToString(report.Nonce) + result.FreshnessCheck.Got = hex.EncodeToString(nonce) + result.Success = false + } else { + result.FreshnessCheck.Success = true + } + + refVals, err := collectReferenceValues(metadata) + if err != nil { + log.Tracef("Failed to collect reference values: %v", err) + result.Success = false + result.ErrorCode = ar.RefValTypeNotSupported + } + + hwAttest := false + for _, m := range report.Measurements { + + switch mtype := m.Type; mtype { + + case "TPM Measurement": + r, ok := verifyTpmMeasurements(m, nonce, refVals["TPM Reference Value"], cas) + if !ok { + result.Success = false + } + result.Measurements = append(result.Measurements, *r) + hwAttest = true + + case "SNP Measurement": + r, ok := verifySnpMeasurements(m, nonce, refVals["SNP Reference Value"]) + if !ok { + result.Success = false + } + result.Measurements = append(result.Measurements, *r) + hwAttest = true + + case "TDX Measurement": + r, ok := verifyTdxMeasurements(m, nonce, intelCache, refVals["TDX Reference Value"]) + if !ok { + result.Success = false + } + result.Measurements = append(result.Measurements, *r) + hwAttest = true + + case "SGX Measurement": + r, ok := verifySgxMeasurements(m, nonce, intelCache, refVals["SGX Reference Value"]) + if !ok { + result.Success = false + } + result.Measurements = append(result.Measurements, *r) + hwAttest = true + + case "IAS Measurement": + r, ok := verifyIasMeasurements(m, nonce, + refVals["IAS Reference Value"], cas) + if !ok { + result.Success = false + } + result.Measurements = append(result.Measurements, *r) + hwAttest = true + + default: + log.Tracef("Unsupported measurement type '%v'", mtype) + result.Success = false + result.ErrorCode = ar.MeasurementTypeNotSupported + } + } + + // The lowest certification level of all components determines the certification + // level for the device's software stack + levels := make([]int, 0) + levels = append(levels, metadata.RtmManifest.CertificationLevel) + levels = append(levels, metadata.OsManifest.CertificationLevel) + for _, app := range metadata.AppManifests { + levels = append(levels, app.CertificationLevel) + } + aggCertLevel := levels[0] + for _, l := range levels { + if l < aggCertLevel { + aggCertLevel = l + } + } + // If no hardware trust anchor is present, the maximum certification level is 1 + // If there are reference values with a higher trust level present, the remote attestation + // must fail + if !hwAttest && aggCertLevel > 1 { + log.Tracef("No hardware trust anchor measurements present but claimed certification level is %v, which requires a hardware trust anchor", aggCertLevel) + result.ErrorCode = ar.InvalidCertificationLevel + result.Success = false + } + result.SwCertLevel = aggCertLevel + + // Verify the compatibility of the attestation report through verifying the + // compatibility of all components + + // Check that the OS and RTM Manifest are specified in the Device Description + if metadata.DeviceDescription.RtmManifest == metadata.RtmManifest.Name { + result.DevDescResult.CorrectRtm.Success = true + } else { + log.Tracef("Device Description listed wrong RTM Manifest: %v vs. %v", + metadata.DeviceDescription.RtmManifest, metadata.RtmManifest.Name) + result.DevDescResult.CorrectRtm.Success = false + result.DevDescResult.CorrectRtm.Expected = metadata.DeviceDescription.RtmManifest + result.DevDescResult.CorrectRtm.Got = metadata.RtmManifest.Name + result.DevDescResult.Summary.Success = false + result.Success = false + } + if metadata.DeviceDescription.OsManifest == metadata.OsManifest.Name { + result.DevDescResult.CorrectOs.Success = true + } else { + log.Tracef("Device Description listed wrong OS Manifest: %v vs. %v", + metadata.DeviceDescription.OsManifest, metadata.OsManifest.Name) + result.DevDescResult.CorrectOs.Success = false + result.DevDescResult.CorrectOs.Expected = metadata.DeviceDescription.OsManifest + result.DevDescResult.CorrectOs.Got = metadata.OsManifest.Name + result.DevDescResult.Summary.Success = false + result.Success = false + } + + // Extract app description names + appDescriptions := make([]string, 0) + for _, a := range metadata.DeviceDescription.AppDescriptions { + appDescriptions = append(appDescriptions, a.AppManifest) + } + // Extract app manifest names + appManifestNames := make([]string, 0) + for _, a := range metadata.AppManifests { + appManifestNames = append(appManifestNames, a.Name) + } + + // Check that every AppManifest has a corresponding AppDescription + log.Tracef("Iterating app manifests length %v", len(metadata.AppManifests)) + for _, a := range metadata.AppManifests { + r := ar.Result{ + Success: true, + ExpectedOneOf: appDescriptions, + Got: a.Name, + } + if !contains(a.Name, appDescriptions) { + log.Tracef("Device Description does not list the following App Manifest: %v", a.Name) + r.Success = false + result.DevDescResult.Summary.Success = false + result.Success = false + } + result.DevDescResult.CorrectApps = append(result.DevDescResult.CorrectApps, r) + } + + // Check that every App Description has a corresponding App Manifest + log.Tracef("Iterating app descriptions length %v", len(metadata.DeviceDescription.AppDescriptions)) + for _, desc := range metadata.DeviceDescription.AppDescriptions { + found := false + for _, manifest := range metadata.AppManifests { + if desc.AppManifest == manifest.Name { + found = true + } + } + if !found { + log.Tracef("No app manifest for app description: %v", desc.AppManifest) + r := ar.Result{ + Success: false, + Got: desc.AppManifest, + ExpectedOneOf: appManifestNames, + } + result.DevDescResult.CorrectApps = append(result.DevDescResult.CorrectApps, r) + result.Success = false + } + } + + // Check that the Rtm Manifest is compatible with the OS Manifest + if contains(metadata.RtmManifest.Name, metadata.OsManifest.Rtms) { + result.DevDescResult.RtmOsCompatibility.Success = true + } else { + log.Tracef("RTM Manifest %v is not compatible with OS Manifest %v", + metadata.RtmManifest.Name, metadata.OsManifest.Name) + result.DevDescResult.RtmOsCompatibility.Success = false + result.DevDescResult.RtmOsCompatibility.ExpectedOneOf = metadata.OsManifest.Rtms + result.DevDescResult.RtmOsCompatibility.Got = metadata.RtmManifest.Name + result.DevDescResult.Summary.Success = false + result.Success = false + } + + // Check that the OS Manifest is compatible with all App Manifests + for _, a := range metadata.AppManifests { + r := ar.Result{ + Success: true, + ExpectedOneOf: a.Oss, + Got: metadata.OsManifest.Name, + } + if !contains(metadata.OsManifest.Name, a.Oss) { + log.Tracef("OS Manifest %v is not compatible with App Manifest %v", metadata.OsManifest.Name, a.Name) + r.Success = false + result.DevDescResult.Summary.Success = false + result.Success = false + } + result.DevDescResult.OsAppsCompatibility = + append(result.DevDescResult.OsAppsCompatibility, r) + } + + // Validate policies if specified + result.PolicySuccess = true + if policies != nil { + p, ok := policyEngines[polEng] + if !ok { + log.Tracef("Internal error: policy engine %v not implemented", polEng) + result.Success = false + result.ErrorCode = ar.PolicyEngineNotImplemented + result.PolicySuccess = false + } else { + ok = p.Validate(policies, result) + if !ok { + log.Trace("Custom policy validation failed") + result.Success = false + result.ErrorCode = ar.VerifyPolicies + result.PolicySuccess = false + } + } + } else { + log.Tracef("No custom policies specified") + } + + // Add additional information + result.Prover = metadata.DeviceDescription.Name + if result.Prover == "" { + result.Prover = "Unknown" + } + result.Created = time.Now().Format(time.RFC3339) + + if result.Success { + log.Infof("SUCCESS: Verification for Prover %v (%v)", result.Prover, result.Created) + } else { + log.Infof("FAILED: Verification for Prover %v (%v)", result.Prover, result.Created) + } + + return result +} + +func extendSha256(hash []byte, data []byte) []byte { + concat := append(hash, data...) + h := sha256.Sum256(concat) + ret := make([]byte, 32) + copy(ret, h[:]) + return ret +} + +func extendSha384(hash []byte, data []byte) []byte { + concat := append(hash, data...) + h := sha512.Sum384(concat) + ret := make([]byte, 48) + copy(ret, h[:]) + return ret +} + +func verifyAr(attestationReport []byte, cas []*x509.Certificate, s ar.Serializer, +) (*ar.AttestationReport, ar.TokenResult, ar.ErrorCode) { + + report := ar.AttestationReport{} + + //Validate Attestation Report signature + result, payload, ok := s.VerifyToken(attestationReport, cas) + if !ok { + log.Trace("Validation of Attestation Report failed") + return nil, result, ar.VerifyAR + } + + err := s.Unmarshal(payload, &report) + if err != nil { + log.Tracef("Parsing of Attestation Report failed: %v", err) + return nil, result, ar.ParseAR + } + + return &report, result, ar.NotSet +} + +func verifyMetadata(report *ar.AttestationReport, cas []*x509.Certificate, s ar.Serializer, +) (*ar.Metadata, *ar.MetadataResult, bool) { + + metadata := &ar.Metadata{} + result := &ar.MetadataResult{} + success := true + + // Validate and unpack Rtm Manifest + tokenRes, payload, ok := s.VerifyToken(report.RtmManifest, cas) + result.RtmResult.Summary = tokenRes.Summary + result.RtmResult.SignatureCheck = tokenRes.SignatureCheck + if !ok { + log.Trace("Validation of RTM Manifest failed") + success = false + } else { + err := s.Unmarshal(payload, &metadata.RtmManifest) + if err != nil { + log.Tracef("Unpacking of RTM Manifest failed: %v", err) + result.RtmResult.Summary.Success = false + result.RtmResult.Summary.ErrorCode = ar.ParseRTMManifest + success = false + } else { + result.RtmResult.MetaInfo = metadata.RtmManifest.MetaInfo + result.RtmResult.ValidityCheck = checkValidity(metadata.RtmManifest.Validity) + result.RtmResult.Details = metadata.RtmManifest.Details + if !result.RtmResult.ValidityCheck.Success { + result.RtmResult.Summary.Success = false + success = false + } + } + } + + // Validate and unpack OS Manifest + tokenRes, payload, ok = s.VerifyToken(report.OsManifest, cas) + result.OsResult.Summary = tokenRes.Summary + result.OsResult.SignatureCheck = tokenRes.SignatureCheck + if !ok { + log.Trace("Validation of OS Manifest failed") + success = false + } else { + err := s.Unmarshal(payload, &metadata.OsManifest) + if err != nil { + log.Tracef("Unpacking of OS Manifest failed: %v", err) + result.OsResult.Summary.Success = false + result.OsResult.Summary.ErrorCode = ar.ParseOSManifest + success = false + } else { + result.OsResult.MetaInfo = metadata.OsManifest.MetaInfo + result.OsResult.ValidityCheck = checkValidity(metadata.OsManifest.Validity) + result.OsResult.Details = metadata.OsManifest.Details + if !result.OsResult.ValidityCheck.Success { + result.OsResult.Summary.Success = false + success = false + } + } + } + + // Validate and unpack App Manifests + for i, amSigned := range report.AppManifests { + result.AppResults = append(result.AppResults, ar.ManifestResult{}) + + tokenRes, payload, ok = s.VerifyToken(amSigned, cas) + result.AppResults[i].Summary = tokenRes.Summary + result.AppResults[i].SignatureCheck = tokenRes.SignatureCheck + if !ok { + log.Trace("Validation of App Manifest failed") + success = false + } else { + var am ar.AppManifest + err := s.Unmarshal(payload, &am) + if err != nil { + log.Tracef("Unpacking of App Manifest failed: %v", err) + result.AppResults[i].Summary.Success = false + result.AppResults[i].Summary.ErrorCode = ar.Parse + success = false + } else { + metadata.AppManifests = append(metadata.AppManifests, am) + result.AppResults[i].MetaInfo = am.MetaInfo + result.AppResults[i].ValidityCheck = checkValidity(am.Validity) + if !result.AppResults[i].ValidityCheck.Success { + log.Tracef("App Manifest %v validity check failed", am.Name) + result.AppResults[i].Summary.Success = false + success = false + + } + } + } + } + + // Validate and unpack Company Description if present + if report.CompanyDescription != nil { + tokenRes, payload, ok = s.VerifyToken(report.CompanyDescription, cas) + result.CompDescResult = &ar.CompDescResult{} + result.CompDescResult.Summary = tokenRes.Summary + result.CompDescResult.SignatureCheck = tokenRes.SignatureCheck + if !ok { + log.Trace("Validation of Company Description Signatures failed") + success = false + } else { + err := s.Unmarshal(payload, &metadata.CompanyDescription) + if err != nil { + log.Tracef("Unpacking of Company Description failed: %v", err) + result.CompDescResult.Summary.Success = false + result.CompDescResult.Summary.ErrorCode = ar.Parse + success = false + } else { + result.CompDescResult.MetaInfo = metadata.CompanyDescription.MetaInfo + result.CompDescResult.CompCertLevel = metadata.CompanyDescription.CertificationLevel + + result.CompDescResult.ValidityCheck = checkValidity(metadata.CompanyDescription.Validity) + if !result.CompDescResult.ValidityCheck.Success { + log.Trace("Company Description invalid") + result.CompDescResult.Summary.Success = false + success = false + } + } + } + } + + // Validate and unpack Device Description + tokenRes, payload, ok = s.VerifyToken(report.DeviceDescription, cas) + result.DevDescResult.Summary = tokenRes.Summary + result.DevDescResult.SignatureCheck = tokenRes.SignatureCheck + if !ok { + log.Trace("Validation of Device Description failed") + success = false + } else { + err := s.Unmarshal(payload, &metadata.DeviceDescription) + if err != nil { + log.Tracef("Unpacking of Device Description failed: %v", err) + result.DevDescResult.Summary.Success = false + result.DevDescResult.Summary.ErrorCode = ar.Parse + success = false + } else { + result.DevDescResult.MetaInfo = metadata.DeviceDescription.MetaInfo + result.DevDescResult.Description = metadata.DeviceDescription.Description + result.DevDescResult.Location = metadata.DeviceDescription.Location + } + for _, appDesc := range metadata.DeviceDescription.AppDescriptions { + appResult := ar.AppDescResult{ + MetaInfo: appDesc.MetaInfo, + AppManifest: appDesc.AppManifest, + External: appDesc.External, + Environment: appDesc.Environment, + } + result.DevDescResult.AppResults = append(result.DevDescResult.AppResults, + appResult) + } + } + + return metadata, result, success +} + +func checkValidity(val ar.Validity) ar.Result { + result := ar.Result{} + result.Success = true + + notBefore, err := time.Parse(time.RFC3339, val.NotBefore) + if err != nil { + log.Tracef("Failed to parse NotBefore time. Time.Parse returned %v", err) + result.Success = false + result.ErrorCode = ar.ParseTime + return result + } + notAfter, err := time.Parse(time.RFC3339, val.NotAfter) + if err != nil { + log.Tracef("Failed to parse NotAfter time. Time.Parse returned %v", err) + result.Success = false + result.ErrorCode = ar.ParseTime + return result + } + currentTime := time.Now() + + if notBefore.After(currentTime) { + log.Trace("Validity check failed: Artifact is not valid yet") + result.Success = false + result.ErrorCode = ar.NotYetValid + } + + if currentTime.After(notAfter) { + log.Trace("Validity check failed: Artifact validity has expired") + result.Success = false + result.ErrorCode = ar.Expired + } + + return result +} + +func collectReferenceValues(metadata *ar.Metadata) (map[string][]ar.ReferenceValue, error) { + // Gather a list of all reference values independent of the type + verList := append(metadata.RtmManifest.ReferenceValues, metadata.OsManifest.ReferenceValues...) + for _, appManifest := range metadata.AppManifests { + verList = append(verList, appManifest.ReferenceValues...) + } + + verMap := make(map[string][]ar.ReferenceValue) + + // Iterate through the reference values and sort them into the different types + for _, v := range verList { + if v.Type != "SNP Reference Value" && v.Type != "SW Reference Value" && v.Type != "TPM Reference Value" && v.Type != "TDX Reference Value" && v.Type != "SGX Reference Value" { + return nil, fmt.Errorf("reference value of type %v is not supported", v.Type) + } + verMap[v.Type] = append(verMap[v.Type], v) + } + return verMap, nil +} + +func checkExtensionUint8(cert *x509.Certificate, oid string, value uint8) (ar.Result, bool) { + + pem := internal.WriteCertPem(cert) + log.Warnf("%v", string(pem)) + + for _, ext := range cert.Extensions { + + log.Tracef("OID: %v", ext.Id.String()) + + if ext.Id.String() == oid { + log.Tracef("Found %v, length %v, values %v", oid, len(ext.Value), ext.Value) + if len(ext.Value) != 3 && len(ext.Value) != 4 { + log.Tracef("extension %v value unexpected length %v (expected 3 or 4)", + oid, len(ext.Value)) + return ar.Result{Success: false, ErrorCode: ar.OidLength}, false + } + if ext.Value[0] != 0x2 { + log.Tracef("extension %v value[0]: %v does not match expected value 2 (tag Integer)", + oid, ext.Value[0]) + return ar.Result{Success: false, ErrorCode: ar.OidTag}, false + } + if ext.Value[1] == 0x1 { + if ext.Value[2] != value { + log.Tracef("extension %v value[2]: %v does not match expected value %v", + oid, ext.Value[2], value) + return ar.Result{ + Success: false, + Expected: strconv.FormatUint(uint64(value), 10), + Got: strconv.FormatUint(uint64(ext.Value[2]), 10), + }, false + } + } else if ext.Value[1] == 0x2 { + // Due to openssl, the sign bit must remain zero for positive integers + // even though this field is defined as unsigned int in the AMD spec + // Thus, if the most significant bit is required, one byte of additional 0x00 padding is added + if ext.Value[2] != 0x00 || ext.Value[3] != value { + log.Tracef("extension %v value = %v%v does not match expected value %v", + oid, ext.Value[2], ext.Value[3], value) + return ar.Result{ + Success: false, + Expected: strconv.FormatUint(uint64(value), 10), + Got: strconv.FormatUint(uint64(ext.Value[3]), 10), + }, false + } + } else { + log.Tracef("extension %v value[1]: %v does not match expected value 1 or 2 (length of integer)", + oid, ext.Value[1]) + return ar.Result{Success: false, ErrorCode: ar.OidLength}, false + } + return ar.Result{Success: true}, true + } + } + + log.Tracef("extension %v not present in certificate", oid) + return ar.Result{Success: false, ErrorCode: ar.OidNotPresent}, false +} + +func checkExtensionBuf(cert *x509.Certificate, oid string, buf []byte) (ar.Result, bool) { + + for _, ext := range cert.Extensions { + + if ext.Id.String() == oid { + if cmp := bytes.Compare(ext.Value, buf); cmp != 0 { + log.Tracef("extension %v value %v does not match expected value %v", + oid, hex.EncodeToString(ext.Value), hex.EncodeToString(buf)) + return ar.Result{ + Success: false, + Expected: hex.EncodeToString(buf), + Got: hex.EncodeToString(ext.Value), + }, false + } + return ar.Result{Success: true}, true + } + } + + log.Tracef("extension %v not present in certificate", oid) + return ar.Result{Success: false, ErrorCode: ar.OidNotPresent}, false +} + +func contains(elem string, list []string) bool { + for _, s := range list { + if s == elem { + return true + } + } + return false +} diff --git a/attestationreport/attestationreport_test.go b/verify/verify_test.go similarity index 81% rename from attestationreport/attestationreport_test.go rename to verify/verify_test.go index 8fcc7a7b..ec27db7d 100644 --- a/attestationreport/attestationreport_test.go +++ b/verify/verify_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestationreport +package verify import ( "crypto" @@ -28,13 +28,15 @@ import ( "testing" "time" + ar "github.com/Fraunhofer-AISEC/cmc/attestationreport" + "github.com/Fraunhofer-AISEC/cmc/generate" "github.com/Fraunhofer-AISEC/cmc/internal" "github.com/sirupsen/logrus" ) // variables for Test_collectReferenceValues var ( - vers = []ReferenceValue{ + vers = []ar.ReferenceValue{ {Type: "TPM Reference Value", Name: "TPM1"}, {Type: "TPM Reference Value", Name: "TPM2"}, {Type: "SNP Reference Value", Name: "SNP1"}, @@ -42,41 +44,41 @@ var ( {Type: "SW Reference Value", Name: "SW2"}, } - verMap = map[string][]ReferenceValue{ + verMap = map[string][]ar.ReferenceValue{ "TPM Reference Value": vers[:2], "SNP Reference Value": {vers[2]}, "SW Reference Value": vers[3:], } - rtmManifest = RtmManifest{ - ReferenceValues: []ReferenceValue{ + rtmManifest = ar.RtmManifest{ + ReferenceValues: []ar.ReferenceValue{ vers[0], vers[2], }, } - osManifest = OsManifest{ - ReferenceValues: []ReferenceValue{ + osManifest = ar.OsManifest{ + ReferenceValues: []ar.ReferenceValue{ vers[1], }, } - appManifests = []AppManifest{ - {ReferenceValues: []ReferenceValue{vers[3]}}, - {ReferenceValues: []ReferenceValue{vers[4]}}, + appManifests = []ar.AppManifest{ + {ReferenceValues: []ar.ReferenceValue{vers[3]}}, + {ReferenceValues: []ar.ReferenceValue{vers[4]}}, } ) // variables for TestVerify var ( - validRtmManifest = RtmManifest{ - MetaInfo: MetaInfo{ + validRtmManifest = ar.RtmManifest{ + MetaInfo: ar.MetaInfo{ Type: "RTM Manifest", Name: "de.test.rtm", Version: "2023-04-10T20:00:00Z", }, DevCommonName: "Test Developer", - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2023-04-10T20:00:00Z", NotAfter: "2030-04-10T20:00:00Z", }, @@ -84,14 +86,14 @@ var ( CertificationLevel: 1, } - validOsManifest = OsManifest{ - MetaInfo: MetaInfo{ + validOsManifest = ar.OsManifest{ + MetaInfo: ar.MetaInfo{ Type: "OS Manifest", Name: "de.test.os", Version: "2023-04-10T20:00:00Z", }, DevCommonName: "Test Developer", - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2023-04-10T20:00:00Z", NotAfter: "2030-04-10T20:00:00Z", }, @@ -102,14 +104,14 @@ var ( }, } - incompatibleOsManifest = OsManifest{ - MetaInfo: MetaInfo{ + incompatibleOsManifest = ar.OsManifest{ + MetaInfo: ar.MetaInfo{ Type: "OS Manifest", Name: "de.test.os", Version: "2023-04-10T20:00:00Z", }, DevCommonName: "Test Developer", - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2023-04-10T20:00:00Z", NotAfter: "2030-04-10T20:00:00Z", }, @@ -120,8 +122,8 @@ var ( }, } - validDeviceDescription = DeviceDescription{ - MetaInfo: MetaInfo{ + validDeviceDescription = ar.DeviceDescription{ + MetaInfo: ar.MetaInfo{ Type: "Device Description", Name: "test-device.test.de", Version: "2023-04-10T20:00:00Z", @@ -131,8 +133,8 @@ var ( OsManifest: "de.test.os", } - invalidDeviceDescription = DeviceDescription{ - MetaInfo: MetaInfo{ + invalidDeviceDescription = ar.DeviceDescription{ + MetaInfo: ar.MetaInfo{ Type: "Device Description", Name: "test-device.test.de", Version: "2023-04-10T20:00:00Z", @@ -150,12 +152,12 @@ type SwSigner struct { priv crypto.PrivateKey } -func (s *SwSigner) Init(c *DriverConfig) error { +func (s *SwSigner) Init(c *ar.DriverConfig) error { return nil } -func (s *SwSigner) Measure(nonce []byte) (Measurement, error) { - return Measurement{}, nil +func (s *SwSigner) Measure(nonce []byte) (ar.Measurement, error) { + return ar.Measurement{}, nil } func (s *SwSigner) Lock() error { @@ -239,18 +241,18 @@ func createCertsAndKeys() (*ecdsa.PrivateKey, []*x509.Certificate, error) { func Test_collectReferenceValues(t *testing.T) { type args struct { - metadata *Metadata + metadata *ar.Metadata } tests := []struct { name string args args - want map[string][]ReferenceValue + want map[string][]ar.ReferenceValue wantErr bool }{ { name: "Success", args: args{ - metadata: &Metadata{ + metadata: &ar.Metadata{ RtmManifest: rtmManifest, OsManifest: osManifest, AppManifests: appManifests, @@ -278,79 +280,79 @@ func TestVerify(t *testing.T) { logrus.SetLevel(logrus.TraceLevel) type args struct { - serializer Serializer - rtmManifest RtmManifest - osManifest OsManifest - deviceDescription DeviceDescription + serializer ar.Serializer + rtmManifest ar.RtmManifest + osManifest ar.OsManifest + deviceDescription ar.DeviceDescription nonce []byte } tests := []struct { name string args args - want VerificationResult + want ar.VerificationResult }{ { name: "Valid Report JSON", args: args{ - serializer: JsonSerializer{}, + serializer: ar.JsonSerializer{}, rtmManifest: validRtmManifest, osManifest: validOsManifest, deviceDescription: validDeviceDescription, nonce: nonce, }, - want: VerificationResult{Success: true}, + want: ar.VerificationResult{Success: true}, }, { name: "Valid Report CBOR", args: args{ - serializer: CborSerializer{}, + serializer: ar.CborSerializer{}, rtmManifest: validRtmManifest, osManifest: validOsManifest, deviceDescription: validDeviceDescription, nonce: nonce, }, - want: VerificationResult{Success: true}, + want: ar.VerificationResult{Success: true}, }, { name: "Nonce mismatch", args: args{ - serializer: JsonSerializer{}, + serializer: ar.JsonSerializer{}, rtmManifest: validRtmManifest, osManifest: validOsManifest, deviceDescription: validDeviceDescription, nonce: []byte{}, }, - want: VerificationResult{Success: false}, + want: ar.VerificationResult{Success: false}, }, { // expected aggregated CertificationLevel in Manifests for // empty measurement is max. 1 (here CertificationLevel = 3) name: "Invalid Certification Level", args: args{ - serializer: JsonSerializer{}, - rtmManifest: RtmManifest{ - MetaInfo: MetaInfo{ + serializer: ar.JsonSerializer{}, + rtmManifest: ar.RtmManifest{ + MetaInfo: ar.MetaInfo{ Type: "RTM Manifest", Name: "de.test.rtm", Version: "2023-04-10T20:00:00Z", }, DevCommonName: "Test Developer", - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2023-04-10T20:00:00Z", NotAfter: "2026-04-10T20:00:00Z", }, Description: "de.test.rtm", CertificationLevel: 3, - ReferenceValues: []ReferenceValue{}, + ReferenceValues: []ar.ReferenceValue{}, }, - osManifest: OsManifest{ - MetaInfo: MetaInfo{ + osManifest: ar.OsManifest{ + MetaInfo: ar.MetaInfo{ Type: "OS Manifest", Name: "de.test.os", Version: "2023-04-10T20:00:00Z", }, DevCommonName: "Test Developer", - Validity: Validity{ + Validity: ar.Validity{ NotBefore: "2023-04-10T20:00:00Z", NotAfter: "2026-04-10T20:00:00Z", }, @@ -363,29 +365,29 @@ func TestVerify(t *testing.T) { deviceDescription: validDeviceDescription, nonce: nonce, }, - want: VerificationResult{Success: false}, + want: ar.VerificationResult{Success: false}, }, { name: "Invalid Device Description", args: args{ - serializer: JsonSerializer{}, + serializer: ar.JsonSerializer{}, rtmManifest: validRtmManifest, osManifest: validOsManifest, deviceDescription: invalidDeviceDescription, nonce: nonce, }, - want: VerificationResult{Success: false}, + want: ar.VerificationResult{Success: false}, }, { name: "Incompatible RTM/OS Manifests", args: args{ - serializer: JsonSerializer{}, + serializer: ar.JsonSerializer{}, rtmManifest: validRtmManifest, osManifest: incompatibleOsManifest, deviceDescription: validDeviceDescription, nonce: nonce, }, - want: VerificationResult{Success: false}, + want: ar.VerificationResult{Success: false}, }, } @@ -409,7 +411,7 @@ func TestVerify(t *testing.T) { // Preparation: Generate a Sample Report log.Trace("Generating a Sample Report") - ar := AttestationReport{ + ar := ar.AttestationReport{ Type: "Attestation Report", Nonce: tt.args.nonce, } @@ -427,15 +429,15 @@ func TestVerify(t *testing.T) { if err != nil { t.Errorf("failed to marshal the DeviceDescription: %v", err) } - ar.RtmManifest, err = Sign(rtmManifest, swSigner, s) + ar.RtmManifest, err = generate.Sign(rtmManifest, swSigner, s) if err != nil { t.Errorf("failed to sign the RtmManifest: %v", err) } - ar.OsManifest, err = Sign(osManifest, swSigner, s) + ar.OsManifest, err = generate.Sign(osManifest, swSigner, s) if err != nil { t.Errorf("failed to sign the OsManifest: %v", err) } - ar.DeviceDescription, err = Sign(deviceDescription, swSigner, s) + ar.DeviceDescription, err = generate.Sign(deviceDescription, swSigner, s) if err != nil { t.Errorf("failed to sign the DeviceDescription: %v", err) } @@ -446,7 +448,7 @@ func TestVerify(t *testing.T) { } // Preparation: Sign the report - arSigned, err := Sign(report, swSigner, s) + arSigned, err := generate.Sign(report, swSigner, s) if err != nil { t.Errorf("Internal Error: Failed to sign Attestion Report: %v", err) } From a97e54c49227b01a84eb35fd522bf60b8e8ccf40 Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Mon, 24 Jun 2024 15:24:06 +0000 Subject: [PATCH 25/26] example setup: updated scripts Signed-off-by: Simon Ott --- example-setup/update-container-manifest-live | 9 ++++++++- example-setup/update-platform | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/example-setup/update-container-manifest-live b/example-setup/update-container-manifest-live index c011e18e..1267fd15 100755 --- a/example-setup/update-container-manifest-live +++ b/example-setup/update-container-manifest-live @@ -47,7 +47,7 @@ runtime="${cmc}/tools/containerd-shim-cmc-v1/containerd-shim-cmc-v1" container="${container%/}" container_name=$(echo "${container}-oci" | sed 's:.*/::' | tr : -) -echo "Creating reference values for container: ${container} with args ${args} and runtime ${runtime}" +echo "Creating reference values for container: ${container} with args ${args}" echo "Using ${data} as directory for local data" # Delete temporary manifests @@ -58,6 +58,13 @@ sudo rm -f /tmp/container-refs echo "Generating reference values for ${client} client" if [[ "${client}" == "ctr" ]]; then + sudo ctr image pull ${container} + set +e + sudo ctr run --detach ${args} ${container} REF_CONTAINER + sudo ctr task kill -s SIGKILL REF_CONTAINER + sudo ctr container delete REF_CONTAINER + set -e +elif [[ "${client}" == "shim" ]]; then sudo ctr image pull ${container} set +e sudo ctr run --runtime ${runtime} -t --rm ${args} ${container} CMC_GENERATE_APP_MANIFEST diff --git a/example-setup/update-platform b/example-setup/update-platform index 69a16c44..8b9ad8ff 100755 --- a/example-setup/update-platform +++ b/example-setup/update-platform @@ -25,6 +25,12 @@ fi echo "Using ${data} as directory for local data" +# Replace existing app description in the device description (will be added through +# update-app-manifest scripts) +json=$(cat "${input}/device.description.json") +json=$(echo "${json}" | jq 'del(.appDescriptions[])') +printf "%s\n" "${json}" > "${input}/device.description.json" + # Parse the values of the RTM PCRs from the kernel's binary bios measurements as reference values referenceValues=$(sudo parse-srtm-pcrs -p 0,1,2,3,4,5,6,7 -f json -e) @@ -48,6 +54,7 @@ key="${data}/pki/signing-cert-key.pem" chain="${data}/pki/signing-cert.pem,${data}/pki/ca.pem" rm -rf "${tmp}" +rm -rf "${out}" mkdir -p "${tmp}" mkdir -p "${out}" From 686d3fca4522175a2cb413a4fb0d5ad26d172f6e Mon Sep 17 00:00:00 2001 From: Simon Ott Date: Mon, 24 Jun 2024 15:25:40 +0000 Subject: [PATCH 26/26] measure: minor changes Signed-off-by: Simon Ott --- go.mod | 1 + go.sum | 2 + measure/rtconfig.go | 90 ++++++++++++++++----- measure/rtconfig_test.go | 149 ++++++++++++++++++++++++++++++++++- testtool/coap.go | 2 +- testtool/grpc.go | 2 +- testtool/libapi.go | 2 +- testtool/socket.go | 2 +- tools/measure-bundle/main.go | 2 +- 9 files changed, 223 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 1f0ec09b..d014d576 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888 github.com/google/go-tpm v0.9.0 github.com/mattn/go-sqlite3 v1.14.16 + github.com/opencontainers/runtime-spec v1.2.0 github.com/plgd-dev/go-coap/v3 v3.1.2 github.com/robertkrimen/otto v0.2.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 1ddb38de..780acc68 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= diff --git a/measure/rtconfig.go b/measure/rtconfig.go index c4ce8f08..01f9e68d 100644 --- a/measure/rtconfig.go +++ b/measure/rtconfig.go @@ -17,14 +17,32 @@ package measure import ( "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" + "path/filepath" "strings" + "github.com/opencontainers/runtime-spec/specs-go" + jcs "github.com/Fraunhofer-AISEC/cmc/jsoncanonicalizer" ) -func GetConfigMeasurement(id string, configData []byte) ([]byte, []byte, error) { +func GetConfigMeasurement(id string, configData []byte) ([]byte, []byte, string, error) { + + normalizedConfig, rootfs, err := normalize(id, configData) + if err != nil { + return nil, nil, "", fmt.Errorf("failed to normalize config: %w", err) + } + + hasher := sha256.New() + hasher.Write(normalizedConfig) + hash := hasher.Sum(nil) + + return hash, normalizedConfig, rootfs, nil +} + +func normalize(id string, configData []byte) ([]byte, string, error) { // The container id varies depending on the client. For ctr, this is a user-specified // name. For docker, this is a random UUID which changes on different container invocations @@ -34,46 +52,76 @@ func GetConfigMeasurement(id string, configData []byte) ([]byte, []byte, error) shortId = shortId[:12] } configString := string(configData) + + // TODO check the security relevance of every manipulated OCI runtime config bundle property + + // Replace container-ID which is a random UUID that varies between each start of the same container configString = strings.ReplaceAll(configString, id, "") configString = strings.ReplaceAll(configString, shortId, "") reproducibleConfig := []byte(configString) // Unmarshal the config for further modifications - var config map[string]interface{} + var config specs.Spec err := json.Unmarshal(reproducibleConfig, &config) if err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal OCI runtime config: %w", err) + return nil, "", fmt.Errorf("failed to unmarshal OCI runtime config: %w", err) + } + + // Make the docker prestart hooks reproducible + // The prestart path (proc//exe) is a symlink to the docker daemon + if config.Hooks != nil { + //lint:ignore SA1019 still needed for old configs + for i := range config.Hooks.Prestart { + //lint:ignore SA1019 still needed for old configs + config.Hooks.Prestart[i].Path, err = filepath.EvalSymlinks(config.Hooks.Prestart[i].Path) + if err != nil { + return nil, "", fmt.Errorf("failed to evaluate dockerd symlink: %w", err) + } + + // TODO FIXME The docker network controller ID is generated randomly (// github.com/moby/moby/libnetwork/controller.go:New()) + // and then the truncated daemon.netController.ID() is added as an argument in the prestart + // hooks (github.com/moby/moby/daemon/oci_linux.go:withLibnetwork()). This is a very hacky + // way to make it reproducible, ideally, the actual network ID controller ID should be + // retrieved and then replaced + //lint:ignore SA1019 still needed for old configs + for j := range config.Hooks.Prestart[i].Args { + //lint:ignore SA1019 still needed for old configs + if len(config.Hooks.Prestart[i].Args[j]) == 12 && isHex(config.Hooks.Prestart[i].Args[j]) { + //lint:ignore SA1019 still needed for old configs + config.Hooks.Prestart[i].Args[j] = "" + } + } + } } // Some values from the config, which are not security relevant and may change // on different container invocations will be ignored - delete(config, "annotations") - delete(config, "root") - delete(config, "hooks") - process, ok := config["process"].(map[string]interface{}) - if ok { - delete(process, "terminal") - delete(process, "consoleSize") - } - linux, ok := config["linux"].(map[string]interface{}) - if ok { - delete(linux, "cgroupsPath") - } + // TODO FIXME check all properties for security relevance , currently experimental + config.Annotations = nil + config.Process.Terminal = false + config.Process.ConsoleSize = nil + + // TODO FIXME Some container engines such as docker store the rootfs in an non-reproducible + // path. Therefore we store the path and hash the rootfs at this path, but remove the + // path from the config.json. Check if this is secure + rootfs := config.Root.Path + config.Root = nil data, err := json.Marshal(config) if err != nil { - return nil, nil, fmt.Errorf("failed to marshal config: %w", err) + return nil, "", fmt.Errorf("failed to marshal config: %w", err) } // Transform to RFC 8785 canonical JSON form for reproducible hashing tbh, err := jcs.Transform(data) if err != nil { - return nil, nil, fmt.Errorf("failed to cannonicalize json: %w", err) + return nil, "", fmt.Errorf("failed to cannonicalize json: %w", err) } - hasher := sha256.New() - hasher.Write(tbh) - hash := hasher.Sum(nil) + return tbh, rootfs, nil +} - return hash, tbh, nil +func isHex(s string) bool { + _, err := hex.DecodeString(s) + return err == nil } diff --git a/measure/rtconfig_test.go b/measure/rtconfig_test.go index de0c864c..ffe5df6d 100644 --- a/measure/rtconfig_test.go +++ b/measure/rtconfig_test.go @@ -16,6 +16,7 @@ package measure import ( + "encoding/hex" "reflect" "testing" ) @@ -37,25 +38,167 @@ func TestGetConfigMeasurement(t *testing.T) { id: "mycontainer", configData: configData, }, - want: []byte{0x9e, 0x4a, 0x82, 0x2d, 0xa5, 0x65, 0x2e, 0x40, 0x42, 0x39, 0xd9, 0xb8, 0x02, 0xdd, 0x59, 0x0a, 0x04, 0x13, 0x23, 0x76, 0xdf, 0xc6, 0xe0, 0xe1, 0x29, 0x68, 0x5c, 0x4e, 0x3f, 0x25, 0x35, 0x54}, + want: []byte{0x17, 0x76, 0xdf, 0xed, 0x31, 0x7c, 0xa1, 0x03, 0xd1, 0x49, 0x13, 0xd6, 0x5e, 0x86, 0x2e, 0x8a, 0xa8, 0x0a, 0x3c, 0x73, 0x87, 0xeb, 0x3f, 0x36, 0x20, 0xd3, 0xe3, 0x2d, 0x46, 0x4c, 0x54, 0x62}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GetConfigMeasurement(tt.args.id, tt.args.configData) + got, _, _, err := GetConfigMeasurement(tt.args.id, tt.args.configData) if (err != nil) != tt.wantErr { t.Errorf("GetConfigMeasurement() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetConfigMeasurement() = %v, want %v", got, tt.want) + t.Errorf("GetConfigMeasurement() = %v, want %v", hex.EncodeToString(got), hex.EncodeToString(tt.want)) + } + }) + } +} + +func Test_normalize(t *testing.T) { + type args struct { + id string + configData []byte + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "normalize_Success", + args: args{ + id: "8d6f55b059bca4d130b60e29807c5a5f63014b8673ad7cee69b7fb84b03b6443", + configData: dockerConfig, + }, + want: normalizedDockerConfig, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, _, err := normalize(tt.args.id, tt.args.configData) + if (err != nil) != tt.wantErr { + t.Errorf("normalize() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("\nGOT = %v,\nWANT= %v", string(got), string(tt.want)) } }) } } var ( + normalizedDockerConfig = []byte(`{"hooks":{"prestart":[{"args":["libnetwork-setkey","-exec-root=/var/run/docker","",""],"path":"/usr/bin/dockerd"}]},"hostname":"","linux":{"cgroupsPath":"system.slice:docker:","maskedPaths":["/proc/asound","/sys/devices/virtual/powercap"],"namespaces":[{"type":"mount"}],"readonlyPaths":["/proc/bus","/proc/sysrq-trigger"],"resources":{"blockIO":{},"devices":[{"access":"rwm","allow":false}]},"seccomp":{"architectures":["SCMP_ARCH_X86_64","SCMP_ARCH_X32"],"defaultAction":"SCMP_ACT_ERRNO","defaultErrnoRet":1,"syscalls":[{"action":"SCMP_ACT_ALLOW","names":["chroot"]}]},"sysctl":{"net.ipv4.ip_unprivileged_port_start":"0","net.ipv4.ping_group_range":"0 2147483647"}},"mounts":[{"destination":"/etc/resolv.conf","options":["rbind","rprivate"],"source":"/var/lib/docker/containers//resolv.conf","type":"bind"}],"ociVersion":"1.2.0","process":{"args":["/bin/sh"],"capabilities":{"permitted":["CAP_CHOWN","CAP_DAC_OVERRIDE"]},"cwd":"/","env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HOSTNAME=","TERM=xterm"],"user":{"gid":0,"uid":0}}}`) + + dockerConfig = []byte(` +{ + "ociVersion": "1.2.0", + "process": { + "terminal": true, + "consoleSize": { + "height": 31, + "width": 127 + }, + "user": { + "gid": 0, + "uid": 0 + }, + "args": [ + "/bin/sh" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=8d6f55b059bc", + "TERM=xterm" + ], + "cwd": "/", + "capabilities": { + "permitted": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE" + ] + } + }, + "root": { + "path": "/var/lib/docker/overlay2/985be77ec6cff5fdb13520443dd9a67e9e9e4d2a70d189009d7dda3e7458b9c1/merged" + }, + "hostname": "8d6f55b059bc", + "mounts": [ + { + "destination": "/etc/resolv.conf", + "type": "bind", + "source": "/var/lib/docker/containers/8d6f55b059bca4d130b60e29807c5a5f63014b8673ad7cee69b7fb84b03b6443/resolv.conf", + "options": [ + "rbind", + "rprivate" + ] + } + ], + "hooks": { + "prestart": [ + { + "path": "/usr/bin/dockerd", + "args": [ + "libnetwork-setkey", + "-exec-root=/var/run/docker", + "8d6f55b059bca4d130b60e29807c5a5f63014b8673ad7cee69b7fb84b03b6443", + "99b3e86f39be" + ] + } + ] + }, + "linux": { + "sysctl": { + "net.ipv4.ip_unprivileged_port_start": "0", + "net.ipv4.ping_group_range": "0 2147483647" + }, + "resources": { + "devices": [ + { + "allow": false, + "access": "rwm" + } + ], + "blockIO": {} + }, + "cgroupsPath": "system.slice:docker:8d6f55b059bca4d130b60e29807c5a5f63014b8673ad7cee69b7fb84b03b6443", + "namespaces": [ + { + "type": "mount" + } + ], + "seccomp": { + "defaultAction": "SCMP_ACT_ERRNO", + "defaultErrnoRet": 1, + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X32" + ], + "syscalls": [ + { + "names": [ + "chroot" + ], + "action": "SCMP_ACT_ALLOW" + } + ] + }, + "maskedPaths": [ + "/proc/asound", + "/sys/devices/virtual/powercap" + ], + "readonlyPaths": [ + "/proc/bus", + "/proc/sysrq-trigger" + ] + } +} +`) + configData = []byte{ 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x6f, 0x63, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x2e, 0x30, 0x2e, 0x32, diff --git a/testtool/coap.go b/testtool/coap.go index a6d43f0a..00674cff 100644 --- a/testtool/coap.go +++ b/testtool/coap.go @@ -148,7 +148,7 @@ func (a CoapApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/grpc.go b/testtool/grpc.go index 2e69de0e..719e77cf 100644 --- a/testtool/grpc.go +++ b/testtool/grpc.go @@ -158,7 +158,7 @@ func (a GrpcApi) measure(c *config) { log.Fatalf("Failed to read container config: %v", err) } - configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/libapi.go b/testtool/libapi.go index 260fffaf..605f4938 100644 --- a/testtool/libapi.go +++ b/testtool/libapi.go @@ -147,7 +147,7 @@ func (a LibApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/testtool/socket.go b/testtool/socket.go index d56ca0f4..485389f1 100644 --- a/testtool/socket.go +++ b/testtool/socket.go @@ -140,7 +140,7 @@ func (a SocketApi) measure(c *config) { log.Fatalf("Failed to read config: %v", err) } - configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) } diff --git a/tools/measure-bundle/main.go b/tools/measure-bundle/main.go index 2e0ba441..8ca4d9b6 100644 --- a/tools/measure-bundle/main.go +++ b/tools/measure-bundle/main.go @@ -52,7 +52,7 @@ func main() { log.Fatalf("Failed to read container config: %v", err) } - configHash, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) + configHash, _, _, err := m.GetConfigMeasurement("mycontainer", ctrConfig) if err != nil { log.Fatalf("Failed to measure config: %v", err) }