mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
COCOS-326 - Add vTPM support to CoCoS (#376)
* manager, cli and agent vtpm support * rebase and changed atls for vtpm * deleted unused code * changed chekproto.yaml script so it find the manager proto file correctly * fixe manager proto version * fix agent tests * fix server agent test * fix attestation test * fix attestation test gofumpt * created dummy RWC for TPM * fix comment * add default PCR values * rebase main * fix rust ci and missing header * changed embedded attestation to VMPL 2 * fix unused impot * fix pkg test * address attestation type * fix agent attestation test * add prc15 check * fix comments * fix cli tests * add doc * add mock for LeveledQuoteProvider when SEV-SNP device is not found Signed-off-by: Sammy Oina <sammyoina@gmail.com> * fix manager reading attestation policy * refactor PCR value checks and update attestation policy values Signed-off-by: Sammy Oina <sammyoina@gmail.com> * fix tests for sev and grpc --------- Signed-off-by: Sammy Oina <sammyoina@gmail.com> Co-authored-by: Sammy Oina <sammyoina@gmail.com>
This commit is contained in:
committed by
GitHub
parent
fa26573643
commit
67f939fc66
@@ -33,8 +33,8 @@ jobs:
|
||||
|
||||
- name: Set up protoc
|
||||
run: |
|
||||
PROTOC_VERSION=29.0
|
||||
PROTOC_GEN_VERSION=v1.36.0
|
||||
PROTOC_VERSION=29.3
|
||||
PROTOC_GEN_VERSION=v1.36.4
|
||||
PROTOC_GRPC_VERSION=v1.5.1
|
||||
|
||||
# Download and install protoc
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Set up Cocos-AI
|
||||
run: |
|
||||
# Rename .pb.go files to .pb.go.tmp to prevent conflicts
|
||||
for p in $(ls pkg/manager/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
mv $p $p.tmp
|
||||
done
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
make protoc
|
||||
|
||||
# Compare generated Go files with the original ones
|
||||
for p in $(ls pkg/manager/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
if ! cmp -s $p $p.tmp; then
|
||||
echo "Proto file and generated Go file $p are out of sync!"
|
||||
exit 1
|
||||
|
||||
@@ -4,6 +4,10 @@ run:
|
||||
issues:
|
||||
max-issues-per-linter: 10
|
||||
max-same-issues: 10
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- makezero
|
||||
text: "with non-zero initialized length"
|
||||
|
||||
linters-settings:
|
||||
importas:
|
||||
|
||||
+54
-35
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.0
|
||||
// protoc v5.29.0
|
||||
// protoc-gen-go v1.36.4
|
||||
// protoc v5.29.3
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -281,7 +282,9 @@ func (x *ResultResponse) GetFile() []byte {
|
||||
|
||||
type AttestationRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be of length 64.
|
||||
TeeNonce []byte `protobuf:"bytes,1,opt,name=teeNonce,proto3" json:"teeNonce,omitempty"` // Should be less or equal 64 bytes.
|
||||
VtpmNonce []byte `protobuf:"bytes,2,opt,name=vtpmNonce,proto3" json:"vtpmNonce,omitempty"` // Should be less or equal 32 bytes.
|
||||
Type int32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -316,13 +319,27 @@ func (*AttestationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetReportData() []byte {
|
||||
func (x *AttestationRequest) GetTeeNonce() []byte {
|
||||
if x != nil {
|
||||
return x.ReportData
|
||||
return x.TeeNonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetVtpmNonce() []byte {
|
||||
if x != nil {
|
||||
return x.VtpmNonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetType() int32 {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AttestationResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
@@ -369,7 +386,7 @@ func (x *AttestationResponse) GetFile() []byte {
|
||||
|
||||
var File_agent_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_agent_proto_rawDesc = []byte{
|
||||
var file_agent_agent_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x0b, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67,
|
||||
@@ -386,40 +403,43 @@ var file_agent_agent_proto_rawDesc = []byte{
|
||||
0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x29,
|
||||
0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12,
|
||||
0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73,
|
||||
0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
|
||||
0x48, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x76, 0x74, 0x70,
|
||||
0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x76, 0x74,
|
||||
0x70, 0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x13, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12,
|
||||
0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74,
|
||||
0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28,
|
||||
0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b,
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
file_agent_agent_proto_rawDescOnce sync.Once
|
||||
file_agent_agent_proto_rawDescData = file_agent_agent_proto_rawDesc
|
||||
file_agent_agent_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_agent_agent_proto_rawDescGZIP() []byte {
|
||||
file_agent_agent_proto_rawDescOnce.Do(func() {
|
||||
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_agent_proto_rawDescData)
|
||||
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)))
|
||||
})
|
||||
return file_agent_agent_proto_rawDescData
|
||||
}
|
||||
@@ -460,7 +480,7 @@ func file_agent_agent_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_agent_agent_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
@@ -471,7 +491,6 @@ func file_agent_agent_proto_init() {
|
||||
MessageInfos: file_agent_agent_proto_msgTypes,
|
||||
}.Build()
|
||||
File_agent_agent_proto = out.File
|
||||
file_agent_agent_proto_rawDesc = nil
|
||||
file_agent_agent_proto_goTypes = nil
|
||||
file_agent_agent_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+3
-1
@@ -36,7 +36,9 @@ message ResultResponse {
|
||||
}
|
||||
|
||||
message AttestationRequest {
|
||||
bytes report_data = 1; // Should be of length 64.
|
||||
bytes teeNonce = 1; // Should be less or equal 64 bytes.
|
||||
bytes vtpmNonce = 2; // Should be less or equal 32 bytes.
|
||||
int32 type = 3;
|
||||
}
|
||||
|
||||
message AttestationResponse {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.0
|
||||
// - protoc v5.29.3
|
||||
// source: agent/agent.proto
|
||||
|
||||
package agent
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
@@ -70,7 +71,7 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
if err := req.validate(); err != nil {
|
||||
return attestationRes{}, err
|
||||
}
|
||||
file, err := svc.Attestation(ctx, req.ReportData)
|
||||
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, config.AttestationType(req.AttType))
|
||||
if err != nil {
|
||||
return attestationRes{}, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -141,11 +142,11 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
},
|
||||
{
|
||||
name: "Service Error",
|
||||
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
@@ -153,9 +154,9 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.name == svcErr {
|
||||
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, errors.New("")).Once()
|
||||
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, errors.New("")).Once()
|
||||
} else {
|
||||
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, nil).Once()
|
||||
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, nil).Once()
|
||||
}
|
||||
endpoint := attestationEndpoint(svc)
|
||||
res, err := endpoint(context.Background(), tt.req)
|
||||
|
||||
@@ -4,6 +4,10 @@ package grpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
type algoReq struct {
|
||||
@@ -38,9 +42,16 @@ func (req resultReq) validate() error {
|
||||
}
|
||||
|
||||
type attestationReq struct {
|
||||
ReportData [64]byte
|
||||
TeeNonce [quoteprovider.Nonce]byte
|
||||
VtpmNonce [vtpm.Nonce]byte
|
||||
AttType config.AttestationType
|
||||
}
|
||||
|
||||
func (req attestationReq) validate() error {
|
||||
switch req.AttType {
|
||||
case config.SNP, config.VTPM, config.SNPvTPM:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid attestation type in attestation request")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -21,6 +24,11 @@ const (
|
||||
FileSizeKey = "file-size"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTEENonceLength = errors.New("malformed report data, expect less or equal to 64 bytes")
|
||||
ErrVTpmNonceLength = errors.New("malformed vTPM nonce, expect less or equal to 32 bytes")
|
||||
)
|
||||
|
||||
var _ agent.AgentServiceServer = (*grpcServer)(nil)
|
||||
|
||||
type grpcServer struct {
|
||||
@@ -96,10 +104,20 @@ func encodeResultResponse(_ context.Context, response interface{}) (interface{},
|
||||
|
||||
func decodeAttestationRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.AttestationRequest)
|
||||
if len(req.ReportData) != agent.ReportDataSize {
|
||||
return nil, errors.New("malformed report data, expect 64 bytes")
|
||||
var reportData [quoteprovider.Nonce]byte
|
||||
var nonce [vtpm.Nonce]byte
|
||||
|
||||
if len(req.TeeNonce) > quoteprovider.Nonce {
|
||||
return nil, ErrTEENonceLength
|
||||
}
|
||||
return attestationReq{ReportData: [agent.ReportDataSize]byte(req.ReportData)}, nil
|
||||
|
||||
if len(req.VtpmNonce) > vtpm.Nonce {
|
||||
return nil, ErrVTpmNonceLength
|
||||
}
|
||||
|
||||
copy(reportData[:], req.TeeNonce)
|
||||
copy(nonce[:], req.VtpmNonce)
|
||||
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: config.AttestationType(req.Type)}, nil
|
||||
}
|
||||
|
||||
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -151,10 +154,12 @@ func TestAttestation(t *testing.T) {
|
||||
mockStream := &MockAgentService_AttestationServer{ctx: context.Background()}
|
||||
mockStream.On("Send", mock.AnythingOfType("*agent.AttestationResponse")).Return(nil)
|
||||
|
||||
reportData := [agent.ReportDataSize]byte{}
|
||||
mockService.On("Attestation", mock.Anything, reportData).Return([]byte("attestation data"), nil)
|
||||
reportData := [quoteprovider.Nonce]byte{}
|
||||
vtpmNonce := [vtpm.Nonce]byte{}
|
||||
attestationType := config.SNP
|
||||
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
|
||||
|
||||
err := server.Attestation(&agent.AttestationRequest{ReportData: reportData[:]}, mockStream)
|
||||
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:]}, mockStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockService.AssertExpectations(t)
|
||||
@@ -199,11 +204,11 @@ func TestEncodeResultResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeAttestationRequest(t *testing.T) {
|
||||
reportData := [agent.ReportDataSize]byte{}
|
||||
req := &agent.AttestationRequest{ReportData: reportData[:]}
|
||||
nonce := [quoteprovider.Nonce]byte{}
|
||||
req := &agent.AttestationRequest{TeeNonce: nonce[:]}
|
||||
decoded, err := decodeAttestationRequest(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, attestationReq{ReportData: reportData}, decoded)
|
||||
assert.Equal(t, attestationReq{TeeNonce: nonce}, decoded)
|
||||
}
|
||||
|
||||
func TestEncodeAttestationResponse(t *testing.T) {
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
var _ agent.Service = (*loggingMiddleware)(nil)
|
||||
@@ -103,7 +106,7 @@ func (lm *loggingMiddleware) Result(ctx context.Context) (response []byte, err e
|
||||
return lm.svc.Result(ctx)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) (response []byte, err error) {
|
||||
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) (response []byte, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method Attestation took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
@@ -113,5 +116,5 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Attestation(ctx, reportData)
|
||||
return lm.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
var _ agent.Service = (*metricsMiddleware)(nil)
|
||||
@@ -89,11 +92,11 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
|
||||
return ms.svc.Result(ctx)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) ([]byte, error) {
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "attestation").Add(1)
|
||||
ms.latency.With("method", "attestation").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Attestation(ctx, reportData)
|
||||
return ms.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error
|
||||
return err
|
||||
}
|
||||
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
as.logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
return err
|
||||
|
||||
+19
-16
@@ -6,9 +6,10 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
agent "github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
@@ -73,9 +74,9 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
|
||||
return _c
|
||||
}
|
||||
|
||||
// Attestation provides a mock function with given fields: ctx, reportData
|
||||
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte, error) {
|
||||
ret := _m.Called(ctx, reportData)
|
||||
// Attestation provides a mock function with given fields: ctx, reportData, nonce, attType
|
||||
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType) ([]byte, error) {
|
||||
ret := _m.Called(ctx, reportData, nonce, attType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attestation")
|
||||
@@ -83,19 +84,19 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) ([]byte, error)); ok {
|
||||
return rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)); ok {
|
||||
return rf(ctx, reportData, nonce, attType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) []byte); ok {
|
||||
r0 = rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) []byte); ok {
|
||||
r0 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte) error); ok {
|
||||
r1 = rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, config.AttestationType) error); ok {
|
||||
r1 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -111,13 +112,15 @@ type Service_Attestation_Call struct {
|
||||
// Attestation is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - reportData [64]byte
|
||||
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}) *Service_Attestation_Call {
|
||||
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData)}
|
||||
// - nonce [32]byte
|
||||
// - attType config.AttestationType
|
||||
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}, nonce interface{}, attType interface{}) *Service_Attestation_Call {
|
||||
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, nonce, attType)}
|
||||
}
|
||||
|
||||
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType)) *Service_Attestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([64]byte))
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(config.AttestationType))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -127,7 +130,7 @@ func (_c *Service_Attestation_Call) Return(_a0 []byte, _a1 error) *Service_Attes
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte) ([]byte, error)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)) *Service_Attestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
+29
-8
@@ -23,6 +23,9 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/agent/statemachine"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -69,8 +72,6 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// ReportDataSize is the size of the report data expected by the attestation service.
|
||||
ReportDataSize = 64
|
||||
algoFilePermission = 0o700
|
||||
)
|
||||
|
||||
@@ -99,6 +100,8 @@ var (
|
||||
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
|
||||
// ErrAttestationFailed attestation failed.
|
||||
ErrAttestationFailed = errors.New("failed to get raw quote")
|
||||
// ErrAttType indicates that the attestation type that is requested does not exist or is not supported.
|
||||
ErrAttestationType = errors.New("attestation type does not exist or is not supported")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
@@ -109,7 +112,7 @@ type Service interface {
|
||||
Algo(ctx context.Context, algorithm Algorithm) error
|
||||
Data(ctx context.Context, dataset Dataset) error
|
||||
Result(ctx context.Context) ([]byte, error)
|
||||
Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error)
|
||||
Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error)
|
||||
State() string
|
||||
}
|
||||
|
||||
@@ -121,16 +124,17 @@ type agentService struct {
|
||||
sm statemachine.StateMachine // Manages the state transitions of the agent service.
|
||||
runError error // Stores any error encountered during the computation run.
|
||||
eventSvc events.Service // Service for publishing events related to computation.
|
||||
quoteProvider client.QuoteProvider // Provider for generating attestation quotes.
|
||||
quoteProvider client.LeveledQuoteProvider // Provider for generating attestation quotes.
|
||||
logger *slog.Logger // Logger for the agent service.
|
||||
resultsConsumed bool // Indicates if the results have been consumed.
|
||||
cancel context.CancelFunc // Cancels the computation context.
|
||||
vmpl int // VMPL at which the Agent is running.
|
||||
}
|
||||
|
||||
var _ Service = (*agentService)(nil)
|
||||
|
||||
// New instantiates the agent service implementation.
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.QuoteProvider) Service {
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.LeveledQuoteProvider, vmlp int) Service {
|
||||
sm := statemachine.NewStateMachine(Idle)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
svc := &agentService{
|
||||
@@ -139,6 +143,7 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
|
||||
quoteProvider: quoteProvider,
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
vmpl: vmlp,
|
||||
}
|
||||
|
||||
transitions := []statemachine.Transition{
|
||||
@@ -397,13 +402,29 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
return as.result, as.runError
|
||||
}
|
||||
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error) {
|
||||
rawQuote, err := as.quoteProvider.GetRawQuote(reportData)
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
switch attType {
|
||||
case config.SNP:
|
||||
rawQuote, err := as.quoteProvider.GetRawQuoteAtLevel(reportData, uint(as.vmpl))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return rawQuote, nil
|
||||
case config.VTPM:
|
||||
vTPMQuote, err := vtpm.Attest(reportData[:], nonce[:], false)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
case config.SNPvTPM:
|
||||
vTPMQuote, err := vtpm.Attest(reportData[:], nonce[:], true)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
default:
|
||||
return []byte{}, ErrAttestationType
|
||||
}
|
||||
}
|
||||
|
||||
func (as *agentService) runComputation(state statemachine.State) {
|
||||
|
||||
+17
-13
@@ -22,6 +22,7 @@ import (
|
||||
smmocks "github.com/ultravioletrs/cocos/agent/statemachine/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ var (
|
||||
const datasetFile = "iris.csv"
|
||||
|
||||
func TestAlgo(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
@@ -120,7 +121,7 @@ func TestAlgo(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -139,7 +140,7 @@ func TestAlgo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestData(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
@@ -215,7 +216,7 @@ func TestData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -240,7 +241,7 @@ func TestData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
@@ -323,23 +324,26 @@ func TestResult(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttestation(t *testing.T) {
|
||||
qp := new(mocks2.QuoteProvider)
|
||||
qp := new(mocks2.LeveledQuoteProvider)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
reportData [ReportDataSize]byte
|
||||
reportData [quoteprovider.Nonce]byte
|
||||
nonce [vtpm.Nonce]byte
|
||||
rawQuote []uint8
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Test attestation successful",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: make([]uint8, 0),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Test attestation failed",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: nil,
|
||||
err: ErrAttestationFailed,
|
||||
},
|
||||
@@ -355,22 +359,22 @@ func TestAttestation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
getQuote := qp.On("GetRawQuote", mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
getQuote := qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
if tc.err != ErrAttestationFailed {
|
||||
getQuote = qp.On("GetRawQuote", mock.Anything).Return(tc.reportData, nil)
|
||||
getQuote = qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.nonce, nil)
|
||||
}
|
||||
defer getQuote.Unset()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
_, err := svc.Attestation(ctx, tc.reportData)
|
||||
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, 0)
|
||||
assert.True(t, errors.Contains(err, tc.err), "expected %v, got %v", tc.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateReportData() [ReportDataSize]byte {
|
||||
bytes := make([]byte, ReportDataSize)
|
||||
func generateReportData() [quoteprovider.Nonce]byte {
|
||||
bytes := make([]byte, quoteprovider.Nonce)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate random bytes: %v", err)
|
||||
|
||||
Binary file not shown.
+89
-17
@@ -25,8 +25,9 @@ import (
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -109,6 +110,9 @@ const (
|
||||
}
|
||||
}
|
||||
`
|
||||
SNP = "snp"
|
||||
VTPM = "vtpm"
|
||||
SNPvTPM = "snp-vtpm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -130,12 +134,13 @@ var (
|
||||
empty32 = [size32]byte{}
|
||||
empty64 = [size64]byte{}
|
||||
defaultReportIdMa = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
getJsonAttestation bool
|
||||
errReportSize = errors.New("attestation contents too small")
|
||||
ErrBadAttestation = errors.New("attestation file is corrupted or in wrong format")
|
||||
output string
|
||||
nonce []byte
|
||||
format string
|
||||
teeNonce []byte
|
||||
getTextProtoAttestation bool
|
||||
)
|
||||
|
||||
var errEmptyFile = errors.New("input file is empty")
|
||||
@@ -179,8 +184,12 @@ func (cli *CLI) NewAttestationCmd() *cobra.Command {
|
||||
func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Retrieve attestation information from agent. Report data expected in hex enoded string of length 64 bytes.",
|
||||
Example: "get <report_data>",
|
||||
Short: "Retrieve attestation information from agent. The argument of the command must be the type of the report (snp or vtpm or snp-vtpm).",
|
||||
ValidArgs: []cobra.Completion{SNP, VTPM, SNPvTPM},
|
||||
Example: fmt.Sprintf(`Based on attestation report type:
|
||||
get %s --tee <512 bit hex value>
|
||||
get %s --vtpm <256 bit hex value>
|
||||
get %s --tee <512 bit hex value> --vtpm <256 bit hex value>`, SNP, VTPM, SNPvTPM),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
@@ -188,21 +197,61 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Getting attestation")
|
||||
|
||||
reportData, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding report data: %v ❌ ", err)
|
||||
if err := cobra.OnlyValidArgs(cmd, args); err != nil {
|
||||
printError(cmd, "Bad attestation type: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if len(reportData) != agent.ReportDataSize {
|
||||
msg := color.New(color.FgRed).Sprintf("report data must be a hex encoded string of length %d bytes ❌ ", agent.ReportDataSize)
|
||||
|
||||
attestationType := args[0]
|
||||
|
||||
attType := config.SNP
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
cmd.Println("Fetching SEV-SNP attestation report")
|
||||
case VTPM:
|
||||
cmd.Println("Fetching vTPM report")
|
||||
attType = config.VTPM
|
||||
case SNPvTPM:
|
||||
cmd.Println("Fetching SEV-SNP and vTPM report")
|
||||
attType = config.SNPvTPM
|
||||
}
|
||||
|
||||
if (attType == config.VTPM || attType == config.SNPvTPM) && len(nonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("vTPM nonce must be defined for vTPM attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if (attType == config.SNP || attType == config.SNPvTPM) && len(teeNonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("TEE nonce must be defined for SEV-SNP attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var fixedReportData [quoteprovider.Nonce]byte
|
||||
if attType != config.VTPM {
|
||||
if len(teeNonce) > quoteprovider.Nonce {
|
||||
msg := color.New(color.FgRed).Sprintf("nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", quoteprovider.Nonce)
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
copy(fixedReportData[:], teeNonce)
|
||||
}
|
||||
|
||||
var fixedVtpmNonceByte [vtpm.Nonce]byte
|
||||
if attType != config.SNP {
|
||||
if len(nonce) > vtpm.Nonce {
|
||||
msg := color.New(color.FgRed).Sprintf("vTPM nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", vtpm.Nonce)
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
copy(fixedVtpmNonceByte[:], nonce)
|
||||
}
|
||||
|
||||
filename := attestationFilePath
|
||||
if getJsonAttestation {
|
||||
if getTextProtoAttestation {
|
||||
filename = attestationJson
|
||||
}
|
||||
|
||||
@@ -212,7 +261,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cli.agentSDK.Attestation(cmd.Context(), [agent.ReportDataSize]byte(reportData), attestationFile); err != nil {
|
||||
if err := cli.agentSDK.Attestation(cmd.Context(), fixedReportData, fixedVtpmNonceByte, int(attType), attestationFile); err != nil {
|
||||
printError(cmd, "Failed to get attestation due to error: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
@@ -222,16 +271,32 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
if getJsonAttestation {
|
||||
if getTextProtoAttestation {
|
||||
result, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
result, err = attesationToJSON(result)
|
||||
case VTPM, SNPvTPM:
|
||||
marshalOptions := prototext.MarshalOptions{
|
||||
Multiline: true,
|
||||
EmitASCII: true,
|
||||
}
|
||||
var attvTPM tpmAttest.Attestation
|
||||
err = proto.Unmarshal(result, &attvTPM)
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting attestation to json: %v ❌ ", err)
|
||||
printError(cmd, "failed to unmarshal the attestation report: %v ❌ ", ErrBadAttestation)
|
||||
}
|
||||
|
||||
result = []byte(marshalOptions.Format(&attvTPM))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting attestation to textproto: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -245,7 +310,9 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&getJsonAttestation, "json", "j", false, "Get attestation in json format")
|
||||
cmd.Flags().BoolVarP(&getTextProtoAttestation, "textproto", "p", false, "Get attestation in textproto format")
|
||||
cmd.Flags().BytesHexVarP(&teeNonce, "tee", "e", []byte{}, "Define the nonce for the SNP attestation report (must be used with attestation type snp and snp-vtpm)")
|
||||
cmd.Flags().BytesHexVarP(&nonce, "vtpm", "t", []byte{}, "Define the nonce for the vTPM attestation report (must be used with attestation type vtpm and snp-vtpm)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -585,7 +652,12 @@ func sevsnpverify(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("error validating input: %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(attestation, &cfg); err != nil {
|
||||
attestationPB, err := abi.ReportCertsToProto(attestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert attestation bytes to struct %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(attestationPB, &cfg); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
cmd.Println("Attestation validation and verification is successful!")
|
||||
|
||||
+56
-23
@@ -18,7 +18,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"github.com/ultravioletrs/cocos/pkg/sdk/mocks"
|
||||
)
|
||||
|
||||
@@ -35,8 +36,8 @@ func TestNewAttestationCmd(t *testing.T) {
|
||||
|
||||
cmd.SetOutput(&buf)
|
||||
|
||||
reportData := bytes.Repeat([]byte{0x01}, agent.ReportDataSize)
|
||||
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(reportData), mock.Anything).Return(nil)
|
||||
reportData := bytes.Repeat([]byte{0x01}, quoteprovider.Nonce)
|
||||
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(reportData), mock.Anything).Return(nil)
|
||||
|
||||
cmd.SetArgs([]string{hex.EncodeToString(reportData)})
|
||||
err := cmd.Execute()
|
||||
@@ -47,6 +48,10 @@ func TestNewAttestationCmd(t *testing.T) {
|
||||
func TestNewGetAttestationCmd(t *testing.T) {
|
||||
validattestation, err := os.ReadFile("../attestation.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
teeNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
|
||||
vtpmNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -56,57 +61,85 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
expectedOut string
|
||||
}{
|
||||
{
|
||||
name: "successful attestation retrieval",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
name: "successful SNP attestation retrieval",
|
||||
args: []string{"snp", "--tee", teeNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "invalid report data (decoding error)",
|
||||
args: []string{"invalid"},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Error decoding report data",
|
||||
name: "successful vTPM attestation retrieval",
|
||||
args: []string{"vtpm", "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "successful SNP-vTPM attestation retrieval",
|
||||
args: []string{"snp-vtpm", "--tee", teeNonce, "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "missing vTPM nonce",
|
||||
args: []string{"snp-vtpm", "--tee", teeNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "vTPM nonce must be defined for vTPM attestation",
|
||||
},
|
||||
{
|
||||
name: "missing TEE nonce",
|
||||
args: []string{"snp-vtpm", "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "TEE nonce must be defined for SEV-SNP attestation",
|
||||
},
|
||||
{
|
||||
name: "invalid report data size",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, 32))},
|
||||
args: []string{"snp", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 65))},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "report data must be a hex encoded string of length 64 bytes",
|
||||
expectedErr: "nonce must be a hex encoded string of length lesser or equal 64 bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid report data hex",
|
||||
name: "invalid vTPM data size",
|
||||
args: []string{"vtpm", "-t", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "vTPM nonce must be a hex encoded string of length lesser or equal 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid arguments",
|
||||
args: []string{"invalid"},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Error decoding report data",
|
||||
expectedErr: "Bad attestation type: invalid argument ",
|
||||
},
|
||||
{
|
||||
name: "failed to get attestation",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Failed to get attestation due to error",
|
||||
},
|
||||
{
|
||||
name: "JSON report error",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
|
||||
name: "Textproto report error",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedErr: "Error converting attestation to json",
|
||||
expectedErr: "Error converting attestation to textproto",
|
||||
},
|
||||
{
|
||||
name: "successful JSON report",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
|
||||
name: "successful Textproto report",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
mockResponse: validattestation,
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "connection error",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("failed to connect to agent"),
|
||||
expectedErr: "Failed to connect to agent",
|
||||
@@ -128,8 +161,8 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
cmd.SetOutput(&buf)
|
||||
|
||||
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
|
||||
_, err := args.Get(2).(*os.File).Write(tc.mockResponse)
|
||||
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce)), [vtpm.Nonce]byte(bytes.Repeat([]byte{0x00}, vtpm.Nonce)), mock.Anything, mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
|
||||
_, err := args.Get(4).(*os.File).Write(tc.mockResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,14 +27,14 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
|
||||
Example: "ca-bundle <path_to_platform_info_json>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
attestationConfiguration := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
err := grpc.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
attestationConfiguration := config.Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err := config.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
if err != nil {
|
||||
printError(cmd, "Error while reading manifest: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
product := attestationConfiguration.RootOfTrust.ProductLine
|
||||
product := attestationConfiguration.SnpCheck.RootOfTrust.ProductLine
|
||||
|
||||
getter := trust.DefaultHTTPSGetter()
|
||||
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
|
||||
|
||||
+32
-4
@@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/api"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
@@ -24,6 +26,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -39,6 +42,7 @@ const (
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"AGENT_VMPL" envDefault:"2"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -72,12 +76,21 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
var qp client.LeveledQuoteProvider
|
||||
|
||||
if !sevGuesDeviceExists() {
|
||||
logger.Info("SEV-SNP device not found")
|
||||
qpMock := new(mocks.LeveledQuoteProvider)
|
||||
qpMock.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return([]uint8{}, errors.New("SEV-SNP device not found"))
|
||||
qp = qpMock
|
||||
} else {
|
||||
qp, err = quoteprovider.GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cvmGrpcConfig := pkggrpc.CVMClientConfig{}
|
||||
if err := env.ParseWithOptions(&cvmGrpcConfig, env.Options{Prefix: envPrefixCVMGRPC}); err != nil {
|
||||
@@ -111,7 +124,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, qp)
|
||||
if cfg.Vmpl < 0 || cfg.Vmpl > 3 {
|
||||
logger.Error("vmpl level must be in a range [0, 3]")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, qp, cfg.Vmpl)
|
||||
|
||||
if err := os.MkdirAll(storageDir, 0o755); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create storage directory: %s", err))
|
||||
@@ -150,8 +169,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.QuoteProvider) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, qp)
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.LeveledQuoteProvider, vmpl int) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, qp, vmpl)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "api")
|
||||
@@ -159,3 +178,12 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func sevGuesDeviceExists() bool {
|
||||
d, err := client.OpenDevice()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
d.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,13 +49,13 @@ MANAGER_QEMU_BIN_PATH=qemu-system-x86_64
|
||||
MANAGER_QEMU_USE_SUDO=true
|
||||
MANAGER_QEMU_ENABLE_SEV=false
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=false
|
||||
MANAGER_QEMU_IGVM_FILE=/etc/cocos/coconut-qemu.igvm
|
||||
MANAGER_QEMU_ENABLE_KVM=true
|
||||
MANAGER_QEMU_MACHINE=q35
|
||||
MANAGER_QEMU_CPU=EPYC
|
||||
MANAGER_QEMU_SMP_COUNT=4
|
||||
MANAGER_QEMU_SMP_MAXCPUS=16
|
||||
MANAGER_QEMU_MEM_ID=ram1
|
||||
MANAGER_QEMU_KERNEL_HASH=false
|
||||
MANAGER_QEMU_NO_GRAPHIC=true
|
||||
MANAGER_QEMU_MONITOR=pty
|
||||
MANAGER_QEMU_HOST_FWD_RANGE=6100-6200
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-sev-guest v0.12.1
|
||||
github.com/google/go-tdx-guest v0.3.1 // indirect
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
@@ -33,8 +33,11 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2 // indirect
|
||||
github.com/google/go-attestation v0.5.0 // indirect
|
||||
github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 // indirect
|
||||
github.com/google/go-attestation v0.5.1 // indirect
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -49,6 +52,7 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
)
|
||||
|
||||
@@ -61,7 +65,7 @@ require (
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/go-configfs-tsm v0.2.2 // indirect
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
|
||||
github.com/google/go-tpm v0.9.3
|
||||
github.com/google/go-tpm-tools v0.4.4
|
||||
github.com/google/logger v1.1.1
|
||||
@@ -89,3 +93,5 @@ require (
|
||||
)
|
||||
|
||||
replace github.com/virtee/sev-snp-measure-go => github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825
|
||||
|
||||
replace github.com/google/go-tpm-tools => github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c
|
||||
|
||||
@@ -188,6 +188,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c h1:gFo8kqRXFoM6ttqMrK+M3xffxco+Yj80kUo3NoMe8LU=
|
||||
github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -333,8 +335,10 @@ github.com/google/certificate-transparency-go v1.1.2-0.20210422104406-9f33727a7a
|
||||
github.com/google/certificate-transparency-go v1.1.2-0.20210512142713-bed466244fa6/go.mod h1:aF2dp7Dh81mY8Y/zpzyXps4fQW5zQbDu2CxfpJB6NkI=
|
||||
github.com/google/certificate-transparency-go v1.1.2 h1:4hE0GEId6NAW28dFpC+LrRGwQX5dtmXQGDbg8+/MZOM=
|
||||
github.com/google/certificate-transparency-go v1.1.2/go.mod h1:3OL+HKDqHPUfdKrHVQxO6T8nDLO0HF7LRTlkIWXaWvQ=
|
||||
github.com/google/go-attestation v0.5.0 h1:jXtAWT2sw2Yu8mYU0BC7FDidR+ngxFPSE+pl6IUu3/0=
|
||||
github.com/google/go-attestation v0.5.0/go.mod h1:0Tik9y3rzV649Jcr7evbljQHQAsIlJucyqQjYDBqktU=
|
||||
github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 h1:1ijRI0+jsZCl3CqeJG3Cib6w+wYCBlD/rWRo5a+ZME4=
|
||||
github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786/go.mod h1:Jvv9i6JF1t7sDVW09zP2x+9vN3lcujtih2Zb/lVXaLs=
|
||||
github.com/google/go-attestation v0.5.1 h1:jqtOrLk5MNdliTKjPbIPrAaRKJaKW+0LIU2n/brJYms=
|
||||
github.com/google/go-attestation v0.5.1/go.mod h1:KqGatdUhg5kPFkokyzSBDxwSCFyRgIgtRkMp6c3lOBQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -349,8 +353,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
|
||||
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU=
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba h1:05m5+kgZjxYUZrx3bZfkKHl6wkch+Khao6N21rFHInk=
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ=
|
||||
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
|
||||
github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@@ -358,12 +364,10 @@ github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVgg
|
||||
github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
|
||||
github.com/google/go-sev-guest v0.12.1 h1:H4rFYnPIn8HtqEsNTmh56Zxcf9BI9n48ZSYCnpYLYvc=
|
||||
github.com/google/go-sev-guest v0.12.1/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
|
||||
github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw=
|
||||
github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g=
|
||||
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
|
||||
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -898,6 +902,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
@@ -27,7 +27,7 @@ BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
|
||||
# Linux headers same as kernel
|
||||
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y
|
||||
BR2_TOOLCHAIN_HEADERS_LATEST=y
|
||||
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.12-rc6"
|
||||
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7"
|
||||
|
||||
# Kernel
|
||||
BR2_LINUX_KERNEL=y
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -51,7 +52,7 @@ type Server struct {
|
||||
server.BaseServer
|
||||
server *grpc.Server
|
||||
registerService serviceRegister
|
||||
quoteProvider client.QuoteProvider
|
||||
quoteProvider client.LeveledQuoteProvider
|
||||
authSvc auth.Authenticator
|
||||
health *health.Server
|
||||
}
|
||||
@@ -60,7 +61,7 @@ type serviceRegister func(srv *grpc.Server)
|
||||
|
||||
var _ server.Server = (*Server)(nil)
|
||||
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.QuoteProvider, authSvc auth.Authenticator) server.Server {
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.LeveledQuoteProvider, authSvc auth.Authenticator) server.Server {
|
||||
base := config.GetBaseConfig()
|
||||
listenFullAddress := fmt.Sprintf("%s:%s", base.Host, base.Port)
|
||||
return &Server{
|
||||
@@ -301,5 +302,19 @@ func generateCertificatesForATLS() ([]byte, []byte, error) {
|
||||
Bytes: privateKeyBytes,
|
||||
})
|
||||
|
||||
cert, err := x509.ParseCertificate(certDERBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
|
||||
pubKeyDER, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal public key to DER format: %w", err)
|
||||
}
|
||||
|
||||
if err := vtpm.ExtendPCR(vtpm.PCR15, pubKeyDER); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to extend vTPM PCR with public key: %w", err)
|
||||
}
|
||||
|
||||
return certBytes, keyBytes, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
authmocks "github.com/ultravioletrs/cocos/agent/auth/mocks"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
@@ -30,6 +31,28 @@ const bufSize = 1024 * 1024
|
||||
|
||||
var lis *bufconn.Listener
|
||||
|
||||
type DummyRWC struct{}
|
||||
|
||||
// Read fills p with byte(len(p)) and returns len(p).
|
||||
func (l *DummyRWC) Read(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
// Fill each byte in p with the value of n as a byte.
|
||||
for i := range p {
|
||||
p[i] = byte(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write simply returns len(p) indicating that all bytes were written.
|
||||
func (l *DummyRWC) Write(p []byte) (int, error) {
|
||||
// In this simple implementation, we ignore the data.
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (l *DummyRWC) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
lis = bufconn.Listen(bufSize)
|
||||
}
|
||||
@@ -47,7 +70,7 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
}
|
||||
logger := slog.Default()
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
@@ -97,7 +120,7 @@ func TestServerStartWithTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
@@ -144,7 +167,7 @@ func TestServerStartWithmTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
@@ -184,7 +207,7 @@ func TestServerStop(t *testing.T) {
|
||||
}
|
||||
buf := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
@@ -259,6 +282,8 @@ func (b *ThreadSafeBuffer) String() string {
|
||||
}
|
||||
|
||||
func TestServerInitializationAndStartup(t *testing.T) {
|
||||
vtpm.ExternalTPM = &DummyRWC{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config server.AgentConfig
|
||||
@@ -374,7 +399,7 @@ func TestServerInitializationAndStartup(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
|
||||
+4
-16
@@ -44,7 +44,10 @@ The service is configured using the environment variables from the following tab
|
||||
| MANAGER_QEMU_SEV_ID | The ID for the Secure Encrypted Virtualization (SEV) device. | sev0 |
|
||||
| MANAGER_QEMU_SEV_CBITPOS | The position of the C-bit in the physical address. | 51 |
|
||||
| MANAGER_QEMU_SEV_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV. | 1 |
|
||||
| MANAGER_QEMU_ENABLE_HOST_DATA | Enable additional data for the SEV host. | false |
|
||||
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV host. | |
|
||||
| MANAGER_QEMU_IGVM_ID | The ID of the IGVM file. | igvm0 |
|
||||
| MANAGER_QEMU_IGVM_FILE | The file path to the IGVM file. | /root/coconut-qemu.igvm |
|
||||
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
|
||||
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
|
||||
| MANAGER_QEMU_VSOCK_VNC | Whether to enable the virtual socket device for VNC. | 0 |
|
||||
@@ -58,7 +61,6 @@ The service is configured using the environment variables from the following tab
|
||||
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
|
||||
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
|
||||
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
|
||||
| MANAGER_QEMU_KERNEL_HASH | Whether to enable kernel hash verification. | false |
|
||||
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
|
||||
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
|
||||
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
|
||||
@@ -232,21 +234,7 @@ MANAGER_QEMU_ENABLE_SEV=false \
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=true \
|
||||
MANAGER_QEMU_SEV_CBITPOS=51 \
|
||||
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
|
||||
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
To include the kernel hash into the measurement of the attestation report (SEV or SEV-SNP), start manager like this
|
||||
|
||||
```sh
|
||||
MANAGER_GRPC_URL=localhost:7001 \
|
||||
MANAGER_LOG_LEVEL=debug \
|
||||
MANAGER_QEMU_ENABLE_SEV=false \
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=true \
|
||||
MANAGER_QEMU_SEV_CBITPOS=51 \
|
||||
MANAGER_QEMU_KERNEL_HASH=true \
|
||||
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
|
||||
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
|
||||
MANAGER_QEMU_IGVM_FILE=<path to IGVM file> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
attestationPolicy.Policy.Measurement = measurement
|
||||
}
|
||||
|
||||
if vmi.Config.HostData != "" {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.HostData)
|
||||
if vmi.Config.SevConfig.EnableHostData {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SevConfig.HostData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
vmConfig: qemu.VMInfo{
|
||||
Config: qemu.Config{
|
||||
EnableSEV: true,
|
||||
EnableSEVSNP: false,
|
||||
SMPCount: 2,
|
||||
CPU: "EPYC",
|
||||
OVMFCodeConfig: qemu.OVMFCodeConfig{
|
||||
@@ -68,23 +69,6 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
},
|
||||
expectedError: "open /path/to/OVMF_CODE.fd: no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "Valid SEV-SNP configuration",
|
||||
computationId: "sev-snp-computation",
|
||||
binaryBehavior: "success",
|
||||
vmConfig: qemu.VMInfo{
|
||||
Config: qemu.Config{
|
||||
EnableSEVSNP: true,
|
||||
SMPCount: 4,
|
||||
CPU: "EPYC-v2",
|
||||
OVMFCodeConfig: qemu.OVMFCodeConfig{
|
||||
File: "/path/to/OVMF_CODE_SNP.fd",
|
||||
},
|
||||
},
|
||||
LaunchTCB: 0,
|
||||
},
|
||||
expectedError: "open /path/to/OVMF_CODE_SNP.fd: no such file or director",
|
||||
},
|
||||
{
|
||||
name: "Invalid computation ID",
|
||||
computationId: "non-existent",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.0
|
||||
// protoc v5.29.0
|
||||
// protoc-gen-go v1.36.4
|
||||
// protoc v5.29.3
|
||||
// source: manager/manager.proto
|
||||
|
||||
package manager
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -422,7 +423,7 @@ func (x *SVMInfoReq) GetId() string {
|
||||
|
||||
var File_manager_manager_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_manager_manager_proto_rawDesc = []byte{
|
||||
var file_manager_manager_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
@@ -488,16 +489,16 @@ var file_manager_manager_proto_rawDesc = []byte{
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
|
||||
0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
})
|
||||
|
||||
var (
|
||||
file_manager_manager_proto_rawDescOnce sync.Once
|
||||
file_manager_manager_proto_rawDescData = file_manager_manager_proto_rawDesc
|
||||
file_manager_manager_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_manager_manager_proto_rawDescGZIP() []byte {
|
||||
file_manager_manager_proto_rawDescOnce.Do(func() {
|
||||
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_manager_manager_proto_rawDescData)
|
||||
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)))
|
||||
})
|
||||
return file_manager_manager_proto_rawDescData
|
||||
}
|
||||
@@ -538,7 +539,7 @@ func file_manager_manager_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_manager_manager_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
@@ -549,7 +550,6 @@ func file_manager_manager_proto_init() {
|
||||
MessageInfos: file_manager_manager_proto_msgTypes,
|
||||
}.Build()
|
||||
File_manager_manager_proto = out.File
|
||||
file_manager_manager_proto_rawDesc = nil
|
||||
file_manager_manager_proto_goTypes = nil
|
||||
file_manager_manager_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.0
|
||||
// - protoc v5.29.3
|
||||
// source: manager/manager.proto
|
||||
|
||||
package manager
|
||||
|
||||
+19
-14
@@ -56,9 +56,15 @@ type SevConfig struct {
|
||||
ID string `env:"SEV_ID" envDefault:"sev0"`
|
||||
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
|
||||
ReducedPhysBits int `env:"SEV_REDUCED_PHYS_BITS" envDefault:"1"`
|
||||
EnableHostData bool `env:"ENABLE_HOST_DATA" envDefault:"false"`
|
||||
HostData string `env:"HOST_DATA" envDefault:""`
|
||||
}
|
||||
|
||||
type IGVMConfig struct {
|
||||
ID string `env:"IGVM_ID" envDefault:"igvm0"`
|
||||
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
|
||||
}
|
||||
|
||||
type VSockConfig struct {
|
||||
ID string `env:"VSOCK_ID" envDefault:"vhost-vsock-pci0"`
|
||||
GuestCID int `env:"VSOCK_GUEST_CID" envDefault:"3"`
|
||||
@@ -80,9 +86,6 @@ type Config struct {
|
||||
MemID string `env:"MEM_ID" envDefault:"ram1"`
|
||||
MemoryConfig
|
||||
|
||||
// Kernel hash
|
||||
KernelHash bool `env:"KERNEL_HASH" envDefault:"false"`
|
||||
|
||||
// OVMF
|
||||
OVMFCodeConfig
|
||||
OVMFVarsConfig
|
||||
@@ -100,6 +103,9 @@ type Config struct {
|
||||
// SEV
|
||||
SevConfig
|
||||
|
||||
// vTPM
|
||||
IGVMConfig
|
||||
|
||||
// display
|
||||
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
|
||||
Monitor string `env:"MONITOR" envDefault:"pty"`
|
||||
@@ -173,40 +179,39 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
// SEV
|
||||
if config.EnableSEV || config.EnableSEVSNP {
|
||||
sevType := "sev-guest"
|
||||
kernelHash := ""
|
||||
hostData := ""
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,igvm-cfg=%s",
|
||||
config.SevConfig.ID,
|
||||
config.MemID))
|
||||
config.MemID,
|
||||
config.IGVMConfig.ID))
|
||||
|
||||
if config.EnableSEVSNP {
|
||||
args = append(args, "-bios", config.OVMFCodeConfig.File)
|
||||
sevType = "sev-snp-guest"
|
||||
|
||||
if config.SevConfig.HostData != "" {
|
||||
if config.SevConfig.EnableHostData {
|
||||
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
|
||||
}
|
||||
}
|
||||
|
||||
if config.KernelHash {
|
||||
kernelHash = ",kernel-hashes=on"
|
||||
}
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("memory-backend-memfd,id=%s,size=%s,share=true,prealloc=false",
|
||||
config.MemID,
|
||||
config.MemoryConfig.Size))
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s%s",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
|
||||
sevType,
|
||||
config.SevConfig.ID,
|
||||
config.SevConfig.CBitPos,
|
||||
config.SevConfig.ReducedPhysBits,
|
||||
kernelHash,
|
||||
hostData))
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("igvm-cfg,id=%s,file=%s",
|
||||
config.IGVMConfig.ID,
|
||||
config.IGVMConfig.File))
|
||||
}
|
||||
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
|
||||
@@ -132,6 +132,10 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
},
|
||||
IGVMConfig: IGVMConfig{
|
||||
ID: "igvm0",
|
||||
File: "/test/path/cocos-igvm.igvm",
|
||||
},
|
||||
NoGraphic: true,
|
||||
Monitor: "pty",
|
||||
},
|
||||
@@ -144,10 +148,10 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
|
||||
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
|
||||
"-device", "vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3",
|
||||
"-machine", "confidential-guest-support=sev0,memory-backend=ram1",
|
||||
"-bios", "/usr/share/OVMF/OVMF_CODE.fd",
|
||||
"-machine", "confidential-guest-support=sev0,memory-backend=ram1,igvm-cfg=igvm0",
|
||||
"-object", "memory-backend-memfd,id=ram1,size=2048M,share=true,prealloc=false",
|
||||
"-object", "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1",
|
||||
"-object", "igvm-cfg,id=igvm0,file=/test/path/cocos-igvm.igvm",
|
||||
"-kernel", "img/bzImage",
|
||||
"-append", "\"quiet console=null\"",
|
||||
"-initrd", "img/rootfs.cpio.gz",
|
||||
@@ -167,37 +171,6 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_KernelHash(t *testing.T) {
|
||||
config := Config{
|
||||
EnableSEVSNP: true,
|
||||
KernelHash: true,
|
||||
SevConfig: SevConfig{
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
},
|
||||
}
|
||||
|
||||
result := config.ConstructQemuArgs()
|
||||
|
||||
expected := "-object"
|
||||
expectedValue := "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on"
|
||||
|
||||
found := false
|
||||
for i, arg := range result {
|
||||
if arg == expected && i+1 < len(result) {
|
||||
if result[i+1] == expectedValue {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("ConstructQemuArgs() did not contain expected SEV-SNP configuration with kernel hashes enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
config := Config{
|
||||
EnableSEVSNP: true,
|
||||
@@ -205,6 +178,7 @@ func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
EnableHostData: true,
|
||||
HostData: "test-host-data",
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ func (v *qemuVM) Start() (err error) {
|
||||
v.vmi.Config.NetDevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.NetDevConfig.ID, id)
|
||||
v.vmi.Config.SevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SevConfig.ID, id)
|
||||
|
||||
if !v.vmi.Config.KernelHash {
|
||||
if !v.vmi.Config.EnableSEVSNP {
|
||||
// Copy firmware vars file.
|
||||
srcFile := v.vmi.Config.OVMFVarsConfig.File
|
||||
dstFile := fmt.Sprintf("%s/%s-%s.fd", tmpDir, firmwareVars, id)
|
||||
|
||||
+4
-4
@@ -20,9 +20,9 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -166,14 +166,14 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
return "", id, errors.Wrap(ErrFailedToReadPolicy, err)
|
||||
}
|
||||
|
||||
var attestationPolicy check.Config
|
||||
attestationPolicy := config.Config{SnpCheck: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(f, &attestationPolicy); err != nil {
|
||||
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
// Define the TCB that was present at launch of the VM.
|
||||
cfg.LaunchTCB = attestationPolicy.Policy.MinimumLaunchTcb
|
||||
cfg.LaunchTCB = attestationPolicy.SnpCheck.Policy.MinimumLaunchTcb
|
||||
}
|
||||
|
||||
agentPort, err := getFreePort(ms.portRangeMin, ms.portRangeMax)
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ packages:
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/google/go-sev-guest/client:
|
||||
interfaces:
|
||||
QuoteProvider:
|
||||
LeveledQuoteProvider:
|
||||
config:
|
||||
dir: "./pkg/attestation/quoteprovider/mocks"
|
||||
filename: "QuoteProvider.go"
|
||||
|
||||
+24
-20
@@ -20,8 +20,8 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,8 +51,8 @@ var (
|
||||
errConnCreate = errors.New("could not create connection")
|
||||
)
|
||||
|
||||
type ValidationVerification func(data1, data2 []byte) error
|
||||
type FetchAttestation func(data1 []byte) ([]byte, error)
|
||||
type ValidationVerification func(data1, data2, data3, data4 []byte) error
|
||||
type FetchAttestation func(data1, data2, data3 []byte) ([]byte, error)
|
||||
|
||||
func registerFetchAttestation(callback FetchAttestation) uintptr {
|
||||
handle := cgo.NewHandle(callback)
|
||||
@@ -70,7 +70,7 @@ func validationVerificationCallback(teeType C.int) uintptr {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerValidationVerification(quoteprovider.VerifyAttestationReportTLS)
|
||||
return registerValidationVerification(vtpm.VTPMVerify)
|
||||
default:
|
||||
return uintptr(0)
|
||||
}
|
||||
@@ -82,22 +82,24 @@ func fetchAttestationCallback(teeType C.int) uintptr {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerFetchAttestation(quoteprovider.FetchAttestation)
|
||||
return registerFetchAttestation(vtpm.FetchATLSQuote)
|
||||
default:
|
||||
return uintptr(0)
|
||||
}
|
||||
}
|
||||
|
||||
//export callVerificationValidationCallback
|
||||
func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uchar, attReportSize C.int, repData *C.uchar) C.int {
|
||||
func callVerificationValidationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, quote *C.uchar, quoteSize C.int, teeNonce *C.uchar, nonce *C.uchar) C.int {
|
||||
handle := cgo.Handle(callbackHandle)
|
||||
defer handle.Delete()
|
||||
|
||||
callback := handle.Value().(ValidationVerification)
|
||||
attestationReport := C.GoBytes(unsafe.Pointer(attReport), attReportSize)
|
||||
reportData := C.GoBytes(unsafe.Pointer(repData), agent.ReportDataSize)
|
||||
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
|
||||
attestationReport := C.GoBytes(unsafe.Pointer(quote), quoteSize)
|
||||
teeData := C.GoBytes(unsafe.Pointer(teeNonce), quoteprovider.Nonce)
|
||||
nonceData := C.GoBytes(unsafe.Pointer(nonce), vtpm.Nonce)
|
||||
|
||||
err := callback(attestationReport, reportData)
|
||||
err := callback(attestationReport, pubKeyCert, teeData, nonceData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "callback failed %v", err)
|
||||
return C.int(-1)
|
||||
@@ -107,20 +109,22 @@ func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uch
|
||||
}
|
||||
|
||||
//export callFetchAttestationCallback
|
||||
func callFetchAttestationCallback(callbackHandle uintptr, reportDataByte *C.uchar, outlen *C.int) *C.uchar {
|
||||
func callFetchAttestationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, teeNonceByte *C.uchar, vTPMNonceByte *C.uchar, outlen *C.ulong) *C.uchar {
|
||||
handle := cgo.Handle(callbackHandle)
|
||||
defer handle.Delete()
|
||||
|
||||
callback := handle.Value().(FetchAttestation)
|
||||
reportData := C.GoBytes(unsafe.Pointer(reportDataByte), agent.ReportDataSize)
|
||||
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
|
||||
teeNonceData := C.GoBytes(unsafe.Pointer(teeNonceByte), quoteprovider.Nonce)
|
||||
vTPMNonce := C.GoBytes(unsafe.Pointer(vTPMNonceByte), vtpm.Nonce)
|
||||
|
||||
quote, err := callback(reportData)
|
||||
quote, err := callback(pubKeyCert, teeNonceData, vTPMNonce)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "attestation callback returned nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
*outlen = C.int(len(quote))
|
||||
*outlen = C.ulong(len(quote))
|
||||
resultC := C.malloc(C.size_t(len(quote)))
|
||||
if resultC == nil {
|
||||
fmt.Fprintf(os.Stderr, "could not allocate memory for fetch attestation callback")
|
||||
@@ -232,23 +236,23 @@ func (c *ATLSConn) Read(b []byte) (int, error) {
|
||||
case noError:
|
||||
return n, nil // no error.
|
||||
case errorZeroReturn:
|
||||
fmt.Fprintf(os.Stdout, "Connection closed by peer")
|
||||
fmt.Fprintf(os.Stdout, "Connection closed by peer\n")
|
||||
return 0, io.EOF // connection closed.
|
||||
case errorWantRead:
|
||||
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later")
|
||||
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later\n")
|
||||
return 0, nil // non-fatal, just retry later.
|
||||
case errorWantWrite:
|
||||
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later")
|
||||
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later\n")
|
||||
return 0, nil // non-fatal, just retry later.
|
||||
case errorSyscall:
|
||||
fmt.Fprintf(os.Stderr, "I/O error")
|
||||
fmt.Fprintf(os.Stderr, "I/O error\n")
|
||||
return 0, syscall.ECONNRESET // return connection reset error.
|
||||
case errorSsl:
|
||||
fmt.Fprintf(os.Stderr, "I/O error")
|
||||
fmt.Fprintf(os.Stderr, "I/O error\n")
|
||||
return 0, syscall.ECONNRESET // return connection reset error.
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "SSL error occurred: %d\n", errCode)
|
||||
return 0, fmt.Errorf("SSL error")
|
||||
return 0, fmt.Errorf("SSL error\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +301,7 @@ func (c *ATLSConn) Close() error {
|
||||
return errTLSConn
|
||||
} else if int(ret) == 1 {
|
||||
c.tlsConn = nil
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+70
-93
@@ -7,30 +7,27 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* attReport, int attReportSize, const u_char* repData);
|
||||
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* reportDataByte, int* outlen);
|
||||
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* quote, int quoteSize, const u_char* teeNonce, const u_char* nonce);
|
||||
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* teeNonceByte, const u_char* vTPMNonceByte, unsigned long* outlen);
|
||||
extern uintptr_t validationVerificationCallback(int teeType);
|
||||
extern uintptr_t fetchAttestationCallback(int teeType);
|
||||
|
||||
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char *attestationReport, int reportSize, u_char *reportData) {
|
||||
if (attestationReport == NULL || reportData == NULL) {
|
||||
fprintf(stderr, "attestation data and report data cannot be NULL\n");
|
||||
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char* pub_key, int pub_key_len, u_char *quote, int quote_size, u_char *tee_nonce, u_char *vtpm_nonce) {
|
||||
if (quote == NULL || vtpm_nonce == NULL || tee_nonce == NULL || pub_key == NULL) {
|
||||
fprintf(stderr, "attestation and noce and public key cannot be NULL\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return callVerificationValidationCallback(callbackHandle, attestationReport, reportSize, reportData);
|
||||
return callVerificationValidationCallback(callbackHandle, pub_key, pub_key_len, quote, quote_size, tee_nonce, vtpm_nonce);
|
||||
}
|
||||
|
||||
u_char* triggerFetchAttestationCallback(uintptr_t callbackHandle, char *reportData) {
|
||||
int outlen = REPORT_DATA_SIZE;
|
||||
|
||||
if(reportData == NULL) {
|
||||
u_char* triggerFetchAttestationCallback(uintptr_t callback_handle, u_char* pub_key, int pub_key_len, char *tee_nonce, char *vtpm_nonce, unsigned long *outlen) {
|
||||
if(tee_nonce == NULL || vtpm_nonce == NULL) {
|
||||
fprintf(stderr, "Report data cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return callFetchAttestationCallback(callbackHandle, reportData, &outlen);
|
||||
return callFetchAttestationCallback(callback_handle, pub_key, pub_key_len, tee_nonce, vtpm_nonce, outlen);
|
||||
}
|
||||
|
||||
int check_sev_snp() {
|
||||
@@ -47,46 +44,6 @@ int check_sev_snp() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int compute_sha256_of_public_key_nonce(X509 *cert, u_char *nonce, u_char *hash) {
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
u_char *concatinated = NULL;
|
||||
int pubkey_len = 0;
|
||||
int totla_len = 0;
|
||||
|
||||
pkey = X509_get_pubkey(cert);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
|
||||
totla_len = pubkey_len + CLIENT_RANDOM_SIZE;
|
||||
concatinated = (u_char*)malloc(totla_len);
|
||||
if (concatinated == NULL) {
|
||||
perror("failed to allocate memory");
|
||||
return -1;
|
||||
}
|
||||
memcpy(concatinated, nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(concatinated + CLIENT_RANDOM_SIZE, pubkey_buf, pubkey_len);
|
||||
|
||||
// Compute the SHA-512 hash of the DER-encoded public key and the random nonce
|
||||
SHA512(concatinated, totla_len, hash);
|
||||
|
||||
// Clean up
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
free(concatinated);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
Evidence request extension
|
||||
- Contains a random nonce that goes into the attestation report
|
||||
@@ -121,9 +78,14 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
}
|
||||
|
||||
if (ext_data != NULL) {
|
||||
if (RAND_bytes(ext_data->er.data, CLIENT_RANDOM_SIZE) != 1) {
|
||||
perror("could not generate random bytes, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.data, CLIENT_RANDOM_SIZE);
|
||||
if (RAND_bytes(ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE) != 1) {
|
||||
perror("could not generate random bytes for vtpm nonce, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
}
|
||||
|
||||
if (RAND_bytes(ext_data->er.tee_nonce, REPORT_DATA_SIZE) != 1) {
|
||||
perror("could not generate random bytes for tee nonce, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "add_arg is NULL\n");
|
||||
@@ -132,7 +94,8 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(er->data, ext_data->er.data, CLIENT_RANDOM_SIZE);
|
||||
memcpy(er->vtpm_nonce, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(er->tee_nonce, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
|
||||
er->tee_type = AMD_TEE;
|
||||
ext_data->er.tee_type = AMD_TEE;
|
||||
|
||||
@@ -201,7 +164,8 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
evidence_request *er = (evidence_request*)in;
|
||||
|
||||
if (ext_data != NULL) {
|
||||
memcpy(ext_data->er.data, er->data, CLIENT_RANDOM_SIZE);
|
||||
memcpy(ext_data->er.vtpm_nonce, er->vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(ext_data->er.tee_nonce, er->tee_nonce, REPORT_DATA_SIZE);
|
||||
ext_data->er.tee_type = er->tee_type;
|
||||
} else {
|
||||
fprintf(stderr, "parse_arg is NULL\n");
|
||||
@@ -238,7 +202,7 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
/*
|
||||
Attestation Certificate extension
|
||||
- Contains the attestation report
|
||||
- The attestation report contains the hash of the nonce and the Public Key of the x.509 Agent certificate
|
||||
- The attestation report contains the hash of the nonce, the Public Key of the x.509 Agent certificate, and the vTPM AK
|
||||
*/
|
||||
void attestation_certificate_ext_free_cb(SSL *s, unsigned int ext_type,
|
||||
unsigned int context,
|
||||
@@ -263,40 +227,46 @@ int attestation_certificate_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
{
|
||||
tls_extension_data *ext_data = (tls_extension_data*)add_arg;
|
||||
if (ext_data != NULL) {
|
||||
u_char *attestation_report;
|
||||
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
|
||||
u_char *quote;
|
||||
size_t len = 0;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
int pubkey_len = 0;
|
||||
|
||||
if (hash == NULL) {
|
||||
perror("could not allocate memory");
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
|
||||
if (x != NULL) {
|
||||
pkey = X509_get_pubkey(x);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (x != NULL) {
|
||||
int ret = compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error while calculating hash\n");
|
||||
free(hash);
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "agent certificate must be used for aTLS\n");
|
||||
free(hash);
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
attestation_report = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, hash);
|
||||
if (attestation_report == NULL) {
|
||||
quote = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, pubkey_buf, pubkey_len, ext_data->er.tee_nonce, ext_data->er.vtpm_nonce, &len);
|
||||
if (quote == NULL) {
|
||||
fprintf(stderr, "attestation report is NULL\n");
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
return -1;
|
||||
}
|
||||
free(hash);
|
||||
|
||||
*out = attestation_report;
|
||||
*outlen = ATTESTATION_REPORT_SIZE;
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
|
||||
*out = quote;
|
||||
*outlen = len;
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "add_arg is NULL\n");
|
||||
@@ -329,34 +299,41 @@ int attestation_certificate_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
tls_extension_data *ext_data = (tls_extension_data*)parse_arg;
|
||||
|
||||
if (ext_data != NULL) {
|
||||
char *attestation_report = (char*)malloc(ATTESTATION_REPORT_SIZE*sizeof(char));
|
||||
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
|
||||
char *quote = (char*)malloc(inlen*sizeof(char));
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
int pubkey_len = 0;
|
||||
int res = 0;
|
||||
|
||||
if (hash == NULL || attestation_report == NULL) {
|
||||
if (quote == NULL) {
|
||||
perror("could not allocate memory");
|
||||
|
||||
if (hash != NULL) free(hash);
|
||||
if (attestation_report != NULL) free(attestation_report);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash) != 0) {
|
||||
fprintf(stderr, "calculating hash failed\n");
|
||||
free(attestation_report);
|
||||
free(hash);
|
||||
return 0;
|
||||
pkey = X509_get_pubkey(x);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(attestation_report, in, inlen);
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
memcpy(quote, in, inlen);
|
||||
|
||||
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
|
||||
attestation_report,
|
||||
ATTESTATION_REPORT_SIZE,
|
||||
hash);
|
||||
free(attestation_report);
|
||||
free(hash);
|
||||
pubkey_buf,
|
||||
pubkey_len,
|
||||
quote,
|
||||
inlen,
|
||||
(u_char*)&ext_data->er.tee_nonce,
|
||||
(u_char*)&ext_data->er.vtpm_nonce);
|
||||
free(quote);
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "verification and validation failed, aborting connection\n");
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#define EVIDENCE_REQUEST_HELLO_EXTENSION_TYPE 65
|
||||
#define ATTESTATION_CERTIFICATE_EXTENSION_TYPE 66
|
||||
#define ATTESTATION_REPORT_SIZE 0x4A0
|
||||
#define REPORT_DATA_SIZE 64
|
||||
#define CLIENT_RANDOM_SIZE 32
|
||||
#define TLS_CLIENT_CTX 0
|
||||
@@ -19,7 +18,8 @@
|
||||
typedef struct evidence_request
|
||||
{
|
||||
int tee_type;
|
||||
char data[CLIENT_RANDOM_SIZE];
|
||||
char vtpm_nonce[CLIENT_RANDOM_SIZE];
|
||||
char tee_nonce[REPORT_DATA_SIZE];
|
||||
} evidence_request;
|
||||
|
||||
typedef struct tls_extension_data
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type AttestationType int32
|
||||
|
||||
const (
|
||||
SNP AttestationType = iota
|
||||
VTPM
|
||||
SNPvTPM
|
||||
)
|
||||
|
||||
var (
|
||||
AttestationPolicy = Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &PcrConfig{}}
|
||||
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
||||
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
||||
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
||||
)
|
||||
|
||||
type PcrValues struct {
|
||||
Sha256 map[string]string `json:"sha256"`
|
||||
Sha384 map[string]string `json:"sha384"`
|
||||
}
|
||||
|
||||
type PcrConfig struct {
|
||||
PCRValues PcrValues `json:"pcr_values"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SnpCheck *check.Config
|
||||
PcrConfig *PcrConfig
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config) error {
|
||||
if policyPath != "" {
|
||||
policyData, err := os.ReadFile(policyPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyOpen, err)
|
||||
}
|
||||
|
||||
return ReadAttestationPolicyFromByte(policyData, attestationConfiguration)
|
||||
}
|
||||
|
||||
return ErrAttestationPolicyMissing
|
||||
}
|
||||
|
||||
func ReadAttestationPolicyFromByte(policyData []byte, attestationConfiguration *Config) error {
|
||||
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
|
||||
|
||||
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.SnpCheck); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(policyData, attestationConfiguration.PcrConfig); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -8,26 +8,24 @@ package quoteprovider
|
||||
|
||||
import (
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
pb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
cocosai "github.com/ultravioletrs/cocos"
|
||||
)
|
||||
|
||||
var (
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
)
|
||||
const Nonce = 64
|
||||
|
||||
var _ client.QuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
var _ client.LeveledQuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
|
||||
type embeddedQuoteProvider struct {
|
||||
}
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
return &embeddedQuoteProvider{}, nil
|
||||
}
|
||||
|
||||
// GetQuote returns the SEV quote for the given report data.
|
||||
func (e *embeddedQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
|
||||
// GetRawQuoteAtLevel returns the SEV quote for the given report data and VMPL.
|
||||
func (e *embeddedQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
@@ -46,6 +44,6 @@ func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
|
||||
func VerifyAttestationReportTLS(attestation *sevsnp.Attestation, reportData []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,42 +10,42 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// QuoteProvider is an autogenerated mock type for the QuoteProvider type
|
||||
type QuoteProvider struct {
|
||||
// LeveledQuoteProvider is an autogenerated mock type for the LeveledQuoteProvider type
|
||||
type LeveledQuoteProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type QuoteProvider_Expecter struct {
|
||||
type LeveledQuoteProvider_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *QuoteProvider) EXPECT() *QuoteProvider_Expecter {
|
||||
return &QuoteProvider_Expecter{mock: &_m.Mock}
|
||||
func (_m *LeveledQuoteProvider) EXPECT() *LeveledQuoteProvider_Expecter {
|
||||
return &LeveledQuoteProvider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetRawQuote provides a mock function with given fields: reportData
|
||||
func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
|
||||
ret := _m.Called(reportData)
|
||||
// GetRawQuoteAtLevel provides a mock function with given fields: reportData, vmpl
|
||||
func (_m *LeveledQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error) {
|
||||
ret := _m.Called(reportData, vmpl)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRawQuote")
|
||||
panic("no return value specified for GetRawQuoteAtLevel")
|
||||
}
|
||||
|
||||
var r0 []uint8
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([64]byte) ([]uint8, error)); ok {
|
||||
return rf(reportData)
|
||||
if rf, ok := ret.Get(0).(func([64]byte, uint) ([]uint8, error)); ok {
|
||||
return rf(reportData, vmpl)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([64]byte) []uint8); ok {
|
||||
r0 = rf(reportData)
|
||||
if rf, ok := ret.Get(0).(func([64]byte, uint) []uint8); ok {
|
||||
r0 = rf(reportData, vmpl)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]uint8)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([64]byte) error); ok {
|
||||
r1 = rf(reportData)
|
||||
if rf, ok := ret.Get(1).(func([64]byte, uint) error); ok {
|
||||
r1 = rf(reportData, vmpl)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -53,36 +53,37 @@ func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// QuoteProvider_GetRawQuote_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRawQuote'
|
||||
type QuoteProvider_GetRawQuote_Call struct {
|
||||
// LeveledQuoteProvider_GetRawQuoteAtLevel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRawQuoteAtLevel'
|
||||
type LeveledQuoteProvider_GetRawQuoteAtLevel_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetRawQuote is a helper method to define mock.On call
|
||||
// GetRawQuoteAtLevel is a helper method to define mock.On call
|
||||
// - reportData [64]byte
|
||||
func (_e *QuoteProvider_Expecter) GetRawQuote(reportData interface{}) *QuoteProvider_GetRawQuote_Call {
|
||||
return &QuoteProvider_GetRawQuote_Call{Call: _e.mock.On("GetRawQuote", reportData)}
|
||||
// - vmpl uint
|
||||
func (_e *LeveledQuoteProvider_Expecter) GetRawQuoteAtLevel(reportData interface{}, vmpl interface{}) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
return &LeveledQuoteProvider_GetRawQuoteAtLevel_Call{Call: _e.mock.On("GetRawQuoteAtLevel", reportData, vmpl)}
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_GetRawQuote_Call) Run(run func(reportData [64]byte)) *QuoteProvider_GetRawQuote_Call {
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Run(run func(reportData [64]byte, vmpl uint)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([64]byte))
|
||||
run(args[0].([64]byte), args[1].(uint))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_GetRawQuote_Call) Return(_a0 []uint8, _a1 error) *QuoteProvider_GetRawQuote_Call {
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Return(_a0 []uint8, _a1 error) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_GetRawQuote_Call) RunAndReturn(run func([64]byte) ([]uint8, error)) *QuoteProvider_GetRawQuote_Call {
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) RunAndReturn(run func([64]byte, uint) ([]uint8, error)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsSupported provides a mock function with given fields:
|
||||
func (_m *QuoteProvider) IsSupported() bool {
|
||||
func (_m *LeveledQuoteProvider) IsSupported() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -99,35 +100,35 @@ func (_m *QuoteProvider) IsSupported() bool {
|
||||
return r0
|
||||
}
|
||||
|
||||
// QuoteProvider_IsSupported_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSupported'
|
||||
type QuoteProvider_IsSupported_Call struct {
|
||||
// LeveledQuoteProvider_IsSupported_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSupported'
|
||||
type LeveledQuoteProvider_IsSupported_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsSupported is a helper method to define mock.On call
|
||||
func (_e *QuoteProvider_Expecter) IsSupported() *QuoteProvider_IsSupported_Call {
|
||||
return &QuoteProvider_IsSupported_Call{Call: _e.mock.On("IsSupported")}
|
||||
func (_e *LeveledQuoteProvider_Expecter) IsSupported() *LeveledQuoteProvider_IsSupported_Call {
|
||||
return &LeveledQuoteProvider_IsSupported_Call{Call: _e.mock.On("IsSupported")}
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_IsSupported_Call) Run(run func()) *QuoteProvider_IsSupported_Call {
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Run(run func()) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_IsSupported_Call) Return(_a0 bool) *QuoteProvider_IsSupported_Call {
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Return(_a0 bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_IsSupported_Call) RunAndReturn(run func() bool) *QuoteProvider_IsSupported_Call {
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) RunAndReturn(run func() bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Product provides a mock function with given fields:
|
||||
func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
|
||||
func (_m *LeveledQuoteProvider) Product() *sevsnp.SevProduct {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -146,40 +147,40 @@ func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
|
||||
return r0
|
||||
}
|
||||
|
||||
// QuoteProvider_Product_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Product'
|
||||
type QuoteProvider_Product_Call struct {
|
||||
// LeveledQuoteProvider_Product_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Product'
|
||||
type LeveledQuoteProvider_Product_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Product is a helper method to define mock.On call
|
||||
func (_e *QuoteProvider_Expecter) Product() *QuoteProvider_Product_Call {
|
||||
return &QuoteProvider_Product_Call{Call: _e.mock.On("Product")}
|
||||
func (_e *LeveledQuoteProvider_Expecter) Product() *LeveledQuoteProvider_Product_Call {
|
||||
return &LeveledQuoteProvider_Product_Call{Call: _e.mock.On("Product")}
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_Product_Call) Run(run func()) *QuoteProvider_Product_Call {
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Run(run func()) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_Product_Call) Return(_a0 *sevsnp.SevProduct) *QuoteProvider_Product_Call {
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Return(_a0 *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *QuoteProvider_Product_Call) RunAndReturn(run func() *sevsnp.SevProduct) *QuoteProvider_Product_Call {
|
||||
func (_c *LeveledQuoteProvider_Product_Call) RunAndReturn(run func() *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewQuoteProvider creates a new instance of QuoteProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// NewLeveledQuoteProvider creates a new instance of LeveledQuoteProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewQuoteProvider(t interface {
|
||||
func NewLeveledQuoteProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *QuoteProvider {
|
||||
mock := &QuoteProvider{}
|
||||
}) *LeveledQuoteProvider {
|
||||
mock := &LeveledQuoteProvider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"github.com/google/go-sev-guest/verify"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/google/logger"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -29,20 +29,19 @@ const (
|
||||
cocosDirectory = ".cocos"
|
||||
caBundleName = "ask_ark.pem"
|
||||
attestationReportSize = 0x4A0
|
||||
reportDataSize = 64
|
||||
Nonce = 64
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
sevVMPL = 2
|
||||
)
|
||||
|
||||
var (
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
timeout = time.Minute * 2
|
||||
maxTryDelay = time.Second * 30
|
||||
)
|
||||
|
||||
var (
|
||||
errProductLine = errors.New(fmt.Sprintf("product name must be %s or %s", sevProductNameMilan, sevProductNameGenoa))
|
||||
errReportSize = errors.New("attestation report size mismatch")
|
||||
errAttVerification = errors.New("attestation verification failed")
|
||||
errAttValidation = errors.New("attestation validation failed")
|
||||
)
|
||||
@@ -138,38 +137,31 @@ func validateReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
return client.GetQuoteProvider()
|
||||
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
return client.GetLeveledQuoteProvider()
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
|
||||
config, err := copyConfig(&AttConfigurationSEVSNP)
|
||||
func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []byte) error {
|
||||
config, err := copyConfig(config.AttestationPolicy.SnpCheck)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create a copy of attestation policy"), err)
|
||||
}
|
||||
|
||||
// Certificate chain is populated based on the extra data that is appended to the SEV-SNP attestation report.
|
||||
// This data is not part of the attestation report and it will be ignored.
|
||||
attestationPB.CertificateChain = nil
|
||||
config.Policy.ReportData = reportData[:]
|
||||
return VerifyAndValidate(attestationBytes, config)
|
||||
return VerifyAndValidate(attestationPB, config)
|
||||
}
|
||||
|
||||
func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
|
||||
func VerifyAndValidate(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
|
||||
logger.Init("", false, false, io.Discard)
|
||||
|
||||
if len(attestationReport) < attestationReportSize {
|
||||
return errReportSize
|
||||
}
|
||||
attestationBytes := attestationReport[:attestationReportSize]
|
||||
|
||||
attestationPB, err := abi.ReportCertsToProto(attestationBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert attestation bytes to struct %v", errors.Wrap(errAttVerification, err))
|
||||
}
|
||||
|
||||
if err = verifyReport(attestationPB, cfg); err != nil {
|
||||
if err := verifyReport(attestationPB, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = validateReport(attestationPB, cfg); err != nil {
|
||||
if err := validateReport(attestationPB, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -177,19 +169,19 @@ func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
|
||||
}
|
||||
|
||||
func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
var reportData [reportDataSize]byte
|
||||
var reportData [Nonce]byte
|
||||
|
||||
qp, err := GetQuoteProvider()
|
||||
qp, err := GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("could not get quote provider")
|
||||
}
|
||||
|
||||
if len(reportData) > reportDataSize {
|
||||
if len(reportData) > Nonce {
|
||||
return []byte{}, fmt.Errorf("attestation report size mismatch")
|
||||
}
|
||||
copy(reportData[:], reportDataSlice)
|
||||
|
||||
rawQuote, err := qp.GetRawQuote(reportData)
|
||||
rawQuote, err := qp.GetRawQuoteAtLevel(reportData, sevVMPL)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to get raw quote")
|
||||
}
|
||||
|
||||
@@ -17,14 +17,10 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const (
|
||||
measurementOffset = 0x90
|
||||
signatureOffset = 0x2A0
|
||||
)
|
||||
|
||||
func TestFillInAttestationLocal(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "test_home")
|
||||
require.NoError(t, err)
|
||||
@@ -76,18 +72,18 @@ func TestFillInAttestationLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportSuccess(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
goodProduct int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, validation and verification is performed succsessfully",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
goodProduct: 1,
|
||||
err: nil,
|
||||
@@ -103,20 +99,20 @@ func TestVerifyAttestationReportSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
// Change random data so in the signature so the signature failes
|
||||
file[signatureOffset] = file[signatureOffset] ^ 0x01
|
||||
attestationPB.Report.Signature[0] = attestationPB.Report.Signature[0] ^ 0x01
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, distorted signature",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errAttVerification,
|
||||
},
|
||||
@@ -131,17 +127,17 @@ func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, unknown product",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errProductLine,
|
||||
},
|
||||
@@ -149,8 +145,8 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AttConfigurationSEVSNP.RootOfTrust.ProductLine = ""
|
||||
AttConfigurationSEVSNP.Policy.Product = nil
|
||||
config.AttestationPolicy.SnpCheck.RootOfTrust.ProductLine = ""
|
||||
config.AttestationPolicy.SnpCheck.Policy.Product = nil
|
||||
err := VerifyAttestationReportTLS(tt.attestationReport, tt.reportData)
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
})
|
||||
@@ -158,20 +154,20 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
// Change random data in the measurement so the measurement does not match
|
||||
file[measurementOffset] = file[measurementOffset] ^ 0x01
|
||||
attestationPB.Report.Measurement[0] = attestationPB.Report.Measurement[0] ^ 0x01
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, malformed policy (measurement)",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errAttVerification,
|
||||
},
|
||||
@@ -185,32 +181,34 @@ func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func prepareForTestVerifyAttestationReport(t *testing.T) ([]byte, []byte) {
|
||||
func prepVerifyAttReport(t *testing.T) (*sevsnp.Attestation, []byte) {
|
||||
file, err := os.ReadFile("../../../attestation.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(file) < attestationReportSize {
|
||||
file = append(file, make([]byte, attestationReportSize-len(file))...)
|
||||
}
|
||||
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
config.AttestationPolicy = config.Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicyFile, err := os.ReadFile("../../../scripts/attestation_policy/attestation_policy.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = protojson.Unmarshal(attestationPolicyFile, &AttConfigurationSEVSNP)
|
||||
unmarshalOptions := protojson.UnmarshalOptions{DiscardUnknown: true}
|
||||
|
||||
err = unmarshalOptions.Unmarshal(attestationPolicyFile, config.AttestationPolicy.SnpCheck)
|
||||
require.NoError(t, err)
|
||||
|
||||
AttConfigurationSEVSNP.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
AttConfigurationSEVSNP.Policy.FamilyId = rr.Report.FamilyId
|
||||
AttConfigurationSEVSNP.Policy.ImageId = rr.Report.ImageId
|
||||
AttConfigurationSEVSNP.Policy.Measurement = rr.Report.Measurement
|
||||
AttConfigurationSEVSNP.Policy.HostData = rr.Report.HostData
|
||||
AttConfigurationSEVSNP.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
AttConfigurationSEVSNP.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
config.AttestationPolicy.SnpCheck.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
config.AttestationPolicy.SnpCheck.Policy.FamilyId = rr.Report.FamilyId
|
||||
config.AttestationPolicy.SnpCheck.Policy.ImageId = rr.Report.ImageId
|
||||
config.AttestationPolicy.SnpCheck.Policy.Measurement = rr.Report.Measurement
|
||||
config.AttestationPolicy.SnpCheck.Policy.HostData = rr.Report.HostData
|
||||
config.AttestationPolicy.SnpCheck.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
config.AttestationPolicy.SnpCheck.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
|
||||
return file, rr.Report.ReportData
|
||||
return rr, rr.Report.ReportData
|
||||
}
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package vtpm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/proto/tpm"
|
||||
"github.com/google/go-tpm-tools/server"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
|
||||
Nonce = 32
|
||||
PCR15 = 15
|
||||
Hash256 = 32
|
||||
Hash384 = 48
|
||||
)
|
||||
|
||||
var (
|
||||
ExternalTPM io.ReadWriteCloser
|
||||
ErrNoHashAlgo = errors.New("hash algo is not supported")
|
||||
)
|
||||
|
||||
type tpmWrapper struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (et tpmWrapper) EventLog() ([]byte, error) {
|
||||
return os.ReadFile(eventLog)
|
||||
}
|
||||
|
||||
func OpenTpm() (io.ReadWriteCloser, error) {
|
||||
if ExternalTPM != nil {
|
||||
return tpmWrapper{ExternalTPM}, nil
|
||||
}
|
||||
|
||||
tw := tpmWrapper{}
|
||||
var err error
|
||||
|
||||
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpmrm0")
|
||||
if os.IsNotExist(err) {
|
||||
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpm0")
|
||||
}
|
||||
|
||||
return tw, err
|
||||
}
|
||||
|
||||
func ExtendPCR(pcrIndex int, value []byte) error {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rwc.Close()
|
||||
|
||||
fixedSha256Hash := sha3.Sum256(value)
|
||||
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA256, fixedSha256Hash[:], ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fixedSha384Hash := sha3.Sum384(value)
|
||||
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA384, fixedSha384Hash[:], ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
if teeAttestaion {
|
||||
attestation, err = addTEEAttestation(attestation, teeNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return marshalQuote(attestation)
|
||||
}
|
||||
|
||||
func FetchATLSQuote(pubKey, teeNonce, vTPMNonce []byte) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKey, attestation.GetAkPub(), teeNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
attestation, err = addTEEAttestation(attestation, reportData)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return marshalQuote(attestation)
|
||||
}
|
||||
|
||||
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte) error {
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
err := proto.Unmarshal(quote, attestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to unmarshal quote: %v", err)
|
||||
}
|
||||
|
||||
ak := attestation.GetAkPub()
|
||||
pub, err := tpm2.DecodePublic(ak)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKeyTLS, ak, teeNonce)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to calculate report data: %v", err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), reportData); err != nil {
|
||||
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
_, err = server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("verifying attestation: %w", err)
|
||||
}
|
||||
|
||||
s256, s384 := calculatePCRTLSKey(pubKeyTLS)
|
||||
|
||||
if err := checkExpectedPCRValues(attestation, s256, s384); err != nil {
|
||||
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func publicKeyToBytes(pubKey interface{}) ([]byte, error) {
|
||||
derBytes, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return derBytes, nil
|
||||
}
|
||||
|
||||
func createTEEAttestationReportNonce(pubKeyTLS []byte, ak []byte, nonce []byte) ([]byte, error) {
|
||||
pub, err := tpm2.DecodePublic(ak)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
pubKeyBytes, err := publicKeyToBytes(cryptoPub)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
reportData := append(append(pubKeyTLS, pubKeyBytes...), nonce...)
|
||||
hash := sha3.Sum512(reportData)
|
||||
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
|
||||
out, err := proto.Marshal(attestation)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to marshal vTPM attestation report: %v", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rwc.Close()
|
||||
|
||||
attestationKey, err := client.AttestationKeyRSA(rwc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create attestation key: %v", err)
|
||||
}
|
||||
defer attestationKey.Close()
|
||||
|
||||
var fixedNonce [Nonce]byte
|
||||
copy(fixedNonce[:], nonce)
|
||||
attestOpts := client.AttestOpts{}
|
||||
attestOpts.Nonce = fixedNonce[:]
|
||||
|
||||
attestOpts.TCGEventLog, err = client.GetEventLog(rwc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve TCG Event Log: %w", err)
|
||||
}
|
||||
|
||||
attestation, err := attestationKey.Attest(attestOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to collect attestation report: %v", err)
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.Attestation, error) {
|
||||
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce)
|
||||
if err != nil {
|
||||
return attestation, fmt.Errorf("failed to fetch TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
|
||||
if err != nil {
|
||||
return attestation, fmt.Errorf("failed to export the TEE report: %v", err)
|
||||
}
|
||||
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
||||
SevSnpAttestation: extReport,
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256 []byte, ePcr384 []byte) error {
|
||||
quotes := attestation.GetQuotes()
|
||||
for i := range quotes {
|
||||
quote := quotes[i]
|
||||
var pcrMap map[string]string
|
||||
var pcr15 []byte
|
||||
switch quote.Pcrs.Hash {
|
||||
case tpm.HashAlgo_SHA256:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha256
|
||||
pcr15 = ePcr256
|
||||
case tpm.HashAlgo_SHA384:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha384
|
||||
pcr15 = ePcr384
|
||||
default:
|
||||
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
|
||||
}
|
||||
|
||||
pcr15Index := uint32(15)
|
||||
if !bytes.Equal(quote.Pcrs.Pcrs[pcr15Index], pcr15) {
|
||||
return fmt.Errorf("for algo %s PCR[15] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], hex.EncodeToString(pcr15), hex.EncodeToString(quote.Pcrs.Pcrs[pcr15Index]))
|
||||
}
|
||||
|
||||
for i, v := range pcrMap {
|
||||
index, err := strconv.ParseInt(i, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting PCR index to int32: %v\n", err)
|
||||
}
|
||||
value, err := hex.DecodeString(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting PCR value to byte: %v\n", err)
|
||||
}
|
||||
if !bytes.Equal(quote.Pcrs.Pcrs[uint32(index)], value) {
|
||||
return fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return SHA256 and SHA384 values of the input public key.
|
||||
func calculatePCRTLSKey(pubKey []byte) ([]byte, []byte) {
|
||||
init256 := make([]byte, Hash256)
|
||||
init384 := make([]byte, Hash384)
|
||||
|
||||
key256 := sha3.Sum256(pubKey)
|
||||
key384 := sha3.Sum384(pubKey)
|
||||
|
||||
pcrValue256 := append(init256, key256[:]...)
|
||||
pcrValue384 := append(init384, key384[:]...)
|
||||
|
||||
newPcr256 := sha256.Sum256(pcrValue256)
|
||||
newPcr384 := sha512.Sum384(pcrValue384)
|
||||
|
||||
return newPcr256[:], newPcr384[:]
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
@@ -112,7 +113,7 @@ func TestAgentClientIntegration(t *testing.T) {
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
err: pkggrpc.ErrAttestationPolicyMissing,
|
||||
err: config.ErrAttestationPolicyMissing,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
|
||||
err := ReadAttestationPolicy(cfg.AttestationPolicy, "eprovider.AttConfigurationSEVSNP)
|
||||
err := config.ReadAttestationPolicy(cfg.AttestationPolicy, &config.AttestationPolicy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
att "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
@@ -200,8 +201,9 @@ func TestClientSecure(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadAttestationPolicy(t *testing.T) {
|
||||
validJSON := `{"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
validJSON := `{"pcr_values":{"sha256":{"0":"123"},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
invalidJSON := `{"invalid_json"`
|
||||
invalidJSONPCR := `{"pcr_values":{"sha256":{"0":true},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -219,19 +221,25 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
name: "Invalid JSON",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSON,
|
||||
err: ErrAttestationPolicyDecode,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
},
|
||||
{
|
||||
name: "Non-existent file",
|
||||
manifestPath: "nonexistent.json",
|
||||
fileContent: "",
|
||||
err: errAttestationPolicyOpen,
|
||||
err: att.ErrAttestationPolicyOpen,
|
||||
},
|
||||
{
|
||||
name: "Empty manifest path",
|
||||
manifestPath: "",
|
||||
fileContent: "",
|
||||
err: ErrAttestationPolicyMissing,
|
||||
err: att.ErrAttestationPolicyMissing,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON PCR",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSONPCR,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,13 +251,13 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
defer os.Remove(tt.manifestPath)
|
||||
}
|
||||
|
||||
config := check.Config{}
|
||||
err := ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
config := att.Config{SnpCheck: &check.Config{}, PcrConfig: &att.PcrConfig{}}
|
||||
err := att.ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
if tt.err == nil {
|
||||
assert.NotNil(t, config.Policy)
|
||||
assert.NotNil(t, config.RootOfTrust)
|
||||
assert.NotNil(t, config.SnpCheck.Policy)
|
||||
assert.NotNil(t, config.SnpCheck.RootOfTrust)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type security int
|
||||
@@ -37,9 +35,6 @@ const (
|
||||
var (
|
||||
errGrpcConnect = errors.New("failed to connect to grpc server")
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
errAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
||||
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
||||
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
||||
errCertificateParse = errors.New("failed to parse x509 certificate")
|
||||
errAttVerification = errors.New("certificat is not sefl signed")
|
||||
errFailedToLoadClientCertKey = errors.New("failed to load client certificate and key")
|
||||
@@ -146,7 +141,9 @@ func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(tc))
|
||||
opts = append(opts, grpc.WithContextDialer(CustomDialer))
|
||||
secure = withaTLS
|
||||
} else {
|
||||
conf := cfg.GetBaseConfig()
|
||||
@@ -198,20 +195,3 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
|
||||
return tc, nil, secure
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(manifestPath string, attestationConfiguration *check.Config) error {
|
||||
if manifestPath != "" {
|
||||
manifest, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(errAttestationPolicyOpen, err)
|
||||
}
|
||||
|
||||
if err := protojson.Unmarshal(manifest, attestationConfiguration); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrAttestationPolicyMissing
|
||||
}
|
||||
|
||||
+6
-3
@@ -26,11 +26,12 @@ type SDK interface {
|
||||
Algo(ctx context.Context, algorithm, requirements *os.File, privKey any) error
|
||||
Data(ctx context.Context, dataset *os.File, filename string, privKey any) error
|
||||
Result(ctx context.Context, privKey any, resultFile *os.File) error
|
||||
Attestation(ctx context.Context, reportData [size64]byte, attestationFile *os.File) error
|
||||
Attestation(ctx context.Context, reportData [size64]byte, nonce [size32]byte, attType int, attestationFile *os.File) error
|
||||
}
|
||||
|
||||
const (
|
||||
size64 = 64
|
||||
size32 = 32
|
||||
algoProgressBarDescription = "Uploading algorithm"
|
||||
dataProgressBarDescription = "Uploading data"
|
||||
resultProgressDescription = "Downloading result"
|
||||
@@ -120,9 +121,11 @@ func (sdk *agentSDK) Result(ctx context.Context, privKey any, resultFile *os.Fil
|
||||
return pb.ReceiveResult(resultProgressDescription, fileSize, stream, resultFile)
|
||||
}
|
||||
|
||||
func (sdk *agentSDK) Attestation(ctx context.Context, reportData [size64]byte, attestationFile *os.File) error {
|
||||
func (sdk *agentSDK) Attestation(ctx context.Context, reportData [size64]byte, nonce [size32]byte, attType int, attestationFile *os.File) error {
|
||||
request := &agent.AttestationRequest{
|
||||
ReportData: reportData[:],
|
||||
TeeNonce: reportData[:],
|
||||
VtpmNonce: nonce[:],
|
||||
Type: int32(attType),
|
||||
}
|
||||
|
||||
stream, err := sdk.client.Attestation(ctx, request)
|
||||
|
||||
+15
-7
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"github.com/ultravioletrs/cocos/pkg/sdk"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc"
|
||||
@@ -364,6 +366,7 @@ func TestAttestation(t *testing.T) {
|
||||
resultConsumer1Key, _ := generateKeys(t, "ed25519")
|
||||
|
||||
reportData := make([]byte, 64)
|
||||
nonce := make([]byte, 64)
|
||||
report := []byte{
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
@@ -385,7 +388,8 @@ func TestAttestation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
userKey any
|
||||
reportData [agent.ReportDataSize]byte
|
||||
reportData [quoteprovider.Nonce]byte
|
||||
nonce [vtpm.Nonce]byte
|
||||
response *agent.AttestationResponse
|
||||
svcRes []byte
|
||||
err error
|
||||
@@ -393,7 +397,8 @@ func TestAttestation(t *testing.T) {
|
||||
{
|
||||
name: "fetch attestation report successfully",
|
||||
userKey: resultConsumerKey,
|
||||
reportData: [agent.ReportDataSize]byte(reportData),
|
||||
reportData: [quoteprovider.Nonce]byte(reportData),
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResponse{
|
||||
File: report,
|
||||
},
|
||||
@@ -403,7 +408,8 @@ func TestAttestation(t *testing.T) {
|
||||
{
|
||||
name: "fetch attestation report with different key type",
|
||||
userKey: resultConsumer1Key,
|
||||
reportData: [agent.ReportDataSize]byte(reportData),
|
||||
reportData: [quoteprovider.Nonce]byte(reportData),
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResponse{
|
||||
File: report,
|
||||
},
|
||||
@@ -413,7 +419,8 @@ func TestAttestation(t *testing.T) {
|
||||
{
|
||||
name: "failed to fetch attestation report",
|
||||
userKey: resultConsumerKey,
|
||||
reportData: [agent.ReportDataSize]byte(reportData),
|
||||
reportData: [quoteprovider.Nonce]byte(reportData),
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResponse{
|
||||
File: []byte{},
|
||||
},
|
||||
@@ -422,7 +429,8 @@ func TestAttestation(t *testing.T) {
|
||||
{
|
||||
name: "invalid report data",
|
||||
userKey: resultConsumerKey,
|
||||
reportData: [agent.ReportDataSize]byte{},
|
||||
reportData: [quoteprovider.Nonce]byte{},
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResponse{
|
||||
File: []byte{},
|
||||
},
|
||||
@@ -433,7 +441,7 @@ func TestAttestation(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svcCall := svc.On("Attestation", mock.Anything, mock.Anything).Return(tc.svcRes, tc.err)
|
||||
svcCall := svc.On("Attestation", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.svcRes, tc.err)
|
||||
|
||||
file, err := os.CreateTemp("", "attestation")
|
||||
require.NoError(t, err)
|
||||
@@ -442,7 +450,7 @@ func TestAttestation(t *testing.T) {
|
||||
os.Remove(file.Name())
|
||||
})
|
||||
|
||||
err = sdk.Attestation(context.Background(), tc.reportData, file)
|
||||
err = sdk.Attestation(context.Background(), tc.reportData, tc.nonce, 0, file)
|
||||
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
|
||||
+12
-10
@@ -74,17 +74,17 @@ func (_c *SDK_Algo_Call) RunAndReturn(run func(context.Context, *os.File, *os.Fi
|
||||
return _c
|
||||
}
|
||||
|
||||
// Attestation provides a mock function with given fields: ctx, reportData, attestationFile
|
||||
func (_m *SDK) Attestation(ctx context.Context, reportData [64]byte, attestationFile *os.File) error {
|
||||
ret := _m.Called(ctx, reportData, attestationFile)
|
||||
// Attestation provides a mock function with given fields: ctx, reportData, nonce, attType, attestationFile
|
||||
func (_m *SDK) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType int, attestationFile *os.File) error {
|
||||
ret := _m.Called(ctx, reportData, nonce, attType, attestationFile)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attestation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, *os.File) error); ok {
|
||||
r0 = rf(ctx, reportData, attestationFile)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, int, *os.File) error); ok {
|
||||
r0 = rf(ctx, reportData, nonce, attType, attestationFile)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@@ -100,14 +100,16 @@ type SDK_Attestation_Call struct {
|
||||
// Attestation is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - reportData [64]byte
|
||||
// - nonce [32]byte
|
||||
// - attType int
|
||||
// - attestationFile *os.File
|
||||
func (_e *SDK_Expecter) Attestation(ctx interface{}, reportData interface{}, attestationFile interface{}) *SDK_Attestation_Call {
|
||||
return &SDK_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, attestationFile)}
|
||||
func (_e *SDK_Expecter) Attestation(ctx interface{}, reportData interface{}, nonce interface{}, attType interface{}, attestationFile interface{}) *SDK_Attestation_Call {
|
||||
return &SDK_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, nonce, attType, attestationFile)}
|
||||
}
|
||||
|
||||
func (_c *SDK_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, attestationFile *os.File)) *SDK_Attestation_Call {
|
||||
func (_c *SDK_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType int, attestationFile *os.File)) *SDK_Attestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].(*os.File))
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(int), args[4].(*os.File))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -117,7 +119,7 @@ func (_c *SDK_Attestation_Call) Return(_a0 error) *SDK_Attestation_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SDK_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, *os.File) error) *SDK_Attestation_Call {
|
||||
func (_c *SDK_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, int, *os.File) error) *SDK_Attestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,28 +1,52 @@
|
||||
{
|
||||
"policy": {
|
||||
"policy": 196608,
|
||||
"family_id": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"image_id": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"vmpl": 0,
|
||||
"minimum_tcb": 15352208179752599555,
|
||||
"minimum_launch_tcb": 15352208179752599555,
|
||||
"require_author_key": false,
|
||||
"measurement": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"host_data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"report_id_ma": "//////////////////////////////////////////8=",
|
||||
"chip_id": "GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==",
|
||||
"minimum_build": 8,
|
||||
"minimum_version": "1.55",
|
||||
"permit_provisional_firmware": true,
|
||||
"require_id_block": false,
|
||||
"product": {
|
||||
"name": 1
|
||||
"pcr_values": {
|
||||
"sha256": {
|
||||
"0": "71e0cc99e4609fdbc44698cceeda9e5ecb2f74fe07bd10710d5330e0eb6bd32b",
|
||||
"1": "a40e22460c21d2450367ca70c751ec0ae5ae1072994a131287a96eadc295603b",
|
||||
"2": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"3": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"4": "e16812b9181e13078b29f2e4844be7087f9e1bbffc3cb4171d2813580cafdb8d",
|
||||
"5": "a5ceb755d043f32431d63e39f5161464620a3437280494b5850dc1b47cc074e0",
|
||||
"6": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"7": "70d12f32fdb109ba0960697b5a8d5d8d860b004a757fe2471be2c2a19ec1a765",
|
||||
"9": "2add30b0f2b31480ee5eb802c436cfffe77ceebc6009e063e84fc6a6ef2c05ac"
|
||||
},
|
||||
"sha384": {
|
||||
"0": "ff93a763afde2c4a152d4843d9fcabe73a70d4f34bf8861845f2ab08440c1f0742b5882ed7f2524e38a3a6e40fbcdfca",
|
||||
"1": "c9b3bcc22d856cbc5be2a2bf72d81819df325db083cfea20e84d082a87f44d643e6fca98f29eb3cce4c87eed2dbca2e5",
|
||||
"2": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"3": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"4": "d18d213c26e7bc309e52448bde2f0a8ef86be388223f64f85c4e0c625f1e0a7f8c901d4f7c98f8445730bc63c4dfa88d",
|
||||
"5": "c50b529497c7f441ea47305587d6ce83e2e31f7b4fab6c13dc0b0c3c900e1d0caf0768321100927862df142bf0465ee4",
|
||||
"6": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"7": "ea40cbd8f51eed103d75821340e71fa3c0cfde3e75c360b4c9aca534b7fed021e12f8890acef36ccfe12b33ea4111576",
|
||||
"9": "02556c6b494abaf21481def35b38574e80dc68f20ceb8385f78a5ad4ecfbab60f9fcfca7c69f09a081fdd4ca13f3c14d"
|
||||
}
|
||||
},
|
||||
"policy": {
|
||||
"chip_id": "GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==",
|
||||
"family_id": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"host_data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"image_id": "AAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"measurement": "oDYo4e98Da2Fy73nDVZmxiWiz+5gnxae7NMRtdfnwpbBuVYZsI0mynz3fpfe+YIX",
|
||||
"minimum_build": 8,
|
||||
"minimum_launch_tcb": 15352208179752599555,
|
||||
"minimum_tcb": 15352208179752599555,
|
||||
"minimum_version": "1.55",
|
||||
"permit_provisional_firmware": true,
|
||||
"policy": 196608,
|
||||
"product": {
|
||||
"name": 1
|
||||
},
|
||||
"report_id_ma": "//////////////////////////////////////////8=",
|
||||
"require_author_key": false,
|
||||
"require_id_block": false,
|
||||
"vmpl": 2
|
||||
},
|
||||
"root_of_trust": {
|
||||
"product": "Milan",
|
||||
"check_crl": true,
|
||||
"disallow_network": false,
|
||||
"product": "Milan",
|
||||
"product_line": "Milan"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"pcr_values": {
|
||||
"sha256": {
|
||||
"0": "71e0cc99e4609fdbc44698cceeda9e5ecb2f74fe07bd10710d5330e0eb6bd32b",
|
||||
"1": "a40e22460c21d2450367ca70c751ec0ae5ae1072994a131287a96eadc295603b",
|
||||
"2": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"3": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"4": "e16812b9181e13078b29f2e4844be7087f9e1bbffc3cb4171d2813580cafdb8d",
|
||||
"5": "a5ceb755d043f32431d63e39f5161464620a3437280494b5850dc1b47cc074e0",
|
||||
"6": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
|
||||
"7": "70d12f32fdb109ba0960697b5a8d5d8d860b004a757fe2471be2c2a19ec1a765",
|
||||
"9": "2add30b0f2b31480ee5eb802c436cfffe77ceebc6009e063e84fc6a6ef2c05ac"
|
||||
},
|
||||
"sha384": {
|
||||
"0": "ff93a763afde2c4a152d4843d9fcabe73a70d4f34bf8861845f2ab08440c1f0742b5882ed7f2524e38a3a6e40fbcdfca",
|
||||
"1": "c9b3bcc22d856cbc5be2a2bf72d81819df325db083cfea20e84d082a87f44d643e6fca98f29eb3cce4c87eed2dbca2e5",
|
||||
"2": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"3": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"4": "d18d213c26e7bc309e52448bde2f0a8ef86be388223f64f85c4e0c625f1e0a7f8c901d4f7c98f8445730bc63c4dfa88d",
|
||||
"5": "c50b529497c7f441ea47305587d6ce83e2e31f7b4fab6c13dc0b0c3c900e1d0caf0768321100927862df142bf0465ee4",
|
||||
"6": "518923b0f955d08da077c96aaba522b9decede61c599cea6c41889cfbea4ae4d50529d96fe4d1afdafb65e7f95bf23c4",
|
||||
"7": "ea40cbd8f51eed103d75821340e71fa3c0cfde3e75c360b4c9aca534b7fed021e12f8890acef36ccfe12b33ea4111576",
|
||||
"9": "02556c6b494abaf21481def35b38574e80dc68f20ceb8385f78a5ad4ecfbab60f9fcfca7c69f09a081fdd4ca13f3c14d"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
use base64::prelude::*;
|
||||
use clap::{value_parser, Arg, Command};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use sev::firmware::host::*;
|
||||
use std::arch::x86_64::__cpuid;
|
||||
use std::fs::File;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::Write;
|
||||
|
||||
const ATTESTATION_POLICY_JSON: &str = "attestation_policy.json";
|
||||
const PCR_VALUES_JSON: &str = "pcr_values.json";
|
||||
|
||||
const EXTENDED_FAMILY_SHIFT: u32 = 20;
|
||||
const EXTENDED_MODEL_SHIFT: u32 = 16;
|
||||
const FAMILY_SHIFT: u32 = 8;
|
||||
@@ -123,7 +126,7 @@ fn main() {
|
||||
let policy: u64 = *matches.get_one::<u64>("policy").unwrap();
|
||||
let family_id = BASE64_STANDARD.encode(vec![0; 16]);
|
||||
let image_id = BASE64_STANDARD.encode(vec![0; 16]);
|
||||
let vmpl = 0;
|
||||
let vmpl = 2;
|
||||
let minimum_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
|
||||
let minimum_launch_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
|
||||
let require_author_key = false;
|
||||
@@ -169,10 +172,33 @@ fn main() {
|
||||
root_of_trust,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&computation).expect("Failed to serialize to JSON");
|
||||
let mut file = File::create(ATTESTATION_POLICY_JSON).expect("Failed to create file");
|
||||
file.write_all(json.as_bytes())
|
||||
.expect("Failed to write to file");
|
||||
let mut computation_value =
|
||||
serde_json::to_value(&computation).expect("Failed to convert computation to JSON");
|
||||
|
||||
// Read and parse the pcr_values.json file.
|
||||
let pcr_content = read_to_string(PCR_VALUES_JSON).expect("Failed to read pcr_values.json");
|
||||
let pcr_value: Value =
|
||||
serde_json::from_str(&pcr_content).expect("Failed to parse pcr_values.json");
|
||||
|
||||
// Merge the pcr_values into the main JSON object.
|
||||
if let Value::Object(ref mut main_map) = computation_value {
|
||||
if let Value::Object(pcr_map) = pcr_value {
|
||||
// The keys in pcr_map (e.g., "pcr_values") will be added
|
||||
main_map.extend(pcr_map);
|
||||
} else {
|
||||
eprintln!("{} is not a JSON object.", PCR_VALUES_JSON);
|
||||
}
|
||||
} else {
|
||||
eprintln!("The computed JSON is not an object.");
|
||||
}
|
||||
|
||||
// Serialize the merged JSON and write to file.
|
||||
let merged_json =
|
||||
serde_json::to_string_pretty(&computation_value).expect("Failed to serialize merged JSON");
|
||||
let mut file =
|
||||
File::create(ATTESTATION_POLICY_JSON).expect("Failed to create attestation policy file");
|
||||
file.write_all(merged_json.as_bytes())
|
||||
.expect("Failed to write merged JSON to file");
|
||||
|
||||
println!(
|
||||
"AttestationPolicy JSON has been written to {}",
|
||||
|
||||
@@ -67,7 +67,15 @@ export AGENT_GRPC_ATTESTATION_POLICY=./scripts/attestation_policy/attestation_po
|
||||
export AGENT_GRPC_ATTESTED_TLS=true
|
||||
|
||||
# Retrieve Attestation
|
||||
./build/cocos-cli attestation get '<report_data>'
|
||||
# Three different attestation reports can be retrieved:
|
||||
# - SEV-SNP with argument snp for attestation get command.
|
||||
./build/cocos-cli attestation get snp --tee '<report_data>'
|
||||
|
||||
# - vTPM with argument vtpm for attestation get command.
|
||||
./build/cocos-cli attestation get vtpm --vtpm '<vtpm_nonce>'
|
||||
|
||||
# - vTPM with SEV-SNP with argument snp-vtpm for attestation get command.
|
||||
./build/cocos-cli attestation get snp-vtpm --tee '<report_data>' --vtpm '<vtpm_nonce>'
|
||||
|
||||
# Validate Attestation
|
||||
# Product name must be Milan or Genoa
|
||||
|
||||
Reference in New Issue
Block a user