mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-08-07 15:26:26 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d34e9b894 | |||
| da5277c55a | |||
| e9e777f438 | |||
| 1cef95dfd1 | |||
| 5960b06126 | |||
| 636d3dcaa0 | |||
| 92f4f0535a | |||
| bf84f45306 | |||
| f5b67ca35b | |||
| 3bb0b2a315 | |||
| 434d58f890 | |||
| 32e2bfb881 | |||
| 26bf5dc643 | |||
| bda3968fdf | |||
| 90807d9576 | |||
| 94c169febb | |||
| 3102114ff3 | |||
| 5c60bc2a48 |
@@ -9,6 +9,7 @@ on:
|
||||
- "pkg/manager/*.pb.go"
|
||||
- "agent/agent.proto"
|
||||
- "agent/*.pb.go"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
with:
|
||||
repository: "buildroot/buildroot"
|
||||
path: buildroot
|
||||
ref: 2024.11-rc2
|
||||
ref: 2025.05-rc1
|
||||
|
||||
- name: Build hal
|
||||
run: |
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
mkdir coverage
|
||||
|
||||
- name: Run Agent tests
|
||||
run: go test --tags embed -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
|
||||
run: go test -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
|
||||
|
||||
- name: Run cli tests
|
||||
run: go test -v --race -covermode=atomic -coverprofile coverage/cli.out ./cli/...
|
||||
|
||||
+242
-25
@@ -384,6 +384,190 @@ func (x *AttestationResponse) GetFile() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type IMAMeasurementsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsRequest) Reset() {
|
||||
*x = IMAMeasurementsRequest{}
|
||||
mi := &file_agent_agent_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IMAMeasurementsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *IMAMeasurementsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IMAMeasurementsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*IMAMeasurementsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
type IMAMeasurementsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
Pcr10 []byte `protobuf:"bytes,2,opt,name=pcr10,proto3" json:"pcr10,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsResponse) Reset() {
|
||||
*x = IMAMeasurementsResponse{}
|
||||
mi := &file_agent_agent_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IMAMeasurementsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *IMAMeasurementsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IMAMeasurementsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*IMAMeasurementsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsResponse) GetFile() []byte {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IMAMeasurementsResponse) GetPcr10() []byte {
|
||||
if x != nil {
|
||||
return x.Pcr10
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AttestationResultRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
TokenNonce []byte `protobuf:"bytes,1,opt,name=tokenNonce,proto3" json:"tokenNonce,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
|
||||
}
|
||||
|
||||
func (x *AttestationResultRequest) Reset() {
|
||||
*x = AttestationResultRequest{}
|
||||
mi := &file_agent_agent_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AttestationResultRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AttestationResultRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AttestationResultRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AttestationResultRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AttestationResultRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *AttestationResultRequest) GetTokenNonce() []byte {
|
||||
if x != nil {
|
||||
return x.TokenNonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AttestationResultRequest) GetType() int32 {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AttestationResultResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AttestationResultResponse) Reset() {
|
||||
*x = AttestationResultResponse{}
|
||||
mi := &file_agent_agent_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AttestationResultResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AttestationResultResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AttestationResultResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_agent_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AttestationResultResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AttestationResultResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *AttestationResultResponse) GetFile() []byte {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_agent_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_agent_proto_rawDesc = string([]byte{
|
||||
@@ -412,7 +596,21 @@ var file_agent_agent_proto_rawDesc = string([]byte{
|
||||
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,
|
||||
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61,
|
||||
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x22, 0x43, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x73, 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, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x70, 0x63, 0x72, 0x31, 0x30, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
|
||||
0x70, 0x63, 0x72, 0x31, 0x30, 0x22, 0x4e, 0x0a, 0x18, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x2f, 0x0a, 0x19, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 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, 0x32, 0xad, 0x03, 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,
|
||||
@@ -428,7 +626,18 @@ var file_agent_agent_proto_rawDesc = string([]byte{
|
||||
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,
|
||||
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x54, 0x0a, 0x0f, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61,
|
||||
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x49, 0x4d, 0x41, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x58, 0x0a, 0x11,
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
|
||||
0x74, 0x12, 0x1f, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
@@ -444,31 +653,39 @@ func file_agent_agent_proto_rawDescGZIP() []byte {
|
||||
return file_agent_agent_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_agent_agent_proto_goTypes = []any{
|
||||
(*AlgoRequest)(nil), // 0: agent.AlgoRequest
|
||||
(*AlgoResponse)(nil), // 1: agent.AlgoResponse
|
||||
(*DataRequest)(nil), // 2: agent.DataRequest
|
||||
(*DataResponse)(nil), // 3: agent.DataResponse
|
||||
(*ResultRequest)(nil), // 4: agent.ResultRequest
|
||||
(*ResultResponse)(nil), // 5: agent.ResultResponse
|
||||
(*AttestationRequest)(nil), // 6: agent.AttestationRequest
|
||||
(*AttestationResponse)(nil), // 7: agent.AttestationResponse
|
||||
(*AlgoRequest)(nil), // 0: agent.AlgoRequest
|
||||
(*AlgoResponse)(nil), // 1: agent.AlgoResponse
|
||||
(*DataRequest)(nil), // 2: agent.DataRequest
|
||||
(*DataResponse)(nil), // 3: agent.DataResponse
|
||||
(*ResultRequest)(nil), // 4: agent.ResultRequest
|
||||
(*ResultResponse)(nil), // 5: agent.ResultResponse
|
||||
(*AttestationRequest)(nil), // 6: agent.AttestationRequest
|
||||
(*AttestationResponse)(nil), // 7: agent.AttestationResponse
|
||||
(*IMAMeasurementsRequest)(nil), // 8: agent.IMAMeasurementsRequest
|
||||
(*IMAMeasurementsResponse)(nil), // 9: agent.IMAMeasurementsResponse
|
||||
(*AttestationResultRequest)(nil), // 10: agent.AttestationResultRequest
|
||||
(*AttestationResultResponse)(nil), // 11: agent.AttestationResultResponse
|
||||
}
|
||||
var file_agent_agent_proto_depIdxs = []int32{
|
||||
0, // 0: agent.AgentService.Algo:input_type -> agent.AlgoRequest
|
||||
2, // 1: agent.AgentService.Data:input_type -> agent.DataRequest
|
||||
4, // 2: agent.AgentService.Result:input_type -> agent.ResultRequest
|
||||
6, // 3: agent.AgentService.Attestation:input_type -> agent.AttestationRequest
|
||||
1, // 4: agent.AgentService.Algo:output_type -> agent.AlgoResponse
|
||||
3, // 5: agent.AgentService.Data:output_type -> agent.DataResponse
|
||||
5, // 6: agent.AgentService.Result:output_type -> agent.ResultResponse
|
||||
7, // 7: agent.AgentService.Attestation:output_type -> agent.AttestationResponse
|
||||
4, // [4:8] is the sub-list for method output_type
|
||||
0, // [0:4] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
0, // 0: agent.AgentService.Algo:input_type -> agent.AlgoRequest
|
||||
2, // 1: agent.AgentService.Data:input_type -> agent.DataRequest
|
||||
4, // 2: agent.AgentService.Result:input_type -> agent.ResultRequest
|
||||
6, // 3: agent.AgentService.Attestation:input_type -> agent.AttestationRequest
|
||||
8, // 4: agent.AgentService.IMAMeasurements:input_type -> agent.IMAMeasurementsRequest
|
||||
10, // 5: agent.AgentService.AttestationResult:input_type -> agent.AttestationResultRequest
|
||||
1, // 6: agent.AgentService.Algo:output_type -> agent.AlgoResponse
|
||||
3, // 7: agent.AgentService.Data:output_type -> agent.DataResponse
|
||||
5, // 8: agent.AgentService.Result:output_type -> agent.ResultResponse
|
||||
7, // 9: agent.AgentService.Attestation:output_type -> agent.AttestationResponse
|
||||
9, // 10: agent.AgentService.IMAMeasurements:output_type -> agent.IMAMeasurementsResponse
|
||||
11, // 11: agent.AgentService.AttestationResult:output_type -> agent.AttestationResultResponse
|
||||
6, // [6:12] is the sub-list for method output_type
|
||||
0, // [0:6] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_agent_agent_proto_init() }
|
||||
@@ -482,7 +699,7 @@ func file_agent_agent_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -12,6 +12,8 @@ service AgentService {
|
||||
rpc Data(stream DataRequest) returns (DataResponse) {}
|
||||
rpc Result(ResultRequest) returns (stream ResultResponse) {}
|
||||
rpc Attestation(AttestationRequest) returns (stream AttestationResponse) {}
|
||||
rpc IMAMeasurements(IMAMeasurementsRequest) returns (stream IMAMeasurementsResponse) {}
|
||||
rpc AttestationResult(AttestationResultRequest) returns (AttestationResultResponse) {}
|
||||
}
|
||||
|
||||
message AlgoRequest {
|
||||
@@ -44,3 +46,19 @@ message AttestationRequest {
|
||||
message AttestationResponse {
|
||||
bytes file = 1;
|
||||
}
|
||||
|
||||
message IMAMeasurementsRequest {
|
||||
}
|
||||
|
||||
message IMAMeasurementsResponse {
|
||||
bytes file = 1;
|
||||
bytes pcr10 = 2;
|
||||
}
|
||||
|
||||
message AttestationResultRequest{
|
||||
bytes tokenNonce = 1; // Should be less or equal 32 bytes
|
||||
int32 type = 3;
|
||||
}
|
||||
message AttestationResultResponse{
|
||||
bytes file = 1;
|
||||
}
|
||||
|
||||
+85
-5
@@ -22,10 +22,12 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
|
||||
AgentService_Data_FullMethodName = "/agent.AgentService/Data"
|
||||
AgentService_Result_FullMethodName = "/agent.AgentService/Result"
|
||||
AgentService_Attestation_FullMethodName = "/agent.AgentService/Attestation"
|
||||
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
|
||||
AgentService_Data_FullMethodName = "/agent.AgentService/Data"
|
||||
AgentService_Result_FullMethodName = "/agent.AgentService/Result"
|
||||
AgentService_Attestation_FullMethodName = "/agent.AgentService/Attestation"
|
||||
AgentService_IMAMeasurements_FullMethodName = "/agent.AgentService/IMAMeasurements"
|
||||
AgentService_AttestationResult_FullMethodName = "/agent.AgentService/AttestationResult"
|
||||
)
|
||||
|
||||
// AgentServiceClient is the client API for AgentService service.
|
||||
@@ -36,6 +38,8 @@ type AgentServiceClient interface {
|
||||
Data(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[DataRequest, DataResponse], error)
|
||||
Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ResultResponse], error)
|
||||
Attestation(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AttestationResponse], error)
|
||||
IMAMeasurements(ctx context.Context, in *IMAMeasurementsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[IMAMeasurementsResponse], error)
|
||||
AttestationResult(ctx context.Context, in *AttestationResultRequest, opts ...grpc.CallOption) (*AttestationResultResponse, error)
|
||||
}
|
||||
|
||||
type agentServiceClient struct {
|
||||
@@ -110,6 +114,35 @@ func (c *agentServiceClient) Attestation(ctx context.Context, in *AttestationReq
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentService_AttestationClient = grpc.ServerStreamingClient[AttestationResponse]
|
||||
|
||||
func (c *agentServiceClient) IMAMeasurements(ctx context.Context, in *IMAMeasurementsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[IMAMeasurementsResponse], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &AgentService_ServiceDesc.Streams[4], AgentService_IMAMeasurements_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[IMAMeasurementsRequest, IMAMeasurementsResponse]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentService_IMAMeasurementsClient = grpc.ServerStreamingClient[IMAMeasurementsResponse]
|
||||
|
||||
func (c *agentServiceClient) AttestationResult(ctx context.Context, in *AttestationResultRequest, opts ...grpc.CallOption) (*AttestationResultResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AttestationResultResponse)
|
||||
err := c.cc.Invoke(ctx, AgentService_AttestationResult_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AgentServiceServer is the server API for AgentService service.
|
||||
// All implementations must embed UnimplementedAgentServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -118,6 +151,8 @@ type AgentServiceServer interface {
|
||||
Data(grpc.ClientStreamingServer[DataRequest, DataResponse]) error
|
||||
Result(*ResultRequest, grpc.ServerStreamingServer[ResultResponse]) error
|
||||
Attestation(*AttestationRequest, grpc.ServerStreamingServer[AttestationResponse]) error
|
||||
IMAMeasurements(*IMAMeasurementsRequest, grpc.ServerStreamingServer[IMAMeasurementsResponse]) error
|
||||
AttestationResult(context.Context, *AttestationResultRequest) (*AttestationResultResponse, error)
|
||||
mustEmbedUnimplementedAgentServiceServer()
|
||||
}
|
||||
|
||||
@@ -140,6 +175,12 @@ func (UnimplementedAgentServiceServer) Result(*ResultRequest, grpc.ServerStreami
|
||||
func (UnimplementedAgentServiceServer) Attestation(*AttestationRequest, grpc.ServerStreamingServer[AttestationResponse]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Attestation not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) IMAMeasurements(*IMAMeasurementsRequest, grpc.ServerStreamingServer[IMAMeasurementsResponse]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method IMAMeasurements not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) AttestationResult(context.Context, *AttestationResultRequest) (*AttestationResultResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AttestationResult not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {}
|
||||
func (UnimplementedAgentServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -197,13 +238,47 @@ func _AgentService_Attestation_Handler(srv interface{}, stream grpc.ServerStream
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentService_AttestationServer = grpc.ServerStreamingServer[AttestationResponse]
|
||||
|
||||
func _AgentService_IMAMeasurements_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(IMAMeasurementsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(AgentServiceServer).IMAMeasurements(m, &grpc.GenericServerStream[IMAMeasurementsRequest, IMAMeasurementsResponse]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type AgentService_IMAMeasurementsServer = grpc.ServerStreamingServer[IMAMeasurementsResponse]
|
||||
|
||||
func _AgentService_AttestationResult_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AttestationResultRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AgentServiceServer).AttestationResult(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AgentService_AttestationResult_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AgentServiceServer).AttestationResult(ctx, req.(*AttestationResultRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AgentService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "agent.AgentService",
|
||||
HandlerType: (*AgentServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "AttestationResult",
|
||||
Handler: _AgentService_AttestationResult_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Algo",
|
||||
@@ -225,6 +300,11 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _AgentService_Attestation_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "IMAMeasurements",
|
||||
Handler: _AgentService_IMAMeasurements_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "agent/agent.proto",
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ func (b *binary) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("error stopping algorithm: %v", err)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,10 @@ func (p *python) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := p.cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("error stopping algorithm: %v", err)
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ func (w *wasm) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if w.cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("error stopping algorithm: %v", err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
@@ -71,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.TeeNonce, req.VtpmNonce, config.AttestationType(req.AttType))
|
||||
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, attestation.PlatformType(req.AttType))
|
||||
if err != nil {
|
||||
return attestationRes{}, err
|
||||
}
|
||||
@@ -79,3 +79,33 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return attestationRes{File: file}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func imaMeasurementsEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(imaMeasurementsReq)
|
||||
|
||||
if err := req.validate(); err != nil {
|
||||
return imaMeasurementsRes{}, err
|
||||
}
|
||||
file, pcr10, err := svc.IMAMeasurements(ctx)
|
||||
if err != nil {
|
||||
return imaMeasurementsRes{}, err
|
||||
}
|
||||
|
||||
return imaMeasurementsRes{File: file, PCR10: pcr10}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func attestationResultEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(FetchAttestationResultReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return fetchAttestationResultRes{}, err
|
||||
}
|
||||
file, err := svc.AttestationResult(ctx, req.tokenNonce, attestation.PlatformType(req.AttType))
|
||||
if err != nil {
|
||||
return fetchAttestationResultRes{}, err
|
||||
}
|
||||
return fetchAttestationResultRes{File: file}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -142,11 +143,11 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
|
||||
},
|
||||
{
|
||||
name: "Service Error",
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.SNP},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
@@ -172,3 +173,55 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationResultEndpoint(t *testing.T) {
|
||||
svc := new(mocks.Service)
|
||||
tests := []struct {
|
||||
name string
|
||||
req FetchAttestationResultReq
|
||||
mockErr error
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
req: FetchAttestationResultReq{tokenNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.AzureToken},
|
||||
mockErr: nil,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "Service Error",
|
||||
req: FetchAttestationResultReq{tokenNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: attestation.AzureToken},
|
||||
mockErr: errors.New("mock failure"),
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Only call service mock if validation is expected to pass
|
||||
if err := tt.req.validate(); err == nil {
|
||||
svc.On("AttestationResult", mock.Anything, tt.req.tokenNonce, attestation.PlatformType(tt.req.AttType)).
|
||||
Return([]byte("mock file"), tt.mockErr).Once()
|
||||
}
|
||||
|
||||
endpoint := attestationResultEndpoint(svc)
|
||||
res, err := endpoint(context.Background(), tt.req)
|
||||
|
||||
if (err != nil) != tt.expectedErr {
|
||||
t.Errorf("attestationResultEndpoint() error = %v, expectedErr %v", err, tt.expectedErr)
|
||||
}
|
||||
|
||||
if !tt.expectedErr {
|
||||
r, ok := res.(fetchAttestationResultRes)
|
||||
if !ok {
|
||||
t.Errorf("attestationResultEndpoint() returned unexpected type %T", res)
|
||||
}
|
||||
if string(r.File) != "mock file" {
|
||||
t.Errorf("expected file content 'mock file', got %s", r.File)
|
||||
}
|
||||
}
|
||||
|
||||
svc.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ package grpc
|
||||
import (
|
||||
"errors"
|
||||
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
@@ -44,14 +44,34 @@ func (req resultReq) validate() error {
|
||||
type attestationReq struct {
|
||||
TeeNonce [quoteprovider.Nonce]byte
|
||||
VtpmNonce [vtpm.Nonce]byte
|
||||
AttType config.AttestationType
|
||||
AttType attestation.PlatformType
|
||||
}
|
||||
|
||||
type FetchAttestationResultReq struct {
|
||||
tokenNonce [vtpm.Nonce]byte
|
||||
AttType attestation.PlatformType
|
||||
}
|
||||
|
||||
func (req attestationReq) validate() error {
|
||||
switch req.AttType {
|
||||
case config.SNP, config.VTPM, config.SNPvTPM:
|
||||
return validateAttestationType(req.AttType)
|
||||
}
|
||||
|
||||
func (req FetchAttestationResultReq) validate() error {
|
||||
return validateAttestationType(req.AttType)
|
||||
}
|
||||
|
||||
func validateAttestationType(attType attestation.PlatformType) error {
|
||||
switch attType {
|
||||
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.AzureToken:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid attestation type in attestation request")
|
||||
return errors.New("invalid attestation type")
|
||||
}
|
||||
}
|
||||
|
||||
type imaMeasurementsReq struct{}
|
||||
|
||||
func (req imaMeasurementsReq) validate() error {
|
||||
// No request parameters to validate, so no validation logic needed
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,3 +13,12 @@ type resultRes struct {
|
||||
type attestationRes struct {
|
||||
File []byte
|
||||
}
|
||||
|
||||
type imaMeasurementsRes struct {
|
||||
File []byte
|
||||
PCR10 []byte
|
||||
}
|
||||
|
||||
type fetchAttestationResultRes struct {
|
||||
File []byte `protobuf:"bytes,1,opt,name=AttestationResult,proto3" json:"AttestationResult,omitempty"`
|
||||
}
|
||||
|
||||
+109
-9
@@ -8,10 +8,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -25,17 +26,20 @@ const (
|
||||
)
|
||||
|
||||
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")
|
||||
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")
|
||||
ErrTokenNonceLength = errors.New("malformed token nonce, expect less or equal to 32 bytes")
|
||||
)
|
||||
|
||||
var _ agent.AgentServiceServer = (*grpcServer)(nil)
|
||||
|
||||
type grpcServer struct {
|
||||
algo grpc.Handler
|
||||
data grpc.Handler
|
||||
result grpc.Handler
|
||||
attestation grpc.Handler
|
||||
algo grpc.Handler
|
||||
data grpc.Handler
|
||||
result grpc.Handler
|
||||
attestation grpc.Handler
|
||||
imaMeasurements grpc.Handler
|
||||
attestationResult grpc.Handler
|
||||
agent.UnimplementedAgentServiceServer
|
||||
}
|
||||
|
||||
@@ -62,6 +66,16 @@ func NewServer(svc agent.Service) agent.AgentServiceServer {
|
||||
decodeAttestationRequest,
|
||||
encodeAttestationResponse,
|
||||
),
|
||||
imaMeasurements: grpc.NewServer(
|
||||
imaMeasurementsEndpoint(svc),
|
||||
decodeIMAMeasurementsRequest,
|
||||
encodeIMAMeasurementsResponse,
|
||||
),
|
||||
attestationResult: grpc.NewServer(
|
||||
attestationResultEndpoint(svc),
|
||||
decodeAttestationResultRequest,
|
||||
encodeAttestationResultResponse,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,12 +126,12 @@ func decodeAttestationRequest(_ context.Context, grpcReq interface{}) (interface
|
||||
}
|
||||
|
||||
if len(req.VtpmNonce) > vtpm.Nonce {
|
||||
return nil, ErrVTpmNonceLength
|
||||
return nil, ErrVTPMNonceLength
|
||||
}
|
||||
|
||||
copy(reportData[:], req.TeeNonce)
|
||||
copy(nonce[:], req.VtpmNonce)
|
||||
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: config.AttestationType(req.Type)}, nil
|
||||
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: attestation.PlatformType(req.Type)}, nil
|
||||
}
|
||||
|
||||
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
@@ -127,6 +141,25 @@ func encodeAttestationResponse(_ context.Context, response interface{}) (interfa
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeAttestationResultResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(fetchAttestationResultRes)
|
||||
return &agent.AttestationResultResponse{
|
||||
File: res.File,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeAttestationResultRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.AttestationResultRequest)
|
||||
var nonce [vtpm.Nonce]byte
|
||||
|
||||
if len(req.TokenNonce) > vtpm.Nonce {
|
||||
return nil, ErrVTPMNonceLength
|
||||
}
|
||||
|
||||
copy(nonce[:], req.TokenNonce)
|
||||
return FetchAttestationResultReq{tokenNonce: nonce, AttType: attestation.PlatformType(req.Type)}, nil
|
||||
}
|
||||
|
||||
// Algo implements agent.AgentServiceServer.
|
||||
func (s *grpcServer) Algo(stream agent.AgentService_AlgoServer) error {
|
||||
var algoFile, reqFile []byte
|
||||
@@ -235,3 +268,70 @@ func (s *grpcServer) Attestation(req *agent.AttestationRequest, stream agent.Age
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeIMAMeasurementsRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
return imaMeasurementsReq{}, nil
|
||||
}
|
||||
|
||||
func encodeIMAMeasurementsResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
res := response.(imaMeasurementsRes)
|
||||
return &agent.IMAMeasurementsResponse{
|
||||
File: res.File,
|
||||
Pcr10: res.PCR10,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) IMAMeasurements(req *agent.IMAMeasurementsRequest, stream agent.AgentService_IMAMeasurementsServer) error {
|
||||
_, res, err := s.imaMeasurements.ServeGRPC(stream.Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rr := res.(*agent.IMAMeasurementsResponse)
|
||||
|
||||
if err := stream.SetHeader(metadata.New(map[string]string{FileSizeKey: strconv.Itoa(len(rr.File))})); err != nil {
|
||||
return status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
imaBuff := bytes.NewBuffer(rr.File)
|
||||
pcr10Buff := bytes.NewBuffer(rr.Pcr10)
|
||||
|
||||
imaResBuff := make([]byte, bufferSize)
|
||||
pcr10ResBuff := make([]byte, bufferSize)
|
||||
|
||||
for {
|
||||
nIma, errIma := imaBuff.Read(imaResBuff)
|
||||
if errIma != nil && errIma != io.EOF {
|
||||
return status.Error(codes.Internal, errIma.Error())
|
||||
}
|
||||
|
||||
nPcr, errPcr := pcr10Buff.Read(pcr10ResBuff)
|
||||
if errPcr != nil && errPcr != io.EOF {
|
||||
return status.Error(codes.Internal, errPcr.Error())
|
||||
}
|
||||
|
||||
if nIma == 0 && errIma == io.EOF &&
|
||||
nPcr == 0 && errPcr == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err := stream.Send(&agent.IMAMeasurementsResponse{File: imaResBuff[:nIma], Pcr10: pcr10ResBuff[:nPcr]}); err != nil {
|
||||
return status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) AttestationResult(ctx context.Context, req *agent.AttestationResultRequest) (*agent.AttestationResultResponse, error) {
|
||||
_, res, err := s.attestationResult.ServeGRPC(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr, ok := res.(*agent.AttestationResultResponse)
|
||||
|
||||
if !ok {
|
||||
return nil, status.Error(codes.Internal, "failed to cast response to FetchAttestationResultResponse")
|
||||
}
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ 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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc"
|
||||
@@ -156,7 +156,7 @@ func TestAttestation(t *testing.T) {
|
||||
|
||||
reportData := [quoteprovider.Nonce]byte{}
|
||||
vtpmNonce := [vtpm.Nonce]byte{}
|
||||
attestationType := config.SNP
|
||||
attestationType := attestation.SNP
|
||||
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
|
||||
|
||||
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:]}, mockStream)
|
||||
@@ -165,6 +165,21 @@ func TestAttestation(t *testing.T) {
|
||||
mockService.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestAttestationResult(t *testing.T) {
|
||||
mockService := new(mocks.Service)
|
||||
server := NewServer(mockService)
|
||||
|
||||
vtpmNonce := [vtpm.Nonce]byte{}
|
||||
attestationType := attestation.SNP
|
||||
mockService.On("AttestationResult", mock.Anything, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
|
||||
|
||||
resp, err := server.AttestationResult(context.Background(), &agent.AttestationResultRequest{TokenNonce: vtpmNonce[:]})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("attestation data"), resp.File)
|
||||
|
||||
mockService.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestDecodeAlgoRequest(t *testing.T) {
|
||||
req := &agent.AlgoRequest{Algorithm: []byte("algo"), Requirements: []byte("req")}
|
||||
decoded, err := decodeAlgoRequest(context.Background(), req)
|
||||
@@ -216,3 +231,17 @@ func TestEncodeAttestationResponse(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &agent.AttestationResponse{File: []byte("attestation")}, encoded)
|
||||
}
|
||||
|
||||
func TestEncodeAttestationResultResponse(t *testing.T) {
|
||||
encoded, err := encodeAttestationResultResponse(context.Background(), fetchAttestationResultRes{File: []byte("attestation")})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &agent.AttestationResultResponse{File: []byte("attestation")}, encoded)
|
||||
}
|
||||
|
||||
func TestDecodeAttestationResultRequest(t *testing.T) {
|
||||
nonce := [vtpm.Nonce]byte{}
|
||||
req := &agent.AttestationResultRequest{TokenNonce: nonce[:]}
|
||||
decoded, err := decodeAttestationResultRequest(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, FetchAttestationResultReq{tokenNonce: nonce}, decoded)
|
||||
}
|
||||
|
||||
+28
-2
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
@@ -106,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 [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) (response []byte, err error) {
|
||||
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) (response []byte, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method Attestation took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
@@ -118,3 +118,29 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quotep
|
||||
|
||||
return lm.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) IMAMeasurements(ctx context.Context) (file []byte, pcr10 []byte, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method IMAMeasurements took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.IMAMeasurements(ctx)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) (response []byte, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method AttestationResult took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
lm.logger.Warn(fmt.Sprintf("%s with error: %s", message, err))
|
||||
return
|
||||
}
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.AttestationResult(ctx, nonce, attType)
|
||||
}
|
||||
|
||||
+20
-2
@@ -12,7 +12,7 @@ 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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
@@ -92,7 +92,7 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
|
||||
return ms.svc.Result(ctx)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "attestation").Add(1)
|
||||
ms.latency.With("method", "attestation").Observe(time.Since(begin).Seconds())
|
||||
@@ -100,3 +100,21 @@ func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quotep
|
||||
|
||||
return ms.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "attestation_result").Add(1)
|
||||
ms.latency.With("method", "attestation_result").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.AttestationResult(ctx, nonce, attType)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "imameasurements").Add(1)
|
||||
ms.latency.With("method", "imameasurements").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.IMAMeasurements(ctx)
|
||||
}
|
||||
|
||||
+207
-125
@@ -431,6 +431,7 @@ type ClientStreamMessage struct {
|
||||
// *ClientStreamMessage_StopComputationRes
|
||||
// *ClientStreamMessage_AgentStateRes
|
||||
// *ClientStreamMessage_VTPMattestationReport
|
||||
// *ClientStreamMessage_AzureAttestationResult
|
||||
Message isClientStreamMessage_Message `protobuf_oneof:"message"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -527,6 +528,15 @@ func (x *ClientStreamMessage) GetVTPMattestationReport() *AttestationResponse {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ClientStreamMessage) GetAzureAttestationResult() *AzureAttestationResponse {
|
||||
if x != nil {
|
||||
if x, ok := x.Message.(*ClientStreamMessage_AzureAttestationResult); ok {
|
||||
return x.AzureAttestationResult
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isClientStreamMessage_Message interface {
|
||||
isClientStreamMessage_Message()
|
||||
}
|
||||
@@ -555,6 +565,10 @@ type ClientStreamMessage_VTPMattestationReport struct {
|
||||
VTPMattestationReport *AttestationResponse `protobuf:"bytes,6,opt,name=vTPMattestationReport,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ClientStreamMessage_AzureAttestationResult struct {
|
||||
AzureAttestationResult *AzureAttestationResponse `protobuf:"bytes,7,opt,name=azureAttestationResult,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ClientStreamMessage_AgentLog) isClientStreamMessage_Message() {}
|
||||
|
||||
func (*ClientStreamMessage_AgentEvent) isClientStreamMessage_Message() {}
|
||||
@@ -567,6 +581,8 @@ func (*ClientStreamMessage_AgentStateRes) isClientStreamMessage_Message() {}
|
||||
|
||||
func (*ClientStreamMessage_VTPMattestationReport) isClientStreamMessage_Message() {}
|
||||
|
||||
func (*ClientStreamMessage_AzureAttestationResult) isClientStreamMessage_Message() {}
|
||||
|
||||
type ServerStreamMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Message:
|
||||
@@ -1193,6 +1209,58 @@ func (x *AttestationResponse) GetCertSerialNumber() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type AzureAttestationResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
CertSerialNumber string `protobuf:"bytes,2,opt,name=certSerialNumber,proto3" json:"certSerialNumber,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AzureAttestationResponse) Reset() {
|
||||
*x = AzureAttestationResponse{}
|
||||
mi := &file_agent_cvms_cvms_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AzureAttestationResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AzureAttestationResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AzureAttestationResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_cvms_cvms_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AzureAttestationResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AzureAttestationResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_cvms_cvms_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *AzureAttestationResponse) GetFile() []byte {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AzureAttestationResponse) GetCertSerialNumber() string {
|
||||
if x != nil {
|
||||
return x.CertSerialNumber
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_agent_cvms_cvms_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
|
||||
@@ -1243,7 +1311,7 @@ var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
|
||||
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x93, 0x03, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xed, 0x03, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65,
|
||||
0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
|
||||
0x2d, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4c,
|
||||
@@ -1268,92 +1336,103 @@ var file_agent_cvms_cvms_proto_rawDesc = string([]byte{
|
||||
0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x15, 0x76, 0x54, 0x50,
|
||||
0x4d, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xca, 0x02,
|
||||
0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43,
|
||||
0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x76,
|
||||
0x6d, 0x73, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x48,
|
||||
0x00, 0x52, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12,
|
||||
0x31, 0x0a, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x17, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x12, 0x41, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x76,
|
||||
0x6d, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63,
|
||||
0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65,
|
||||
0x71, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
|
||||
0x65, 0x71, 0x12, 0x3b, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
|
||||
0x52, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x76, 0x6d, 0x73,
|
||||
0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x48, 0x00,
|
||||
0x52, 0x0d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x42,
|
||||
0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x0c, 0x52,
|
||||
0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64,
|
||||
0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x06, 0x69, 0x73, 0x4c, 0x61, 0x73, 0x74, 0x22, 0xaa, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6d,
|
||||
0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73,
|
||||
0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x44, 0x61,
|
||||
0x74, 0x61, 0x73, 0x65, 0x74, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12,
|
||||
0x2d, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69,
|
||||
0x74, 0x68, 0x6d, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x3f,
|
||||
0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65,
|
||||
0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e,
|
||||
0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x52, 0x0f,
|
||||
0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x12,
|
||||
0x34, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65,
|
||||
0x79, 0x22, 0x53, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x39, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69,
|
||||
0x74, 0x68, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b,
|
||||
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65,
|
||||
0x79, 0x22, 0xe5, 0x01, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a,
|
||||
0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x46,
|
||||
0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x61,
|
||||
0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67,
|
||||
0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f,
|
||||
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74,
|
||||
0x65, 0x64, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x74,
|
||||
0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54, 0x6c, 0x73, 0x22, 0x55, 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, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69,
|
||||
0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
|
||||
0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,
|
||||
0x32, 0x50, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x07, 0x50,
|
||||
0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x43, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x1a, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01,
|
||||
0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x63, 0x76, 0x6d, 0x73, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x72, 0x74, 0x12, 0x58, 0x0a, 0x16, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x07, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x48, 0x00, 0x52, 0x16, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x09, 0x0a, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xca, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
|
||||
0x38, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x52, 0x75, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x75, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x75, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x76, 0x6d, 0x73,
|
||||
0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x48, 0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x41, 0x0a, 0x0f,
|
||||
0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53, 0x74, 0x6f,
|
||||
0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f,
|
||||
0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x3b, 0x0a, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x0d, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x3b, 0x0a, 0x0d,
|
||||
0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x63,
|
||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
|
||||
0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x0c, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x43,
|
||||
0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f,
|
||||
0x6c, 0x61, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4c, 0x61,
|
||||
0x73, 0x74, 0x22, 0xaa, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b,
|
||||
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29,
|
||||
0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x0d, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x52,
|
||||
0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x09, 0x61, 0x6c, 0x67,
|
||||
0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63,
|
||||
0x76, 0x6d, 0x73, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x09, 0x61,
|
||||
0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x3f, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75,
|
||||
0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x12, 0x34, 0x0a, 0x0c, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x11, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
|
||||
0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65,
|
||||
0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0x53, 0x0a, 0x07, 0x44,
|
||||
0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73,
|
||||
0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65,
|
||||
0x72, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x22, 0x39, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73,
|
||||
0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0xe5, 0x01, 0x0a, 0x0b,
|
||||
0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12,
|
||||
0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08,
|
||||
0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a,
|
||||
0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x46,
|
||||
0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
|
||||
0x12, 0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6c, 0x73,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64,
|
||||
0x54, 0x6c, 0x73, 0x22, 0x55, 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, 0x12, 0x2a,
|
||||
0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62,
|
||||
0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65,
|
||||
0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x5a, 0x0a, 0x18, 0x61, 0x7a,
|
||||
0x75, 0x72, 0x65, 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, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x65,
|
||||
0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x65, 0x72, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c,
|
||||
0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0x45, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x19, 0x2e, 0x63,
|
||||
0x76, 0x6d, 0x73, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x19, 0x2e, 0x63, 0x76, 0x6d, 0x73, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x63, 0x76,
|
||||
0x6d, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
@@ -1368,52 +1447,54 @@ func file_agent_cvms_cvms_proto_rawDescGZIP() []byte {
|
||||
return file_agent_cvms_cvms_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||
var file_agent_cvms_cvms_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
||||
var file_agent_cvms_cvms_proto_goTypes = []any{
|
||||
(*AgentStateReq)(nil), // 0: cvms.AgentStateReq
|
||||
(*AgentStateRes)(nil), // 1: cvms.AgentStateRes
|
||||
(*StopComputation)(nil), // 2: cvms.StopComputation
|
||||
(*StopComputationResponse)(nil), // 3: cvms.StopComputationResponse
|
||||
(*RunResponse)(nil), // 4: cvms.RunResponse
|
||||
(*AgentEvent)(nil), // 5: cvms.AgentEvent
|
||||
(*AgentLog)(nil), // 6: cvms.AgentLog
|
||||
(*ClientStreamMessage)(nil), // 7: cvms.ClientStreamMessage
|
||||
(*ServerStreamMessage)(nil), // 8: cvms.ServerStreamMessage
|
||||
(*DisconnectReq)(nil), // 9: cvms.DisconnectReq
|
||||
(*RunReqChunks)(nil), // 10: cvms.RunReqChunks
|
||||
(*ComputationRunReq)(nil), // 11: cvms.ComputationRunReq
|
||||
(*ResultConsumer)(nil), // 12: cvms.ResultConsumer
|
||||
(*Dataset)(nil), // 13: cvms.Dataset
|
||||
(*Algorithm)(nil), // 14: cvms.Algorithm
|
||||
(*AgentConfig)(nil), // 15: cvms.AgentConfig
|
||||
(*AttestationResponse)(nil), // 16: cvms.AttestationResponse
|
||||
(*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp
|
||||
(*AgentStateReq)(nil), // 0: cvms.AgentStateReq
|
||||
(*AgentStateRes)(nil), // 1: cvms.AgentStateRes
|
||||
(*StopComputation)(nil), // 2: cvms.StopComputation
|
||||
(*StopComputationResponse)(nil), // 3: cvms.StopComputationResponse
|
||||
(*RunResponse)(nil), // 4: cvms.RunResponse
|
||||
(*AgentEvent)(nil), // 5: cvms.AgentEvent
|
||||
(*AgentLog)(nil), // 6: cvms.AgentLog
|
||||
(*ClientStreamMessage)(nil), // 7: cvms.ClientStreamMessage
|
||||
(*ServerStreamMessage)(nil), // 8: cvms.ServerStreamMessage
|
||||
(*DisconnectReq)(nil), // 9: cvms.DisconnectReq
|
||||
(*RunReqChunks)(nil), // 10: cvms.RunReqChunks
|
||||
(*ComputationRunReq)(nil), // 11: cvms.ComputationRunReq
|
||||
(*ResultConsumer)(nil), // 12: cvms.ResultConsumer
|
||||
(*Dataset)(nil), // 13: cvms.Dataset
|
||||
(*Algorithm)(nil), // 14: cvms.Algorithm
|
||||
(*AgentConfig)(nil), // 15: cvms.AgentConfig
|
||||
(*AttestationResponse)(nil), // 16: cvms.AttestationResponse
|
||||
(*AzureAttestationResponse)(nil), // 17: cvms.azureAttestationResponse
|
||||
(*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp
|
||||
}
|
||||
var file_agent_cvms_cvms_proto_depIdxs = []int32{
|
||||
17, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
17, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
|
||||
18, // 0: cvms.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
18, // 1: cvms.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
|
||||
6, // 2: cvms.ClientStreamMessage.agent_log:type_name -> cvms.AgentLog
|
||||
5, // 3: cvms.ClientStreamMessage.agent_event:type_name -> cvms.AgentEvent
|
||||
4, // 4: cvms.ClientStreamMessage.run_res:type_name -> cvms.RunResponse
|
||||
3, // 5: cvms.ClientStreamMessage.stopComputationRes:type_name -> cvms.StopComputationResponse
|
||||
1, // 6: cvms.ClientStreamMessage.agentStateRes:type_name -> cvms.AgentStateRes
|
||||
16, // 7: cvms.ClientStreamMessage.vTPMattestationReport:type_name -> cvms.AttestationResponse
|
||||
10, // 8: cvms.ServerStreamMessage.runReqChunks:type_name -> cvms.RunReqChunks
|
||||
11, // 9: cvms.ServerStreamMessage.runReq:type_name -> cvms.ComputationRunReq
|
||||
2, // 10: cvms.ServerStreamMessage.stopComputation:type_name -> cvms.StopComputation
|
||||
0, // 11: cvms.ServerStreamMessage.agentStateReq:type_name -> cvms.AgentStateReq
|
||||
9, // 12: cvms.ServerStreamMessage.disconnectReq:type_name -> cvms.DisconnectReq
|
||||
13, // 13: cvms.ComputationRunReq.datasets:type_name -> cvms.Dataset
|
||||
14, // 14: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm
|
||||
12, // 15: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer
|
||||
15, // 16: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig
|
||||
7, // 17: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
|
||||
8, // 18: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
|
||||
18, // [18:19] is the sub-list for method output_type
|
||||
17, // [17:18] is the sub-list for method input_type
|
||||
17, // [17:17] is the sub-list for extension type_name
|
||||
17, // [17:17] is the sub-list for extension extendee
|
||||
0, // [0:17] is the sub-list for field type_name
|
||||
17, // 8: cvms.ClientStreamMessage.azureAttestationResult:type_name -> cvms.azureAttestationResponse
|
||||
10, // 9: cvms.ServerStreamMessage.runReqChunks:type_name -> cvms.RunReqChunks
|
||||
11, // 10: cvms.ServerStreamMessage.runReq:type_name -> cvms.ComputationRunReq
|
||||
2, // 11: cvms.ServerStreamMessage.stopComputation:type_name -> cvms.StopComputation
|
||||
0, // 12: cvms.ServerStreamMessage.agentStateReq:type_name -> cvms.AgentStateReq
|
||||
9, // 13: cvms.ServerStreamMessage.disconnectReq:type_name -> cvms.DisconnectReq
|
||||
13, // 14: cvms.ComputationRunReq.datasets:type_name -> cvms.Dataset
|
||||
14, // 15: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm
|
||||
12, // 16: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer
|
||||
15, // 17: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig
|
||||
7, // 18: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
|
||||
8, // 19: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
|
||||
19, // [19:20] is the sub-list for method output_type
|
||||
18, // [18:19] is the sub-list for method input_type
|
||||
18, // [18:18] is the sub-list for extension type_name
|
||||
18, // [18:18] is the sub-list for extension extendee
|
||||
0, // [0:18] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_agent_cvms_cvms_proto_init() }
|
||||
@@ -1428,6 +1509,7 @@ func file_agent_cvms_cvms_proto_init() {
|
||||
(*ClientStreamMessage_StopComputationRes)(nil),
|
||||
(*ClientStreamMessage_AgentStateRes)(nil),
|
||||
(*ClientStreamMessage_VTPMattestationReport)(nil),
|
||||
(*ClientStreamMessage_AzureAttestationResult)(nil),
|
||||
}
|
||||
file_agent_cvms_cvms_proto_msgTypes[8].OneofWrappers = []any{
|
||||
(*ServerStreamMessage_RunReqChunks)(nil),
|
||||
@@ -1442,7 +1524,7 @@ func file_agent_cvms_cvms_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_cvms_cvms_proto_rawDesc), len(file_agent_cvms_cvms_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 17,
|
||||
NumMessages: 18,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -60,6 +60,7 @@ message ClientStreamMessage {
|
||||
StopComputationResponse stopComputationRes = 4;
|
||||
AgentStateRes agentStateRes = 5;
|
||||
AttestationResponse vTPMattestationReport = 6;
|
||||
azureAttestationResponse azureAttestationResult = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +123,8 @@ message AttestationResponse {
|
||||
bytes file = 1;
|
||||
string certSerialNumber = 2;
|
||||
}
|
||||
|
||||
message azureAttestationResponse {
|
||||
bytes file = 1;
|
||||
string certSerialNumber = 2;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
@@ -33,17 +32,15 @@ type agentServer struct {
|
||||
logger *slog.Logger
|
||||
svc agent.Service
|
||||
host string
|
||||
qp client.LeveledQuoteProvider
|
||||
caUrl string
|
||||
cvmId string
|
||||
}
|
||||
|
||||
func NewServer(logger *slog.Logger, svc agent.Service, host string, qp client.LeveledQuoteProvider, caUrl string, cvmId string) AgentServer {
|
||||
func NewServer(logger *slog.Logger, svc agent.Service, host string, caUrl string, cvmId string) AgentServer {
|
||||
return &agentServer{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
host: host,
|
||||
qp: qp,
|
||||
caUrl: caUrl,
|
||||
cvmId: cvmId,
|
||||
}
|
||||
@@ -81,7 +78,7 @@ func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, as.qp, authSvc, as.caUrl, as.cvmId)
|
||||
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, authSvc, as.caUrl, as.cvmId)
|
||||
|
||||
go func() {
|
||||
err := as.gs.Start()
|
||||
|
||||
+136
-9
@@ -7,7 +7,7 @@ package mocks
|
||||
|
||||
import (
|
||||
agent "github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
attestation "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
|
||||
context "context"
|
||||
|
||||
@@ -75,7 +75,7 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
ret := _m.Called(ctx, reportData, nonce, attType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -84,10 +84,10 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) ([]byte, error)); ok {
|
||||
return rf(ctx, reportData, nonce, attType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) []byte); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) []byte); ok {
|
||||
r0 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
@@ -95,7 +95,7 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, config.AttestationType) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, attestation.PlatformType) error); ok {
|
||||
r1 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@@ -113,14 +113,14 @@ type Service_Attestation_Call struct {
|
||||
// - ctx context.Context
|
||||
// - reportData [64]byte
|
||||
// - nonce [32]byte
|
||||
// - attType config.AttestationType
|
||||
// - attType attestation.PlatformType
|
||||
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, nonce [32]byte, attType config.AttestationType)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType attestation.PlatformType)) *Service_Attestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(config.AttestationType))
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(attestation.PlatformType))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -130,7 +130,67 @@ 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, [32]byte, config.AttestationType) ([]byte, error)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, attestation.PlatformType) ([]byte, error)) *Service_Attestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AttestationResult provides a mock function with given fields: ctx, nonce, attType
|
||||
func (_m *Service) AttestationResult(ctx context.Context, nonce [32]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
ret := _m.Called(ctx, nonce, attType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AttestationResult")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [32]byte, attestation.PlatformType) ([]byte, error)); ok {
|
||||
return rf(ctx, nonce, attType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [32]byte, attestation.PlatformType) []byte); ok {
|
||||
r0 = rf(ctx, nonce, attType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [32]byte, attestation.PlatformType) error); ok {
|
||||
r1 = rf(ctx, nonce, attType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_AttestationResult_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AttestationResult'
|
||||
type Service_AttestationResult_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AttestationResult is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - nonce [32]byte
|
||||
// - attType attestation.PlatformType
|
||||
func (_e *Service_Expecter) AttestationResult(ctx interface{}, nonce interface{}, attType interface{}) *Service_AttestationResult_Call {
|
||||
return &Service_AttestationResult_Call{Call: _e.mock.On("AttestationResult", ctx, nonce, attType)}
|
||||
}
|
||||
|
||||
func (_c *Service_AttestationResult_Call) Run(run func(ctx context.Context, nonce [32]byte, attType attestation.PlatformType)) *Service_AttestationResult_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([32]byte), args[2].(attestation.PlatformType))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AttestationResult_Call) Return(_a0 []byte, _a1 error) *Service_AttestationResult_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AttestationResult_Call) RunAndReturn(run func(context.Context, [32]byte, attestation.PlatformType) ([]byte, error)) *Service_AttestationResult_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -182,6 +242,73 @@ func (_c *Service_Data_Call) RunAndReturn(run func(context.Context, agent.Datase
|
||||
return _c
|
||||
}
|
||||
|
||||
// IMAMeasurements provides a mock function with given fields: ctx
|
||||
func (_m *Service) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IMAMeasurements")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 []byte
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) ([]byte, []byte, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) []byte); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) []byte); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func(context.Context) error); ok {
|
||||
r2 = rf(ctx)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// Service_IMAMeasurements_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IMAMeasurements'
|
||||
type Service_IMAMeasurements_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IMAMeasurements is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *Service_Expecter) IMAMeasurements(ctx interface{}) *Service_IMAMeasurements_Call {
|
||||
return &Service_IMAMeasurements_Call{Call: _e.mock.On("IMAMeasurements", ctx)}
|
||||
}
|
||||
|
||||
func (_c *Service_IMAMeasurements_Call) Run(run func(ctx context.Context)) *Service_IMAMeasurements_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_IMAMeasurements_Call) Return(_a0 []byte, _a1 []byte, _a2 error) *Service_IMAMeasurements_Call {
|
||||
_c.Call.Return(_a0, _a1, _a2)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_IMAMeasurements_Call) RunAndReturn(run func(context.Context) ([]byte, []byte, error)) *Service_IMAMeasurements_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// InitComputation provides a mock function with given fields: ctx, cmp
|
||||
func (_m *Service) InitComputation(ctx context.Context, cmp agent.Computation) error {
|
||||
ret := _m.Called(ctx, cmp)
|
||||
|
||||
+72
-37
@@ -6,7 +6,6 @@ package agent
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
"slices"
|
||||
sync "sync"
|
||||
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/binary"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/docker"
|
||||
@@ -23,7 +22,7 @@ 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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/crypto/sha3"
|
||||
@@ -75,6 +74,11 @@ const (
|
||||
algoFilePermission = 0o700
|
||||
)
|
||||
|
||||
const (
|
||||
ImaMeasurementsFilePath = "/sys/kernel/security/integrity/ima/ascii_runtime_measurements"
|
||||
ImaPcrIndex = 10
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMalformedEntity indicates malformed entity specification (e.g.
|
||||
// invalid username or password).
|
||||
@@ -100,6 +104,10 @@ var (
|
||||
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
|
||||
// ErrAttestationFailed attestation failed.
|
||||
ErrAttestationFailed = errors.New("failed to get raw quote")
|
||||
// ErrAttestationVTpmFailed vTPM attestation failed.
|
||||
ErrAttestationVTpmFailed = errors.New("failed to get vTPM quote")
|
||||
// ErrFetchAzureToken azure token fetch failed.
|
||||
ErrFetchAzureToken = errors.New("failed to get azure token")
|
||||
// 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")
|
||||
)
|
||||
@@ -112,40 +120,40 @@ 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 [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error)
|
||||
Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error)
|
||||
IMAMeasurements(ctx context.Context) ([]byte, []byte, error)
|
||||
AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error)
|
||||
State() string
|
||||
}
|
||||
|
||||
type agentService struct {
|
||||
mu sync.Mutex
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
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.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.
|
||||
vtpmAttest vtpm.VtpmAttest // Attestation function.
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
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.
|
||||
provider attestation.Provider // 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.LeveledQuoteProvider, vmlp int, vtpmAttest vtpm.VtpmAttest) Service {
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, provider attestation.Provider, vmlp int) Service {
|
||||
sm := statemachine.NewStateMachine(Idle)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
eventSvc: eventSvc,
|
||||
quoteProvider: quoteProvider,
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
vmpl: vmlp,
|
||||
vtpmAttest: vtpmAttest,
|
||||
sm: sm,
|
||||
eventSvc: eventSvc,
|
||||
provider: provider,
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
vmpl: vmlp,
|
||||
}
|
||||
|
||||
transitions := []statemachine.Transition{
|
||||
@@ -417,26 +425,39 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
return as.result, as.runError
|
||||
}
|
||||
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
switch attType {
|
||||
case config.SNP:
|
||||
rawQuote, err := as.quoteProvider.GetRawQuoteAtLevel(reportData, uint(as.vmpl))
|
||||
case attestation.SNP:
|
||||
rawQuote, err := as.provider.TeeAttestation(reportData[:])
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
return []byte{}, errors.Wrap(ErrAttestationFailed, err)
|
||||
}
|
||||
return rawQuote, nil
|
||||
case config.VTPM:
|
||||
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], false)
|
||||
case attestation.VTPM:
|
||||
vTPMQuote, err := as.provider.VTpmAttestation(nonce[:])
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(ErrAttestationVTpmFailed, err)
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
case attestation.SNPvTPM:
|
||||
vTPMQuote, err := as.provider.Attestation(reportData[:], nonce[:])
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(ErrAttestationVTpmFailed, err)
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
default:
|
||||
return []byte{}, ErrAttestationType
|
||||
}
|
||||
}
|
||||
|
||||
func (as *agentService) AttestationResult(ctx context.Context, nonce [vtpm.Nonce]byte, attType attestation.PlatformType) ([]byte, error) {
|
||||
switch attType {
|
||||
case attestation.AzureToken:
|
||||
token, err := as.provider.AzureAttestationToken(nonce[:])
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
case config.SNPvTPM:
|
||||
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], true)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
return token, nil
|
||||
default:
|
||||
return []byte{}, ErrAttestationType
|
||||
}
|
||||
@@ -495,3 +516,17 @@ func (as *agentService) publishEvent(status string) statemachine.Action {
|
||||
as.eventSvc.SendEvent(as.computation.ID, state.String(), status, json.RawMessage{})
|
||||
}
|
||||
}
|
||||
|
||||
func (as *agentService) IMAMeasurements(ctx context.Context) ([]byte, []byte, error) {
|
||||
data, err := os.ReadFile(ImaMeasurementsFilePath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error reading Linux IMA measurements file: %s", err.Error())
|
||||
}
|
||||
|
||||
pcr10, err := vtpm.GetPCRSHA1Value(ImaPcrIndex)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error reading TPM PCR #10: %s", err.Error())
|
||||
}
|
||||
|
||||
return data, pcr10, nil
|
||||
}
|
||||
|
||||
+102
-23
@@ -20,8 +20,9 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/events/mocks"
|
||||
"github.com/ultravioletrs/cocos/agent/statemachine"
|
||||
smmocks "github.com/ultravioletrs/cocos/agent/statemachine/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/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"
|
||||
@@ -36,9 +37,6 @@ var (
|
||||
const datasetFile = "iris.csv"
|
||||
|
||||
func TestAlgo(t *testing.T) {
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -121,7 +119,7 @@ func TestAlgo(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -140,9 +138,6 @@ func TestAlgo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestData(t *testing.T) {
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -216,7 +211,7 @@ func TestData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
svc := New(ctx, mglog.NewMock(), events, &attestation.EmptyProvider{}, 0)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -241,9 +236,6 @@ func TestData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
@@ -301,10 +293,10 @@ func TestResult(t *testing.T) {
|
||||
sm.On("SendEvent", mock.Anything).Return()
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
eventSvc: events,
|
||||
quoteProvider: qp,
|
||||
computation: testComputation(t),
|
||||
sm: sm,
|
||||
eventSvc: events,
|
||||
provider: &attestation.EmptyProvider{},
|
||||
computation: testComputation(t),
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -324,29 +316,64 @@ func TestResult(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttestation(t *testing.T) {
|
||||
qp := new(mocks2.LeveledQuoteProvider)
|
||||
provider := new(mocks2.Provider)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
reportData [quoteprovider.Nonce]byte
|
||||
nonce [vtpm.Nonce]byte
|
||||
rawQuote []uint8
|
||||
platform attestation.PlatformType
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Test attestation successful",
|
||||
name: "Test SNP attestation successful",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: make([]uint8, 0),
|
||||
platform: attestation.SNP,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Test attestation failed",
|
||||
name: "Test SNP attestation failed",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: nil,
|
||||
platform: attestation.SNP,
|
||||
err: ErrAttestationFailed,
|
||||
},
|
||||
{
|
||||
name: "Test vTPM attestation successful",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: make([]uint8, 0),
|
||||
platform: attestation.VTPM,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Test vTPM attestation failed",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: nil,
|
||||
platform: attestation.VTPM,
|
||||
err: ErrAttestationVTpmFailed,
|
||||
},
|
||||
{
|
||||
name: "Test SNP-vTPM attestation successful",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: make([]uint8, 0),
|
||||
platform: attestation.SNPvTPM,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Test SNP-vTPM attestation failed",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: nil,
|
||||
platform: attestation.SNPvTPM,
|
||||
err: ErrAttestationVTpmFailed,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -359,13 +386,13 @@ func TestAttestation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
getQuote := qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
if tc.err != ErrAttestationFailed {
|
||||
getQuote = qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.nonce, nil)
|
||||
getQuote := provider.On("TeeAttestation", mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
if tc.err != ErrAttestationFailed && tc.err != ErrAttestationVTpmFailed {
|
||||
getQuote = provider.On("TeeAttestation", mock.Anything).Return(tc.nonce, nil)
|
||||
}
|
||||
defer getQuote.Unset()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
svc := New(ctx, mglog.NewMock(), events, provider, 0)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, 0)
|
||||
assert.True(t, errors.Contains(err, tc.err), "expected %v, got %v", tc.err, err)
|
||||
@@ -373,6 +400,58 @@ func TestAttestation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationResult(t *testing.T) {
|
||||
provider := new(mocks2.Provider)
|
||||
cases := []struct {
|
||||
name string
|
||||
nonce [vtpm.Nonce]byte
|
||||
platform attestation.PlatformType
|
||||
token []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Azure token fetch successful",
|
||||
nonce: [32]byte{1, 2, 3}, // any test nonce
|
||||
platform: attestation.AzureToken,
|
||||
token: []byte("mockToken"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Azure token fetch failed",
|
||||
nonce: [32]byte{4, 5, 6},
|
||||
platform: attestation.AzureToken,
|
||||
token: []byte{},
|
||||
err: ErrFetchAzureToken,
|
||||
},
|
||||
{
|
||||
name: "Invalid attestation type",
|
||||
nonce: [32]byte{7, 8, 9},
|
||||
platform: attestation.SNP,
|
||||
token: []byte{},
|
||||
err: ErrAttestationType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
events := new(mocks.Service)
|
||||
events.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
||||
|
||||
if tc.platform == attestation.AzureToken {
|
||||
provider.On("AzureAttestationToken", tc.nonce[:]).Return(tc.token, tc.err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, provider, 0)
|
||||
|
||||
result, err := svc.AttestationResult(ctx, tc.nonce, tc.platform)
|
||||
assert.True(t, errors.Contains(err, tc.err), "expected error %v, got %v", tc.err, err)
|
||||
assert.Equal(t, tc.token, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateReportData() [quoteprovider.Nonce]byte {
|
||||
bytes := make([]byte, quoteprovider.Nonce)
|
||||
_, err := rand.Read(bytes)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package cocosai
|
||||
|
||||
import _ "embed"
|
||||
|
||||
+254
-177
@@ -3,7 +3,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -20,11 +20,10 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/server"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
@@ -34,29 +33,31 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMinimumTcb = 0
|
||||
defaultMinimumLaunchTcb = 0
|
||||
defaultMinimumGuestSvn = 0
|
||||
defaultGuestPolicy = 0x0000000000030000
|
||||
defaultMinimumBuild = 0
|
||||
defaultCheckCrl = false
|
||||
defaultTimeout = 2 * time.Minute
|
||||
defaultMaxRetryDelay = 30 * time.Second
|
||||
defaultRequireAuthor = false
|
||||
defaultRequireIdBlock = false
|
||||
defaultMinVersion = "0.0"
|
||||
size16 = 16
|
||||
size32 = 32
|
||||
size48 = 48
|
||||
size64 = 64
|
||||
attestationFilePath = "attestation.bin"
|
||||
vtpmFilePath = "../quote.dat"
|
||||
attestationJson = "attestation.json"
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
FormatBinaryPB = "binarypb"
|
||||
FormatTextProto = "textproto"
|
||||
exampleJSONConfig = `
|
||||
defaultMinimumTcb = 0
|
||||
defaultMinimumLaunchTcb = 0
|
||||
defaultMinimumGuestSvn = 0
|
||||
defaultGuestPolicy = 0x0000000000030000
|
||||
defaultMinimumBuild = 0
|
||||
defaultCheckCrl = false
|
||||
defaultTimeout = 2 * time.Minute
|
||||
defaultMaxRetryDelay = 30 * time.Second
|
||||
defaultRequireAuthor = false
|
||||
defaultRequireIdBlock = false
|
||||
defaultMinVersion = "0.0"
|
||||
size16 = 16
|
||||
size32 = 32
|
||||
size48 = 48
|
||||
size64 = 64
|
||||
attestationFilePath = "attestation.bin"
|
||||
azureAttestResultFilePath = "azure_attest_result.json"
|
||||
azureAttestTokenFilePath = "azure_attest_token.jwt"
|
||||
vtpmFilePath = "../quote.dat"
|
||||
attestationReportJson = "attestation.json"
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
FormatBinaryPB = "binarypb"
|
||||
FormatTextProto = "textproto"
|
||||
exampleJSONConfig = `
|
||||
{
|
||||
"rootOfTrust":{
|
||||
"product":"test_product",
|
||||
@@ -109,47 +110,48 @@ const (
|
||||
}
|
||||
}
|
||||
`
|
||||
SNP = "snp"
|
||||
VTPM = "vtpm"
|
||||
SNPvTPM = "snp-vtpm"
|
||||
SNP = "snp"
|
||||
VTPM = "vtpm"
|
||||
SNPvTPM = "snp-vtpm"
|
||||
AzureToken = "azure-token"
|
||||
CCNone = "none"
|
||||
CCAzure = "azure"
|
||||
CCGCP = "gcp"
|
||||
)
|
||||
|
||||
var (
|
||||
mode string
|
||||
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
cfgString string
|
||||
timeout time.Duration
|
||||
maxRetryDelay time.Duration
|
||||
platformInfo string
|
||||
stepping string
|
||||
trustedAuthorKeys []string
|
||||
trustedAuthorHashes []string
|
||||
trustedIdKeys []string
|
||||
trustedIdKeyHashes []string
|
||||
attestationFile string
|
||||
tpmAttestationFile string
|
||||
attestation []byte
|
||||
empty16 = [size16]byte{}
|
||||
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}
|
||||
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
|
||||
mode string
|
||||
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
cfgString string
|
||||
timeout time.Duration
|
||||
maxRetryDelay time.Duration
|
||||
platformInfo string
|
||||
stepping string
|
||||
trustedAuthorKeys []string
|
||||
trustedAuthorHashes []string
|
||||
trustedIdKeys []string
|
||||
trustedIdKeyHashes []string
|
||||
attestationFile string
|
||||
tpmAttestationFile string
|
||||
attestationRaw []byte
|
||||
empty16 = [size16]byte{}
|
||||
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}
|
||||
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
|
||||
tokenNonce []byte
|
||||
getTextProtoAttestationReport bool
|
||||
getAzureTokenJWT bool
|
||||
cloud string
|
||||
)
|
||||
|
||||
var errEmptyFile = errors.New("input file is empty")
|
||||
|
||||
var marshalOptions = prototext.MarshalOptions{
|
||||
Multiline: true,
|
||||
EmitASCII: true,
|
||||
}
|
||||
var unmarshalOptions = prototext.UnmarshalOptions{}
|
||||
|
||||
func (cli *CLI) NewAttestationCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "attestation [command]",
|
||||
@@ -184,11 +186,12 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
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},
|
||||
ValidArgs: []cobra.Completion{SNP, VTPM, SNPvTPM, AzureToken},
|
||||
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),
|
||||
get %s --tee <512 bit hex value> --vtpm <256 bit hex value>
|
||||
get %s --token <256 bit hex value>`, SNP, VTPM, SNPvTPM, AzureToken),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
@@ -203,32 +206,41 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
|
||||
attestationType := args[0]
|
||||
|
||||
attType := config.SNP
|
||||
attType := attestation.SNP
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
cmd.Println("Fetching SEV-SNP attestation report")
|
||||
case VTPM:
|
||||
cmd.Println("Fetching vTPM report")
|
||||
attType = config.VTPM
|
||||
attType = attestation.VTPM
|
||||
case SNPvTPM:
|
||||
cmd.Println("Fetching SEV-SNP and vTPM report")
|
||||
attType = config.SNPvTPM
|
||||
attType = attestation.SNPvTPM
|
||||
case AzureToken:
|
||||
cmd.Println("Fetching Azure token")
|
||||
attType = attestation.AzureToken
|
||||
}
|
||||
|
||||
if (attType == config.VTPM || attType == config.SNPvTPM) && len(nonce) == 0 {
|
||||
if (attType == attestation.VTPM || attType == attestation.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 {
|
||||
if (attType == attestation.SNP || attType == attestation.SNPvTPM) && len(teeNonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("TEE nonce must be defined for SEV-SNP attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if (attType == attestation.AzureToken) && len(tokenNonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("Token nonce must be defined for Azure attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var fixedReportData [quoteprovider.Nonce]byte
|
||||
if attType != config.VTPM {
|
||||
if attType == attestation.SNP || attType == attestation.SNPvTPM {
|
||||
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)
|
||||
@@ -239,19 +251,29 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
var fixedVtpmNonceByte [vtpm.Nonce]byte
|
||||
if attType != config.SNP {
|
||||
if len(nonce) > vtpm.Nonce {
|
||||
if attType != attestation.SNP {
|
||||
if (len(nonce) > vtpm.Nonce) || (len(tokenNonce) > 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)
|
||||
if attType == attestation.AzureToken {
|
||||
copy(fixedVtpmNonceByte[:], tokenNonce)
|
||||
} else {
|
||||
copy(fixedVtpmNonceByte[:], nonce)
|
||||
}
|
||||
}
|
||||
|
||||
filename := attestationFilePath
|
||||
if getTextProtoAttestation {
|
||||
filename = attestationJson
|
||||
|
||||
if attType == attestation.AzureToken {
|
||||
filename = azureAttestResultFilePath
|
||||
}
|
||||
|
||||
if getTextProtoAttestationReport {
|
||||
filename = attestationReportJson
|
||||
} else if getAzureTokenJWT {
|
||||
filename = azureAttestTokenFilePath
|
||||
}
|
||||
|
||||
attestationFile, err := os.Create(filename)
|
||||
@@ -260,9 +282,21 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
var returnJsonAzureToken bool
|
||||
|
||||
if attType == attestation.AzureToken {
|
||||
err := cli.agentSDK.AttestationResult(cmd.Context(), fixedVtpmNonceByte, int(attType), attestationFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to get attestation result due to error: %v ❌", err)
|
||||
return
|
||||
}
|
||||
returnJsonAzureToken = !getAzureTokenJWT
|
||||
} else {
|
||||
err := cli.agentSDK.Attestation(cmd.Context(), fixedReportData, fixedVtpmNonceByte, int(attType), attestationFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to get attestation due to error: %v ❌", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := attestationFile.Close(); err != nil {
|
||||
@@ -270,7 +304,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
if getTextProtoAttestation {
|
||||
if getTextProtoAttestationReport || returnJsonAzureToken {
|
||||
result, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation file: %v ❌ ", err)
|
||||
@@ -280,6 +314,11 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
result, err = attesationToJSON(result)
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting SNP attestation to JSON: %v ❌", err)
|
||||
return
|
||||
}
|
||||
|
||||
case VTPM, SNPvTPM:
|
||||
marshalOptions := prototext.MarshalOptions{
|
||||
Multiline: true,
|
||||
@@ -288,15 +327,17 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
var attvTPM tpmAttest.Attestation
|
||||
err = proto.Unmarshal(result, &attvTPM)
|
||||
if err != nil {
|
||||
printError(cmd, "failed to unmarshal the attestation report: %v ❌ ", ErrBadAttestation)
|
||||
printError(cmd, "Failed to unmarshal the attestation report: %v ❌", err)
|
||||
return
|
||||
}
|
||||
|
||||
result = []byte(marshalOptions.Format(&attvTPM))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting attestation to textproto: %v ❌ ", err)
|
||||
return
|
||||
case AzureToken:
|
||||
result, err = decodeJWTToJSON(result)
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding Azure token: %v ❌", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filename, result, 0o644); err != nil {
|
||||
@@ -309,9 +350,11 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
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)")
|
||||
cmd.Flags().BoolVarP(&getAzureTokenJWT, "azurejwt", "t", false, "Get azure attestation token as jwt format")
|
||||
cmd.Flags().BoolVarP(&getTextProtoAttestationReport, "reporttextproto", "r", false, "Get attestation report in textproto format")
|
||||
cmd.Flags().BytesHexVar(&teeNonce, "tee", []byte{}, "Define the nonce for the SNP attestation report (must be used with attestation type snp and snp-vtpm)")
|
||||
cmd.Flags().BytesHexVar(&nonce, "vtpm", []byte{}, "Define the nonce for the vTPM attestation report (must be used with attestation type vtpm and snp-vtpm)")
|
||||
cmd.Flags().BytesHexVar(&tokenNonce, "token", []byte{}, "Define the nonce for the Azure attestation token (must be used with attestation type azure-token)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -344,13 +387,15 @@ func isFileJSON(filename string) bool {
|
||||
func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "validate",
|
||||
Short: "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.",
|
||||
Short: fmt.Sprintf("Validate and verify attestation information. You can define the confidential computing cloud provider (%s, %s, %s; %s is the default) and can choose from 3 modes: %s, %s and %s. Default mode is %s.", CCNone, CCAzure, CCGCP, CCNone, SNP, VTPM, SNPvTPM, SNP),
|
||||
Example: `Based on mode:
|
||||
validate <attestationreportfilepath> --report_data <reportdata> --product <product data> //default
|
||||
validate <attestationreportfilepath> --report_data <reportdata> --product <product data> --platform <cc platform> //default
|
||||
validate --mode snp <attestationreportfilepath> --report_data <reportdata> --product <product data>
|
||||
validate --mode vtpm <attestationreportfilepath> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>
|
||||
validate --mode snp-vtpm <attestationreportfilepath> --report_data <reportdata> --product <product data> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>`,
|
||||
|
||||
validate --mode snp-vtpm <attestationreportfilepath> --report_data <reportdata> --product <product data> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>
|
||||
validate --cloud none --mode snp <attestationreportfilepath> --report_data <reportdata> --product <product data>
|
||||
validate --cloud azure --mode vtpm <attestationreportfilepath> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>
|
||||
validate --cloud gcp --mode snp-vtpm <attestationreportfilepath> --report_data <reportdata> --product <product data> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
mode, _ := cmd.Flags().GetString("mode")
|
||||
if len(args) != 1 {
|
||||
@@ -359,38 +404,38 @@ func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
|
||||
// Validate flags based on the mode
|
||||
switch mode {
|
||||
case "snp":
|
||||
case SNP:
|
||||
if err := cmd.MarkFlagRequired("report_data"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-%s mode: %v", SNP, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("product"); err != nil {
|
||||
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
|
||||
}
|
||||
case "snp-vtpm":
|
||||
case SNPvTPM:
|
||||
if err := cmd.MarkFlagRequired("nonce"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("report_data"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-%s mode: %v", SNP, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("product"); err != nil {
|
||||
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("format"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'format' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("output"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'output' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
case "vtpm":
|
||||
case VTPM:
|
||||
if err := cmd.MarkFlagRequired("nonce"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("format"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'format' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("output"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
|
||||
return fmt.Errorf("failed to mark 'output' as required for %s mode: %v", VTPM, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown mode: %s", mode)
|
||||
@@ -399,13 +444,35 @@ func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
mode, _ := cmd.Flags().GetString("mode")
|
||||
cloud, _ := cmd.Flags().GetString("cloud")
|
||||
|
||||
output, err := createOutputFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %v ❌ ", err)
|
||||
}
|
||||
if closer, ok := output.(*os.File); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
|
||||
var provider attestation.Provider
|
||||
switch cloud {
|
||||
case CCNone:
|
||||
provider = vtpm.New(nil, false, 0, output)
|
||||
case CCAzure:
|
||||
provider = azure.New(output)
|
||||
case CCGCP:
|
||||
provider = vtpm.New(nil, false, 0, output)
|
||||
default:
|
||||
provider = vtpm.New(nil, false, 0, output)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "snp":
|
||||
return sevsnpverify(cmd, args)
|
||||
case "snp-vtpm":
|
||||
return vtpmSevSnpverify(args)
|
||||
case "vtpm":
|
||||
return vtpmverify(args)
|
||||
case SNP:
|
||||
return sevsnpverify(cmd, provider, args)
|
||||
case SNPvTPM:
|
||||
return vtpmSevSnpverify(args, provider)
|
||||
case VTPM:
|
||||
return vtpmverify(args, provider)
|
||||
default:
|
||||
return fmt.Errorf("unknown mode: %s", mode)
|
||||
}
|
||||
@@ -413,11 +480,18 @@ func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
cmd.Flags().StringVar(
|
||||
&cloud,
|
||||
"cloud",
|
||||
"none", // default CC provider
|
||||
"The confidential computing cloud provider. Example: azure",
|
||||
)
|
||||
|
||||
cmd.Flags().StringVar(
|
||||
&mode,
|
||||
"mode",
|
||||
"snp", // default mode
|
||||
"The attestation validation mode. Example: sevsnp",
|
||||
"The attestation validation mode. Example: snp",
|
||||
)
|
||||
|
||||
// VTPM FLAGS
|
||||
@@ -659,7 +733,7 @@ func (cli *CLI) NewMeasureCmd(igvmBinaryPath string) *cobra.Command {
|
||||
return igvmmeasureCmd
|
||||
}
|
||||
|
||||
func sevsnpverify(cmd *cobra.Command, args []string) error {
|
||||
func sevsnpverify(cmd *cobra.Command, provider attestation.Provider, args []string) error {
|
||||
cmd.Println("Checking attestation")
|
||||
|
||||
attestationFile = string(args[0])
|
||||
@@ -669,20 +743,18 @@ func sevsnpverify(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// This format is the attestation report in AMD's specified ABI format, immediately
|
||||
// followed by the certificate table bytes.
|
||||
if len(attestation) < abi.ReportSize {
|
||||
return fmt.Errorf("attestation too small: got 0x%x bytes, need at least 0x%x bytes", len(attestation), abi.ReportSize)
|
||||
if len(attestationRaw) < abi.ReportSize {
|
||||
return fmt.Errorf("attestation too small: got 0x%x bytes, need at least 0x%x bytes", len(attestationRaw), abi.ReportSize)
|
||||
}
|
||||
|
||||
if err := parseAttestationConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attestationPB, err := abi.ReportCertsToProto(attestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert attestation bytes to struct %v ❌ ", err)
|
||||
}
|
||||
// Used for verification of SNP attestation report
|
||||
attestation.AttestationPolicy.Config = &cfg
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(attestationPB, &cfg); err != nil {
|
||||
if err := provider.VerifTeeAttestation(attestationRaw, cfg.Policy.ReportData); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
|
||||
@@ -712,85 +784,40 @@ func parseAttestationConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func vtpmSevSnpverify(args []string) error {
|
||||
attestation, err := returnvTPMAttestation(args)
|
||||
func vtpmSevSnpverify(args []string, provider attestation.Provider) error {
|
||||
attest, err := returnvTPMAttestation(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var teeAttestation *sevsnp.Attestation
|
||||
switch attestation.GetTeeAttestation().(type) {
|
||||
case *tpmAttest.Attestation_SevSnpAttestation:
|
||||
teeAttestation = attestation.GetSevSnpAttestation()
|
||||
default:
|
||||
teeAttestation = nil
|
||||
}
|
||||
|
||||
if teeAttestation == nil {
|
||||
return fmt.Errorf("tee attestation not fetched")
|
||||
}
|
||||
|
||||
if err := parseAttestationConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(teeAttestation, &cfg); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
// Used for verification of SNP attestation report
|
||||
attestation.AttestationPolicy.Config = &cfg
|
||||
|
||||
if err := verifyvTPM(attestation); err != nil {
|
||||
return err
|
||||
if err := provider.VerifyAttestation(attest, cfg.Policy.ReportData, nonce); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vtpmverify(args []string) error {
|
||||
func vtpmverify(args []string, provider attestation.Provider) error {
|
||||
attestation, err := returnvTPMAttestation(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := verifyvTPM(attestation); err != nil {
|
||||
return err
|
||||
if err := provider.VerifVTpmAttestation(attestation, nonce); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyvTPM(attestation *tpmAttest.Attestation) error {
|
||||
pub, err := tpm2.DecodePublic(attestation.GetAkPub())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{cryptoPub}, TEEOpts: nil})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out, err := marshalOptions.Marshal(ms)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
output, err := createOutputFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closer, ok := output.(*os.File); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
if _, err := output.Write(out); err != nil {
|
||||
return fmt.Errorf("failed to write verified attestation report: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnvTPMAttestation(args []string) (*tpmAttest.Attestation, error) {
|
||||
func returnvTPMAttestation(args []string) ([]byte, error) {
|
||||
tpmAttestationFile = string(args[0])
|
||||
input, err := openInputFile()
|
||||
if err != nil {
|
||||
@@ -806,8 +833,9 @@ func returnvTPMAttestation(args []string) (*tpmAttest.Attestation, error) {
|
||||
attestation := &tpmAttest.Attestation{}
|
||||
|
||||
if format == FormatBinaryPB {
|
||||
err = proto.Unmarshal(attestationBytes, attestation)
|
||||
return attestationBytes, nil
|
||||
} else if format == FormatTextProto {
|
||||
unmarshalOptions := prototext.UnmarshalOptions{}
|
||||
err = unmarshalOptions.Unmarshal(attestationBytes, attestation)
|
||||
} else {
|
||||
return nil, fmt.Errorf("format should be either binarypb or textproto")
|
||||
@@ -816,7 +844,12 @@ func returnvTPMAttestation(args []string) (*tpmAttest.Attestation, error) {
|
||||
return nil, fmt.Errorf("fail to unmarshal attestation report: %v", err)
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
attestationBytes, err = proto.Marshal(attestation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to marshal vTPM attestation report: %v", err)
|
||||
}
|
||||
|
||||
return attestationBytes, nil
|
||||
}
|
||||
|
||||
func openInputFile() (io.Reader, error) {
|
||||
@@ -926,9 +959,9 @@ func parseAttestationFile() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attestation = file
|
||||
attestationRaw = file
|
||||
if isFileJSON(attestationFile) {
|
||||
attestation, err = attesationFromJSON(attestation)
|
||||
attestationRaw, err = attesationFromJSON(attestationRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1050,3 +1083,47 @@ func validateFieldLength(fieldName string, field []byte, expectedLength int) err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeJWTToJSON(tokenBytes []byte) ([]byte, error) {
|
||||
token := string(tokenBytes) // convert to string
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("invalid JWT: must have at least 2 parts")
|
||||
}
|
||||
|
||||
decode := func(seg string) (map[string]interface{}, error) {
|
||||
// Add padding if missing
|
||||
if m := len(seg) % 4; m != 0 {
|
||||
seg += strings.Repeat("=", 4-m)
|
||||
}
|
||||
|
||||
data, err := base64.URLEncoding.DecodeString(seg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
header, err := decode(parts[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode header: %v", err)
|
||||
}
|
||||
|
||||
payload, err := decode(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode payload: %v", err)
|
||||
}
|
||||
|
||||
combined := map[string]interface{}{
|
||||
"header": header,
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
return json.MarshalIndent(combined, "", " ")
|
||||
}
|
||||
|
||||
+58
-10
@@ -16,7 +16,8 @@ import (
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/gcp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -37,13 +38,14 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
errDecode = errors.New("base64 string could not be decoded")
|
||||
errDataLength = errors.New("data does not have an adequate length")
|
||||
errReadingAttestationPolicyFile = errors.New("error while reading the attestation policy file")
|
||||
errUnmarshalJSON = errors.New("failed to unmarshal json")
|
||||
errMarshalJSON = errors.New("failed to marshal json")
|
||||
errWriteFile = errors.New("failed to write to file")
|
||||
errAttestationPolicyField = errors.New("the specified field type does not exist in the attestation policy")
|
||||
errDecode = errors.New("base64 string could not be decoded")
|
||||
errDataLength = errors.New("data does not have an adequate length")
|
||||
errReadingAttestationPolicyFile = errors.New("error while reading the attestation policy file")
|
||||
errUnmarshalJSON = errors.New("failed to unmarshal json")
|
||||
errMarshalJSON = errors.New("failed to marshal json")
|
||||
errWriteFile = errors.New("failed to write to file")
|
||||
errAttestationPolicyField = errors.New("the specified field type does not exist in the attestation policy")
|
||||
policy uint64 = 196639
|
||||
)
|
||||
|
||||
func (cli *CLI) NewAttestationPolicyCmd() *cobra.Command {
|
||||
@@ -226,6 +228,52 @@ func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *CLI) NewAzureAttestationPolicy() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "azure",
|
||||
Short: "Get attestation policy for Azure CVM",
|
||||
Example: `azure <azure_maa_token_file> <product_name>`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
token, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
product := args[1]
|
||||
|
||||
config, err := azure.GenerateAttestationPolicy(string(token), product, policy)
|
||||
if err != nil {
|
||||
printError(cmd, "Error generating attestation policy: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestationPolicyJson, err := json.MarshalIndent(&config, "", " ")
|
||||
if err != nil {
|
||||
printError(cmd, "Error marshaling attestation policy: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile("attestation_policy.json", attestationPolicyJson, filePermission); err != nil {
|
||||
printError(cmd, "Error writing attestation policy file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Attestation policy file generated successfully ✅")
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Uint64Var(
|
||||
&policy,
|
||||
"policy",
|
||||
policy,
|
||||
"Policy of the guest CVM",
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func changeAttestationConfiguration(fileName, base64Data string, expectedLength int, field fieldType) error {
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
@@ -236,14 +284,14 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
|
||||
return errDataLength
|
||||
}
|
||||
|
||||
ac := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
ac := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
|
||||
f, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(errReadingAttestationPolicyFile, err)
|
||||
}
|
||||
|
||||
if err = config.ReadAttestationPolicyFromByte(f, &ac); err != nil {
|
||||
if err = attestation.ReadAttestationPolicyFromByte(f, &ac); err != nil {
|
||||
return errors.Wrap(errUnmarshalJSON, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
@@ -19,7 +19,7 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
initialConfig := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
initialConfig := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
|
||||
initialJSON, err := json.Marshal(initialConfig)
|
||||
require.NoError(t, err)
|
||||
@@ -87,8 +87,8 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
content, err := os.ReadFile(tmpfile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
ap := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err = config.ReadAttestationPolicyFromByte(content, &ap)
|
||||
ap := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
err = attestation.ReadAttestationPolicyFromByte(content, &ap)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedData, _ := base64.StdEncoding.DecodeString(tt.base64Data)
|
||||
|
||||
+90
-9
@@ -49,6 +49,7 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
|
||||
teeNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
|
||||
vtpmNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
|
||||
tokenNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -102,7 +103,7 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid vTPM data size",
|
||||
args: []string{"vtpm", "-t", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
|
||||
args: []string{"vtpm", "--vtpm", 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",
|
||||
@@ -116,39 +117,60 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "failed to get attestation",
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
args: []string{"snp", "--tee", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Failed to get attestation due to error",
|
||||
},
|
||||
{
|
||||
name: "Textproto report error",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
args: []string{"snp", "--tee", teeNonce, "--reporttextproto"},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedErr: "Error converting attestation to textproto",
|
||||
expectedErr: "Fetching SEV-SNP attestation report\nError converting SNP attestation to JSON: attestation contents too small : attestation contents too small (0x10 bytes). Want at least 0x4a0 bytes ❌\n",
|
||||
},
|
||||
{
|
||||
name: "successful Textproto report",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
args: []string{"snp", "--tee", teeNonce, "--reporttextproto"},
|
||||
mockResponse: validattestation,
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "connection error",
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
args: []string{"snp", "--tee", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("failed to connect to agent"),
|
||||
expectedErr: "Failed to connect to agent",
|
||||
},
|
||||
{
|
||||
name: "successful Azure token retrieval",
|
||||
args: []string{"azure-token", "--token", tokenNonce},
|
||||
mockResponse: []byte("eyJhbGciOiAiUlMyNTYifQ.eyJzdWIiOiAidGVzdC11c2VyIn0.signature"),
|
||||
mockError: nil,
|
||||
expectedOut: "Fetching Azure token\nAttestation result retrieved and saved successfully!\n",
|
||||
},
|
||||
{
|
||||
name: "failed to retrieve Azure token",
|
||||
args: []string{"azure-token", "--token", tokenNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Fetching Azure token\nFailed to get attestation result due to error: error ❌\n",
|
||||
},
|
||||
{
|
||||
name: "invalid token nonce size",
|
||||
args: []string{"azure-token", "--token", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Fetching Azure token\nvTPM nonce must be a hex encoded string of length lesser or equal 32 bytes ❌ \n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
os.Remove(attestationFilePath)
|
||||
os.Remove(attestationJson)
|
||||
os.Remove(attestationReportJson)
|
||||
})
|
||||
mockSDK := new(mocks.SDK)
|
||||
cli := &CLI{agentSDK: mockSDK}
|
||||
@@ -164,6 +186,11 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
mockSDK.On("AttestationResult", mock.Anything, [vtpm.Nonce]byte(bytes.Repeat([]byte{0x00}, vtpm.Nonce)), mock.Anything, mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
|
||||
_, err := args.Get(3).(*os.File).Write(tc.mockResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
cmd.SetArgs(tc.args)
|
||||
err := cmd.Execute()
|
||||
|
||||
@@ -182,7 +209,8 @@ func TestNewValidateAttestationValidationCmdDefaults(t *testing.T) {
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
assert.Equal(t, "validate", cmd.Use)
|
||||
assert.Equal(t, "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.", cmd.Short)
|
||||
expectedMessage := fmt.Sprintf("Validate and verify attestation information. You can define the confidential computing cloud provider (%s, %s, %s; %s is the default) and can choose from 3 modes: %s, %s and %s. Default mode is %s.", CCNone, CCAzure, CCGCP, CCNone, SNP, VTPM, SNPvTPM, SNP)
|
||||
assert.Equal(t, expectedMessage, cmd.Short)
|
||||
|
||||
assert.Equal(t, fmt.Sprint(defaultMinimumTcb), cmd.Flag("minimum_tcb").Value.String())
|
||||
assert.Equal(t, fmt.Sprint(defaultMinimumLaunchTcb), cmd.Flag("minimum_lauch_tcb").Value.String())
|
||||
@@ -394,7 +422,7 @@ func TestParseFiles(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
err = parseTrustedKeys()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("test attestation"), attestation)
|
||||
assert.Equal(t, []byte("test attestation"), attestationRaw)
|
||||
assert.Len(t, cfg.Policy.TrustedAuthorKeys, 1)
|
||||
assert.Len(t, cfg.Policy.TrustedIdKeys, 1)
|
||||
|
||||
@@ -626,3 +654,56 @@ func TestRoundTrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, roundTripReport)
|
||||
}
|
||||
|
||||
func TestDecodeJWTToJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
err error
|
||||
validate func(t *testing.T, output []byte)
|
||||
}{
|
||||
{
|
||||
name: "Valid JWT",
|
||||
input: []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
|
||||
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +
|
||||
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"),
|
||||
err: nil,
|
||||
validate: func(t *testing.T, output []byte) {
|
||||
assert.NotEmpty(t, output)
|
||||
assert.Contains(t, string(output), `"header"`)
|
||||
assert.Contains(t, string(output), `"payload"`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid JWT - one part",
|
||||
input: []byte("justonepart"),
|
||||
err: fmt.Errorf("invalid JWT: must have at least 2 parts"),
|
||||
validate: func(t *testing.T, output []byte) {
|
||||
assert.Nil(t, output)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid Base64",
|
||||
input: []byte("bad@@@.header"),
|
||||
err: errors.New("illegal base64 data"),
|
||||
validate: func(t *testing.T, output []byte) {
|
||||
assert.Nil(t, output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := decodeJWTToJSON(tt.input)
|
||||
|
||||
if tt.err != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
tt.validate(t, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,8 +27,8 @@ 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 := config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err := config.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
attestationConfiguration := attestation.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
err := attestation.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
if err != nil {
|
||||
printError(cmd, "Error while reading manifest: %v ❌ ", err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
const (
|
||||
imaMeasurementsFilename = "ima_measurements"
|
||||
)
|
||||
|
||||
func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "ima-measurements",
|
||||
Short: "Retrieve Linux IMA measurements file",
|
||||
Example: "ima-measurements <optional_file_name>",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("⏳ Retrieving computation Linux IMA measurements file")
|
||||
|
||||
filename := imaMeasurementsFilename
|
||||
if len(args) >= 1 {
|
||||
filename = args[0]
|
||||
}
|
||||
|
||||
imaMeasurementsFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error creating imaMeasurements file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer imaMeasurementsFile.Close()
|
||||
|
||||
pcr10, err := cli.agentSDK.IMAMeasurements(cmd.Context(), imaMeasurementsFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Error retrieving Linux IMA measurements file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println(color.New(color.FgGreen).Sprintf("Linux IMA measurements file retrieved and saved successfully as %s! PCR10 = %s ✔ ", filename, hex.EncodeToString(pcr10)))
|
||||
|
||||
calculatedPCR10 := make([]byte, vtpm.Hash1)
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to open file: %v ❌ ", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.Fields(line)
|
||||
|
||||
if parts[0] != "10" {
|
||||
continue
|
||||
}
|
||||
|
||||
digestHex := parts[1]
|
||||
if digestHex == strings.Repeat("0", 40) {
|
||||
digestHex = strings.Repeat("f", 40)
|
||||
}
|
||||
|
||||
digest, err := hex.DecodeString(digestHex)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to decode digest: %v ❌ ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
hasher := sha1.New()
|
||||
hasher.Write(calculatedPCR10)
|
||||
hasher.Write(digest)
|
||||
calculatedPCR10 = hasher.Sum(nil)
|
||||
}
|
||||
|
||||
if hex.EncodeToString(pcr10) != hex.EncodeToString(calculatedPCR10) {
|
||||
printError(cmd, "Measurements file not verified ❌ ", err)
|
||||
} else {
|
||||
cmd.Println(color.New(color.FgGreen).Sprintf("Measurements file verified!"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -40,7 +40,7 @@ func (c *CLI) InitializeAgentSDK(cmd *cobra.Command) error {
|
||||
c.connectErr = err
|
||||
return err
|
||||
}
|
||||
cmd.Println("🔗 Connected to agent using ", agentGRPCClient.Secure())
|
||||
cmd.Println("🔗 Connected to agent ", agentGRPCClient.Secure())
|
||||
c.client = agentGRPCClient
|
||||
|
||||
c.agentSDK = sdk.NewAgentSDK(agentClient)
|
||||
|
||||
+79
-41
@@ -8,7 +8,6 @@ import (
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
@@ -20,8 +19,6 @@ import (
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"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"
|
||||
@@ -29,9 +26,8 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/cvms/server"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
|
||||
attestationconfig "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
|
||||
@@ -47,11 +43,15 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"AGENT_VMPL" envDefault:"2"`
|
||||
AgentGrpcHost string `env:"AGENT_GRPC_HOST" envDefault:"0.0.0.0"`
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"AGENT_VMPL" envDefault:"2"`
|
||||
AgentGrpcHost string `env:"AGENT_GRPC_HOST" envDefault:"0.0.0.0"`
|
||||
CAUrl string `env:"AGENT_CVM_CA_URL" envDefault:""`
|
||||
CVMId string `env:"AGENT_CVM_ID" envDefault:""`
|
||||
CVMId string `env:"AGENT_CVM_ID" envDefault:""`
|
||||
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
|
||||
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
|
||||
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
|
||||
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -85,22 +85,27 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
var qp client.LeveledQuoteProvider
|
||||
vtpmAttest := vtpm.Attest
|
||||
var provider attestation.Provider
|
||||
ccPlatform := attestation.CCPlatform()
|
||||
|
||||
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
|
||||
vtpmAttest = vtpm.EmptyAttest
|
||||
} else {
|
||||
qp, err = quoteprovider.GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
azureConfig := azure.NewEnvConfigFromAgent(
|
||||
cfg.AgentOSBuild,
|
||||
cfg.AgentOSType,
|
||||
cfg.AgentOSDistro,
|
||||
cfg.AgentMaaURL,
|
||||
)
|
||||
azure.InitializeDefaultMAAVars(azureConfig)
|
||||
|
||||
switch ccPlatform {
|
||||
case attestation.SNP:
|
||||
provider = vtpm.New(nil, false, uint(cfg.Vmpl), nil)
|
||||
case attestation.SNPvTPM:
|
||||
provider = vtpm.New(nil, true, uint(cfg.Vmpl), nil)
|
||||
case attestation.Azure:
|
||||
provider = azure.New(nil)
|
||||
case attestation.NoCC:
|
||||
logger.Info("TEE device not found")
|
||||
provider = &attestation.EmptyProvider{}
|
||||
}
|
||||
|
||||
cvmGrpcConfig := pkggrpc.CVMClientConfig{}
|
||||
@@ -146,7 +151,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, qp, cfg.Vmpl, vtpmAttest)
|
||||
svc := newService(ctx, logger, eventSvc, provider, cfg.Vmpl)
|
||||
|
||||
if err := os.MkdirAll(storageDir, 0o755); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create storage directory: %s", err))
|
||||
@@ -154,7 +159,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost, qp, cfg.CAUrl, cfg.CVMId), storageDir, reconnectFn, cvmGRPCClient)
|
||||
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost, cfg.CAUrl, cfg.CVMId), storageDir, reconnectFn, cvmGRPCClient)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -180,17 +185,34 @@ func main() {
|
||||
return mc.Process(ctx, cancel)
|
||||
})
|
||||
|
||||
attestation, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
if ccPlatform == attestation.Azure {
|
||||
azureAttestationResult, azureCertSerialNumber, err := azureAttestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
eventsLogsQueue <- &cvms.ClientStreamMessage{
|
||||
Message: &cvms.ClientStreamMessage_AzureAttestationResult{
|
||||
AzureAttestationResult: &cvms.AzureAttestationResponse{
|
||||
File: azureAttestationResult,
|
||||
CertSerialNumber: azureCertSerialNumber,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
eventsLogsQueue <- &cvms.ClientStreamMessage{
|
||||
Message: &cvms.ClientStreamMessage_VTPMattestationReport{
|
||||
VTPMattestationReport: &cvms.AttestationResponse{
|
||||
File: attestation,
|
||||
File: attest,
|
||||
CertSerialNumber: certSerialNumber,
|
||||
},
|
||||
},
|
||||
@@ -201,8 +223,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.LeveledQuoteProvider, vmpl int, vtpmAttest vtpm.VtpmAttest) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, qp, vmpl, vtpmAttest)
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, provider attestation.Provider, vmpl int) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, provider, vmpl)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "api")
|
||||
@@ -211,15 +233,6 @@ 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
|
||||
}
|
||||
|
||||
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
|
||||
if certFilePath == "" {
|
||||
return nil, "", nil
|
||||
@@ -238,7 +251,32 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
|
||||
|
||||
nonceSNP := sha512.Sum512(certFile)
|
||||
nonceVTPM := sha256.Sum256(certFile)
|
||||
attestation, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestationconfig.SNPvTPM)
|
||||
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return attest, certx509.SerialNumber.String(), nil
|
||||
}
|
||||
|
||||
func azureAttestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
|
||||
if certFilePath == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
certFile, err := os.ReadFile(certFilePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
certPem, _ := pem.Decode(certFile)
|
||||
certx509, err := x509.ParseCertificate(certPem.Bytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
nonceAzure := sha256.Sum256(certFile)
|
||||
attestation, err := svc.AttestationResult(ctx, nonceAzure, attestation.AzureToken)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ func main() {
|
||||
rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath))
|
||||
rootCmd.AddCommand(cliSVC.NewCreateVMCmd())
|
||||
rootCmd.AddCommand(cliSVC.NewRemoveVMCmd())
|
||||
rootCmd.AddCommand(cliSVC.NewIMAMeasurementsCmd())
|
||||
|
||||
// Attestation commands
|
||||
attestationCmd.AddCommand(cliSVC.NewGetAttestationCmd())
|
||||
@@ -162,6 +163,7 @@ func main() {
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewAddHostDataCmd())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewGCPAttestationPolicy())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewDownloadGCPOvmfFile())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewAzureAttestationPolicy())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logErrorCmd(*rootCmd, err)
|
||||
|
||||
+21
-5
@@ -84,7 +84,23 @@ func main() {
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
qemuCfg := qemu.Config{}
|
||||
qemuCfg, err := qemu.NewConfig()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create config: %v", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
if qemuCfg.EnableTDX {
|
||||
logger.Info("Manager started with TDX enabled")
|
||||
} else if qemuCfg.EnableSEVSNP {
|
||||
logger.Info("Manager started with SEV-SNP enabled")
|
||||
} else if qemuCfg.EnableSEV {
|
||||
logger.Info("Manager started with SEV enabled")
|
||||
} else {
|
||||
logger.Info("Manager started without confidential computing support")
|
||||
}
|
||||
|
||||
if err := env.ParseWithOptions(&qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load QEMU configuration: %s", err))
|
||||
exitCode = 1
|
||||
@@ -100,7 +116,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
|
||||
svc, err := newService(ctx, logger, tracer, *qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -112,7 +128,7 @@ func main() {
|
||||
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(svc))
|
||||
}
|
||||
|
||||
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, nil, "", "")
|
||||
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, "", "")
|
||||
|
||||
g.Go(func() error {
|
||||
return gs.Start()
|
||||
@@ -127,8 +143,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
|
||||
func newService(ctx context.Context, logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(ctx, qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ MANAGER_QEMU_SEV_ID=sev0
|
||||
MANAGER_QEMU_SEV_CBITPOS=51
|
||||
MANAGER_QEMU_SEV_REDUCED_PHYS_BITS=1
|
||||
MANAGER_QEMU_HOST_DATA=
|
||||
MANAGER_QEMU_TDX_ID=tdx0
|
||||
MANAGER_QEMU_QUOTE_GENERATION_PORT=4050
|
||||
MANAGER_QEMU_OVMF_FILE=/usr/share/ovmf/OVMF.fd
|
||||
MANAGER_QEMU_VSOCK_ID=vhost-vsock-pci0
|
||||
MANAGER_QEMU_VSOCK_GUEST_CID=3
|
||||
MANAGER_QEMU_VSOCK_VNC=0
|
||||
@@ -51,6 +54,7 @@ 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_ENABLE_TDX=false
|
||||
MANAGER_QEMU_IGVM_FILE=/etc/cocos/coconut-qemu.igvm
|
||||
MANAGER_QEMU_ENABLE_KVM=true
|
||||
MANAGER_QEMU_MACHINE=q35
|
||||
|
||||
@@ -18,28 +18,29 @@ require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/virtee/sev-snp-measure-go v0.0.0-20240530153610-e6e8dc9b6877
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
|
||||
go.opentelemetry.io/otel/trace v1.35.0
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/sync v0.12.0
|
||||
google.golang.org/grpc v1.71.1
|
||||
go.opentelemetry.io/otel/trace v1.36.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/sync v0.14.0
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.51.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/gce-tcb-verifier v0.3.1
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.19.2 // indirect
|
||||
cel.dev/expr v0.20.0 // indirect
|
||||
cloud.google.com/go v0.118.3 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.4.1 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -52,10 +53,12 @@ require (
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.8 // 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
|
||||
@@ -72,10 +75,12 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
@@ -96,6 +101,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
@@ -115,13 +121,13 @@ require (
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -233,8 +233,39 @@ write_files:
|
||||
|
||||
echo "algo_user setup completed successfully"
|
||||
permissions: "0755"
|
||||
|
||||
# IMA setup for first boot
|
||||
- path: /cocos_init/linux_ima_init.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
GRUB_FILE="/etc/default/grub"
|
||||
|
||||
if ! grep -q '^GRUB_CMDLINE_LINUX=.*ima_policy' "$GRUB_FILE"; then
|
||||
echo "First boot, rebooting with fix IMA appraise policy"
|
||||
|
||||
NEW_PARAMS='ima_policy=tcb'
|
||||
|
||||
# Modify the GRUB_CMDLINE_LINUX line
|
||||
sed -i -E \
|
||||
"s#^(GRUB_CMDLINE_LINUX=\")(.*)(\")#\1\2 $NEW_PARAMS\3#" \
|
||||
"$GRUB_FILE"
|
||||
|
||||
echo "Updated GRUB_CMDLINE_LINUX:"
|
||||
grep "^GRUB_CMDLINE_LINUX=" "$GRUB_FILE"
|
||||
|
||||
sudo update-grub
|
||||
sudo reboot
|
||||
else
|
||||
sudo find / -fstype ext4 -type f -uid 0 -exec dd if='{}' of=/dev/null count=0 status=none \;
|
||||
fi
|
||||
permissions: "0755"
|
||||
|
||||
runcmd:
|
||||
# Enable Linux IMA with fix policy
|
||||
- echo "Enable Linux IMA with fix policy and rebooting if necessary"
|
||||
- sh /cocos_init/linux_ima_init.sh
|
||||
|
||||
# Create necessary directories
|
||||
- mkdir -p /cocos
|
||||
- mkdir -p /cocos_init
|
||||
|
||||
@@ -111,7 +111,6 @@ construct_qemu_args() {
|
||||
fi
|
||||
|
||||
args+=("-monitor" "$MONITOR")
|
||||
args+=("-no-reboot")
|
||||
args+=("-vnc" ":9")
|
||||
|
||||
echo "${args[@]}"
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ HAL uses [Buildroot](https://buildroot.org/)'s [_External Tree_ mechanism](https
|
||||
git clone git@github.com:ultravioletrs/cocos.git
|
||||
git clone git@github.com:buildroot/buildroot.git
|
||||
cd buildroot
|
||||
git checkout 2024.11-rc2
|
||||
git checkout 2025.05-rc1
|
||||
make BR2_EXTERNAL=../cocos/hal/linux cocos_defconfig
|
||||
# Execute 'make menuconfig' only if you want to make additional configuration changes to Buildroot.
|
||||
make menuconfig
|
||||
|
||||
@@ -76,3 +76,36 @@ CONFIG_9P_FS_SECURITY=y
|
||||
CONFIG_TCG_TPM=y
|
||||
CONFIG_TCG_TPM2_HMAC=y
|
||||
CONFIG_TCG_PLATFORM=y
|
||||
|
||||
# Linux IMA
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITYFS=y
|
||||
CONFIG_INTEGRITY=y
|
||||
CONFIG_INTEGRITY_SIGNATURE=y
|
||||
CONFIG_IMA=y
|
||||
CONFIG_IMA_MEASURE_PCR_IDX=10 # optional but good
|
||||
CONFIG_IMA_LSM_RULES=y
|
||||
CONFIG_IMA_APPRAISE=y # if you want appraisal (signature checking)
|
||||
CONFIG_IMA_DEFAULT_TEMPLATE="ima-ng"
|
||||
CONFIG_KEYS=y
|
||||
CONFIG_ENCRYPTED_KEYS=y # if you plan to use encrypted keys
|
||||
CONFIG_IMA_DEFAULT_HASH="sha256" # or sha1 if you prefer
|
||||
|
||||
#Intel TDX
|
||||
CONFIG_EXPERT=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_KVM_SW_PROTECTED_VM=y
|
||||
CONFIG_KVM_INTEL=y
|
||||
CONFIG_PCI_MSI=y
|
||||
CONFIG_IRQ_REMAP=y
|
||||
CONFIG_X86_X2APIC=y
|
||||
CONFIG_CRYPTO_SHA256=y
|
||||
CONFIG_X86_SGX=y
|
||||
CONFIG_X86_SGX_KVM=y
|
||||
CONFIG_INTEL_TDX_GUEST=y
|
||||
CONFIG_KSM=n
|
||||
CONFIG_EISA=n
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
CONFIG_VIRTIO_NET=y
|
||||
CONFIG_TDX_GUEST_DRIVER=y
|
||||
|
||||
@@ -7,11 +7,11 @@ Before=docker.service
|
||||
WorkingDirectory=/cocos
|
||||
StandardOutput=file:/var/log/cocos/agent.stdout
|
||||
StandardError=file:/var/log/cocos/agent.stderr
|
||||
|
||||
EnvironmentFile=/etc/cocos/environment
|
||||
|
||||
ExecStartPre=/cocos_init/agent_setup.sh
|
||||
ExecStart=/cocos_init/agent_start_script.sh
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
certscli "github.com/absmach/certs/cli"
|
||||
"github.com/absmach/certs/errors"
|
||||
certssdk "github.com/absmach/certs/sdk"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
@@ -61,7 +60,6 @@ type Server struct {
|
||||
server.BaseServer
|
||||
server *grpc.Server
|
||||
registerService serviceRegister
|
||||
quoteProvider client.LeveledQuoteProvider
|
||||
authSvc auth.Authenticator
|
||||
health *health.Server
|
||||
caUrl string
|
||||
@@ -76,7 +74,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.LeveledQuoteProvider, authSvc auth.Authenticator, caUrl string, cvmId string) server.Server {
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, authSvc auth.Authenticator, caUrl string, cvmId string) server.Server {
|
||||
base := config.GetBaseConfig()
|
||||
listenFullAddress := fmt.Sprintf("%s:%s", base.Host, base.Port)
|
||||
return &Server{
|
||||
@@ -89,7 +87,6 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
|
||||
Logger: logger,
|
||||
},
|
||||
registerService: registerService,
|
||||
quoteProvider: qp,
|
||||
authSvc: authSvc,
|
||||
caUrl: caUrl,
|
||||
cvmId: cvmId,
|
||||
@@ -111,6 +108,7 @@ func (s *Server) Start() error {
|
||||
creds := grpc.Creds(insecure.NewCredentials())
|
||||
var listener net.Listener
|
||||
|
||||
c := s.Config.GetBaseConfig()
|
||||
if agCfg, ok := s.Config.(server.AgentConfig); ok && agCfg.AttestedTLS {
|
||||
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.caUrl, s.cvmId)
|
||||
if err != nil {
|
||||
@@ -127,6 +125,41 @@ func (s *Server) Start() error {
|
||||
Certificates: []tls.Certificate{certificate},
|
||||
}
|
||||
|
||||
var mtls bool
|
||||
mtls = false
|
||||
|
||||
// Loading Server CA file
|
||||
rootCA, err := loadCertFile(c.ServerCAFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load server ca file: %w", err)
|
||||
}
|
||||
if len(rootCA) > 0 {
|
||||
if tlsConfig.RootCAs == nil {
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
}
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) {
|
||||
return fmt.Errorf("failed to append server ca to tls.Config")
|
||||
}
|
||||
mtls = true
|
||||
}
|
||||
|
||||
// Loading Client CA File
|
||||
clientCA, err := loadCertFile(c.ClientCAFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load client ca file: %w", err)
|
||||
}
|
||||
if len(clientCA) > 0 {
|
||||
if tlsConfig.ClientCAs == nil {
|
||||
tlsConfig.ClientCAs = x509.NewCertPool()
|
||||
}
|
||||
if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) {
|
||||
return fmt.Errorf("failed to append client ca to tls.Config")
|
||||
}
|
||||
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
mtls = true
|
||||
}
|
||||
|
||||
creds = grpc.Creds(credentials.NewTLS(tlsConfig))
|
||||
|
||||
listener, err = atls.Listen(
|
||||
@@ -134,12 +167,15 @@ func (s *Server) Start() error {
|
||||
certificateBytes,
|
||||
privateKeyBytes,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Listener for aTLS: %w", err)
|
||||
} else if mtls {
|
||||
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested mTLS", s.Name, s.Address))
|
||||
} else {
|
||||
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))
|
||||
}
|
||||
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))
|
||||
} else {
|
||||
c := s.Config.GetBaseConfig()
|
||||
switch {
|
||||
case c.CertFile != "" || c.KeyFile != "":
|
||||
certificate, err := loadX509KeyPair(c.CertFile, c.KeyFile)
|
||||
@@ -256,6 +292,8 @@ func readFileOrData(input string) ([]byte, error) {
|
||||
data, err := os.ReadFile(input)
|
||||
if err == nil {
|
||||
return data, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return []byte(input), nil
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
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"
|
||||
@@ -70,10 +69,9 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
}
|
||||
logger := slog.Default()
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
|
||||
|
||||
assert.NotNil(t, srv)
|
||||
assert.IsType(t, &Server{}, srv)
|
||||
@@ -120,10 +118,9 @@ func TestServerStartWithTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -167,10 +164,9 @@ func TestServerStartWithmTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -207,10 +203,9 @@ func TestServerStop(t *testing.T) {
|
||||
}
|
||||
buf := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
|
||||
|
||||
go func() {
|
||||
err := srv.Start()
|
||||
@@ -332,60 +327,53 @@ func TestServerInitializationAndStartup(t *testing.T) {
|
||||
expectedLog: "failed to load auth certificates",
|
||||
},
|
||||
{
|
||||
name: "mTLS Server Startup",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupCallback: setupMTLSConfig,
|
||||
expectedLog: "TestServer service gRPC server listening at localhost:0 with TLS",
|
||||
},
|
||||
{
|
||||
name: "mTLS Server Startup with Invalid Root CA",
|
||||
name: "maTLS Server Startup",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
ServerCAFile: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupCallback: setupInvalidRootCAConfig,
|
||||
expectError: true,
|
||||
expectedLog: "failed to append root ca to tls.Config",
|
||||
},
|
||||
{
|
||||
name: "mTLS Server Startup with Invalid Client CA",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
ServerCAFile: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupCallback: setupInvalidClientCAConfig,
|
||||
expectError: true,
|
||||
expectedLog: "failed to append client ca to tls.Config",
|
||||
},
|
||||
{
|
||||
name: "Attested TLS Server Startup",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
ServerCAFile: "",
|
||||
ClientCAFile: "",
|
||||
},
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
expectedLog: "TestServer service gRPC server listening at localhost:0 with Attested TLS",
|
||||
setupCallback: setupMTLSConfig,
|
||||
expectError: false,
|
||||
expectedLog: "with Attested mTLS",
|
||||
},
|
||||
{
|
||||
name: "maTLS Server Startup with Invalid Server CA file",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
ServerCAFile: "invalid",
|
||||
},
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
setupCallback: setupInvalidRootCAConfig,
|
||||
expectError: true,
|
||||
expectedLog: "failed to load server ca file",
|
||||
},
|
||||
{
|
||||
name: "maTLS Server Startup with Invalid Clinet CA file",
|
||||
config: server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: "localhost",
|
||||
Port: "0",
|
||||
ServerCAFile: "invalid",
|
||||
},
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
setupCallback: setupInvalidClientCAConfig,
|
||||
expectError: true,
|
||||
expectedLog: "failed to load client ca file",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -399,10 +387,9 @@ func TestServerInitializationAndStartup(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, authSvc, "", "")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ The service is configured using the environment variables from the following tab
|
||||
| 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_TDX_ID | The ID for the Trust Domain Extensions (TDX) device. | tdx0 |
|
||||
| MANAGER_QEMU_QUOTE_GENERATION_PORT | The port number for virtual socket used to communicate with the Quote Generation Service (QGS). | 4050 |
|
||||
| MANAGER_QEMU_OVMF_FILE | The file path for the OVMF file (combined OVMF_CODE and OVMF_VARS file). | /usr/share/ovmf/OVMF.fd |
|
||||
| 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 |
|
||||
@@ -57,6 +60,7 @@ The service is configured using the environment variables from the following tab
|
||||
| MANAGER_QEMU_USE_SUDO | Whether to use sudo to run QEMU. | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV | Whether to enable Secure Encrypted Virtualization (SEV). | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV_SNP | Whether to enable Secure Nested Paging (SEV-SNP). | true |
|
||||
| MANAGER_QEMU_ENABLE_TDX | Whether to enable Trust Domain Extensions (TDX). | false |
|
||||
| MANAGER_QEMU_ENABLE_KVM | Whether to enable the Kernel-based Virtual Machine (KVM) acceleration. | true |
|
||||
| MANAGER_QEMU_MACHINE | The machine type for QEMU. | q35 |
|
||||
| MANAGER_QEMU_CPU | The CPU model for QEMU. | EPYC |
|
||||
@@ -268,6 +272,20 @@ MANAGER_QEMU_IGVM_FILE=<path to IGVM file> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
To enable [TDX](https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html) support, start manager like this
|
||||
|
||||
```sh
|
||||
MANAGER_GRPC_URL=localhost:7001 \
|
||||
MANAGER_LOG_LEVEL=debug \
|
||||
MANAGER_QEMU_ENABLE_SEV=false \
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=false \
|
||||
MANAGER_QEMU_ENABLE_TDX=true \
|
||||
MANAGER_QEMU_CPU=host \
|
||||
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
|
||||
MANAGER_QEMU_OVMF_FILE=<path to OVMF file> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If the `ps aux | grep qemu-system-x86_64` give you something like this
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/virtee/sev-snp-measure-go/cpuid"
|
||||
"github.com/virtee/sev-snp-measure-go/guest"
|
||||
@@ -63,9 +63,9 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
attestationPolicy := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
if err = attestation.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -110,8 +110,8 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
attestationPolicy.Config.Policy.Measurement = measurement
|
||||
}
|
||||
|
||||
if vmi.Config.SevConfig.EnableHostData {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SevConfig.HostData)
|
||||
if vmi.Config.SEVConfig.EnableHostData {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SEVConfig.HostData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+58
-11
@@ -5,11 +5,14 @@ package qemu
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
const (
|
||||
BaseGuestCID = 3
|
||||
KernelCommandLine = "quiet console=null"
|
||||
TdxObject = "'{\"qom-type\":\"tdx-guest\",\"id\":\"%s\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"%d\"}}'"
|
||||
)
|
||||
|
||||
type MemoryConfig struct {
|
||||
@@ -52,7 +55,7 @@ type DiskImgConfig struct {
|
||||
RootFsFile string `env:"DISK_IMG_ROOTFS_FILE" envDefault:"img/rootfs.cpio.gz"`
|
||||
}
|
||||
|
||||
type SevConfig struct {
|
||||
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"`
|
||||
@@ -60,6 +63,12 @@ type SevConfig struct {
|
||||
HostData string `env:"HOST_DATA" envDefault:""`
|
||||
}
|
||||
|
||||
type TDXConfig struct {
|
||||
ID string `env:"TDX_ID" envDefault:"tdx0"`
|
||||
QuoteGenerationPort int `env:"QUOTE_GENERATION_PORT" envDefault:"4050"`
|
||||
OVMF string `env:"OVMF_FILE" envDefault:"/usr/share/ovmf/OVMF.fd"`
|
||||
}
|
||||
|
||||
type IGVMConfig struct {
|
||||
ID string `env:"IGVM_ID" envDefault:"igvm0"`
|
||||
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
|
||||
@@ -71,10 +80,11 @@ type VSockConfig struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
EnableSEV bool
|
||||
EnableSEVSNP bool
|
||||
EnableTDX bool
|
||||
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
|
||||
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
|
||||
EnableSEV bool `env:"ENABLE_SEV" envDefault:"false"`
|
||||
EnableSEVSNP bool `env:"ENABLE_SEV_SNP" envDefault:"true"`
|
||||
|
||||
EnableKVM bool `env:"ENABLE_KVM" envDefault:"true"`
|
||||
|
||||
@@ -101,7 +111,10 @@ type Config struct {
|
||||
DiskImgConfig
|
||||
|
||||
// SEV
|
||||
SevConfig
|
||||
SEVConfig
|
||||
|
||||
// TDX
|
||||
TDXConfig
|
||||
|
||||
// vTPM
|
||||
IGVMConfig
|
||||
@@ -142,7 +155,7 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
config.MemoryConfig.Slots,
|
||||
config.MemoryConfig.Max))
|
||||
|
||||
if !config.EnableSEVSNP {
|
||||
if !config.EnableSEVSNP && !config.EnableTDX {
|
||||
// OVMF
|
||||
args = append(args, "-drive",
|
||||
fmt.Sprintf("if=%s,format=%s,unit=%d,file=%s,readonly=%s",
|
||||
@@ -183,15 +196,15 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,igvm-cfg=%s",
|
||||
config.SevConfig.ID,
|
||||
config.SEVConfig.ID,
|
||||
config.MemID,
|
||||
config.IGVMConfig.ID))
|
||||
|
||||
if config.EnableSEVSNP {
|
||||
sevType = "sev-snp-guest"
|
||||
|
||||
if config.SevConfig.EnableHostData {
|
||||
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
|
||||
if config.SEVConfig.EnableHostData {
|
||||
hostData = fmt.Sprintf(",host-data=%s", config.SEVConfig.HostData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +216,9 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
|
||||
sevType,
|
||||
config.SevConfig.ID,
|
||||
config.SevConfig.CBitPos,
|
||||
config.SevConfig.ReducedPhysBits,
|
||||
config.SEVConfig.ID,
|
||||
config.SEVConfig.CBitPos,
|
||||
config.SEVConfig.ReducedPhysBits,
|
||||
hostData))
|
||||
|
||||
args = append(args, "-object",
|
||||
@@ -214,6 +227,26 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
config.IGVMConfig.File))
|
||||
}
|
||||
|
||||
if config.EnableTDX {
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf(TdxObject,
|
||||
config.TDXConfig.ID,
|
||||
config.TDXConfig.QuoteGenerationPort))
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,hpet=off",
|
||||
config.TDXConfig.ID,
|
||||
config.MemID))
|
||||
|
||||
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, "-bios", config.TDXConfig.OVMF)
|
||||
args = append(args, "-nodefaults")
|
||||
}
|
||||
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
args = append(args, "-append", strconv.Quote(KernelCommandLine))
|
||||
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
|
||||
@@ -237,3 +270,17 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
cfg := Config{}
|
||||
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.EnableSEV = SEVEnabledOnHost()
|
||||
cfg.EnableSEVSNP = SEVSNPEnabledOnHost()
|
||||
cfg.EnableTDX = TDXEnabledOnHost()
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
SevConfig: SevConfig{
|
||||
SEVConfig: SEVConfig{
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
@@ -174,7 +174,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
config := Config{
|
||||
EnableSEVSNP: true,
|
||||
SevConfig: SevConfig{
|
||||
SEVConfig: SEVConfig{
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
|
||||
+54
-1
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -60,7 +61,8 @@ 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)
|
||||
v.vmi.Config.SEVConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SEVConfig.ID, id)
|
||||
v.vmi.Config.TDXConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.TDXConfig.ID, id)
|
||||
|
||||
if !v.vmi.Config.EnableSEVSNP {
|
||||
// Copy firmware vars file.
|
||||
@@ -197,3 +199,54 @@ func (v *qemuVM) GetCID() int {
|
||||
func (v *qemuVM) GetConfig() interface{} {
|
||||
return v.vmi
|
||||
}
|
||||
|
||||
func SEVEnabled(cpuinfo string, sevPresent bool) bool {
|
||||
return strings.Contains(cpuinfo, "sev") && sevPresent
|
||||
}
|
||||
|
||||
func SEVSNPEnabled(cpuinfo, kernelParam string) bool {
|
||||
return strings.Contains(cpuinfo, "sev_snp") && strings.TrimSpace(kernelParam) == "1"
|
||||
}
|
||||
|
||||
func TDXEnabled(cpuinfo, kernelParam string) bool {
|
||||
return strings.Contains(cpuinfo, "tdx_host_platform") && strings.TrimSpace(kernelParam) == "1"
|
||||
}
|
||||
|
||||
// Checks if SEV is supported and usable by verifying both CPU flags and the /dev/sev device.
|
||||
func SEVEnabledOnHost() bool {
|
||||
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = os.Stat("/dev/sev")
|
||||
return SEVEnabled(string(cpuinfo), err == nil)
|
||||
}
|
||||
|
||||
func SEVSNPEnabledOnHost() bool {
|
||||
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
kernelParam, err := os.ReadFile("/sys/module/kvm_amd/parameters/sev_snp")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return SEVSNPEnabled(string(cpuinfo), string(kernelParam))
|
||||
}
|
||||
|
||||
func TDXEnabledOnHost() bool {
|
||||
cpuinfo, err := os.ReadFile("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
kernelParam, err := os.ReadFile("/sys/module/kvm_intel/parameters/tdx")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return TDXEnabled(string(cpuinfo), string(kernelParam))
|
||||
}
|
||||
|
||||
@@ -161,3 +161,45 @@ func TestGetConfig(t *testing.T) {
|
||||
config := vm.GetConfig()
|
||||
assert.Equal(t, expectedConfig, config)
|
||||
}
|
||||
|
||||
func TestSEVEnabled(t *testing.T) {
|
||||
t.Run("cpuinfo contains sev and device exists", func(t *testing.T) {
|
||||
assert.True(t, SEVEnabled("flags: xyz sev abc", true))
|
||||
})
|
||||
|
||||
t.Run("cpuinfo missing sev", func(t *testing.T) {
|
||||
assert.False(t, SEVEnabled("flags: xyz abc", true))
|
||||
})
|
||||
|
||||
t.Run("device does not exist", func(t *testing.T) {
|
||||
assert.False(t, SEVEnabled("flags: sev abc", false))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSEVSNPEnabled(t *testing.T) {
|
||||
t.Run("cpuinfo and kvm param correct", func(t *testing.T) {
|
||||
assert.True(t, SEVSNPEnabled("flags: sev_snp abc", "1"))
|
||||
})
|
||||
|
||||
t.Run("missing sev_snp in cpuinfo", func(t *testing.T) {
|
||||
assert.False(t, SEVSNPEnabled("flags: abc", "1"))
|
||||
})
|
||||
|
||||
t.Run("kernel param not enabled", func(t *testing.T) {
|
||||
assert.False(t, SEVSNPEnabled("flags: sev_snp", "0"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTDXEnabled(t *testing.T) {
|
||||
t.Run("cpuinfo and kvm param correct", func(t *testing.T) {
|
||||
assert.True(t, TDXEnabled("flags: tdx_host_platform abc", "1"))
|
||||
})
|
||||
|
||||
t.Run("missing tdx_host_platform in cpuinfo", func(t *testing.T) {
|
||||
assert.False(t, TDXEnabled("flags: abc", "1"))
|
||||
})
|
||||
|
||||
t.Run("kernel param not enabled", func(t *testing.T) {
|
||||
assert.False(t, TDXEnabled("flags: tdx_host_platform", "0"))
|
||||
})
|
||||
}
|
||||
|
||||
+8
-6
@@ -22,7 +22,7 @@ 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/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
@@ -99,12 +99,13 @@ type managerService struct {
|
||||
portRangeMax int
|
||||
persistence qemu.Persistence
|
||||
eosVersion string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
func New(ctx context.Context, cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
start, end, err := decodeRange(cfg.HostFwdRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -127,6 +128,7 @@ func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinary
|
||||
portRangeMax: end,
|
||||
persistence: persistence,
|
||||
eosVersion: eosVersion,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
if err := ms.restoreVMs(); err != nil {
|
||||
@@ -181,9 +183,9 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
return "", id, errors.Wrap(ErrFailedToCreateAttestationPolicy, err)
|
||||
}
|
||||
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
attestationPolicy := attestation.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &attestation.PcrConfig{}}
|
||||
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
if err = attestation.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
@@ -216,7 +218,7 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
if cfg.Config.EnableSEVSNP {
|
||||
todo := sha3.Sum256([]byte("TODO"))
|
||||
// Define host-data value of QEMU for SEV-SNP, with a base64 encoding of the computation hash.
|
||||
cfg.Config.SevConfig.HostData = base64.StdEncoding.EncodeToString(todo[:])
|
||||
cfg.Config.SEVConfig.HostData = base64.StdEncoding.EncodeToString(todo[:])
|
||||
}
|
||||
|
||||
cvm := ms.vmFactory(cfg, id, ms.logger)
|
||||
@@ -239,7 +241,7 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
if err := ms.RemoveVM(ctx, id); err != nil {
|
||||
ms.logger.Error("Failed to remove VM after TTL", "error", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
case <-ms.ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestNew(t *testing.T) {
|
||||
logger := slog.Default()
|
||||
vmf := new(mocks.Provider)
|
||||
|
||||
service, err := New(cfg, "", "", "", logger, vmf.Execute, "")
|
||||
service, err := New(context.Background(), cfg, "", "", "", logger, vmf.Execute, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
|
||||
+7
-14
@@ -41,13 +41,6 @@ packages:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "state.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/internal/logger:
|
||||
interfaces:
|
||||
io.Writer:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "io_writer.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/internal/server:
|
||||
interfaces:
|
||||
Server:
|
||||
@@ -105,13 +98,6 @@ packages:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "server.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/google/go-sev-guest/client:
|
||||
interfaces:
|
||||
LeveledQuoteProvider:
|
||||
config:
|
||||
dir: "./pkg/attestation/quoteprovider/mocks"
|
||||
filename: "QuoteProvider.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage:
|
||||
interfaces:
|
||||
Storage:
|
||||
@@ -126,3 +112,10 @@ packages:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "client.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/pkg/attestation:
|
||||
interfaces:
|
||||
Provider:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "attestation.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
|
||||
+49
-55
@@ -5,14 +5,15 @@ package atls
|
||||
|
||||
// #cgo LDFLAGS: -lssl -lcrypto
|
||||
// #include "extensions.h"
|
||||
// #include <string.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"crypto/sha3"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"runtime/cgo"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -20,15 +21,12 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
const (
|
||||
NoTee int = iota
|
||||
AmdSevSnp
|
||||
)
|
||||
|
||||
const (
|
||||
noError = 0
|
||||
errorZeroReturn = 6
|
||||
@@ -37,6 +35,7 @@ const (
|
||||
errorSyscall = 5
|
||||
errorSsl = 1
|
||||
waitTime = 2
|
||||
vmpl2 = 2
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -51,57 +50,42 @@ var (
|
||||
errConnCreate = errors.New("could not create connection")
|
||||
)
|
||||
|
||||
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)
|
||||
return uintptr(handle)
|
||||
func formTeeData(pubKey []byte, teeNonce []byte) []byte {
|
||||
combined := append(pubKey, teeNonce...)
|
||||
sum := sha3.Sum512(combined)
|
||||
return sum[:]
|
||||
}
|
||||
|
||||
func registerValidationVerification(callback ValidationVerification) uintptr {
|
||||
handle := cgo.NewHandle(callback)
|
||||
return uintptr(handle)
|
||||
}
|
||||
|
||||
//export validationVerificationCallback
|
||||
func validationVerificationCallback(teeType C.int) uintptr {
|
||||
switch int(teeType) {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerValidationVerification(vtpm.VTPMVerify)
|
||||
func getPlatformProvider(platformType attestation.PlatformType, pubKey []byte) (attestation.Provider, error) {
|
||||
switch platformType {
|
||||
case attestation.SNPvTPM:
|
||||
return vtpm.New(pubKey, true, vmpl2, nil), nil
|
||||
case attestation.Azure:
|
||||
return azure.New(nil), nil
|
||||
default:
|
||||
return uintptr(0)
|
||||
}
|
||||
}
|
||||
|
||||
//export fetchAttestationCallback
|
||||
func fetchAttestationCallback(teeType C.int) uintptr {
|
||||
switch int(teeType) {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerFetchAttestation(vtpm.FetchATLSQuote)
|
||||
default:
|
||||
return uintptr(0)
|
||||
return nil, fmt.Errorf("unsupported platform type: %d", platformType)
|
||||
}
|
||||
}
|
||||
|
||||
//export callVerificationValidationCallback
|
||||
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)
|
||||
func callVerificationValidationCallback(platformType C.int, pubKey *C.uchar, pubKeyLen C.int, attestReport *C.uchar, attestReportSize C.int, teeNonceByte *C.uchar, vTPMNonceByte *C.uchar) C.int {
|
||||
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)
|
||||
teeNonceData := C.GoBytes(unsafe.Pointer(teeNonceByte), quoteprovider.Nonce)
|
||||
vTPMNonce := C.GoBytes(unsafe.Pointer(vTPMNonceByte), vtpm.Nonce)
|
||||
pType := attestation.PlatformType(int(platformType))
|
||||
attestationReport := C.GoBytes(unsafe.Pointer(attestReport), attestReportSize)
|
||||
|
||||
teeData := formTeeData(pubKeyCert, teeNonceData)
|
||||
|
||||
err := callback(attestationReport, pubKeyCert, teeData, nonceData)
|
||||
provider, err := getPlatformProvider(pType, pubKeyCert)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "callback failed %v", err)
|
||||
fmt.Fprintf(os.Stderr, "no attestation provider found for platform type %s", err.Error())
|
||||
return C.int(-1)
|
||||
}
|
||||
|
||||
err = provider.VerifyAttestation(attestationReport, teeData, vTPMNonce)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "verification callback failed %s", err.Error())
|
||||
return C.int(-1)
|
||||
}
|
||||
|
||||
@@ -109,18 +93,23 @@ func callVerificationValidationCallback(callbackHandle uintptr, pubKey *C.uchar,
|
||||
}
|
||||
|
||||
//export callFetchAttestationCallback
|
||||
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)
|
||||
func callFetchAttestationCallback(platformType C.int, pubKey *C.uchar, pubKeyLen C.int, teeNonceByte *C.uchar, vTPMNonceByte *C.uchar, outlen *C.ulong) *C.uchar {
|
||||
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
|
||||
teeNonceData := C.GoBytes(unsafe.Pointer(teeNonceByte), quoteprovider.Nonce)
|
||||
vTPMNonce := C.GoBytes(unsafe.Pointer(vTPMNonceByte), vtpm.Nonce)
|
||||
pType := attestation.PlatformType(int(platformType))
|
||||
|
||||
quote, err := callback(pubKeyCert, teeNonceData, vTPMNonce)
|
||||
teeData := formTeeData(pubKeyCert, teeNonceData)
|
||||
|
||||
provider, err := getPlatformProvider(pType, pubKeyCert)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "attestation callback returned nil")
|
||||
fmt.Fprintf(os.Stderr, "no attestation provider found for platform type %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
quote, err := provider.Attestation(teeData, vTPMNonce)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "attestation callback returned nil: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,6 +125,11 @@ func callFetchAttestationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKe
|
||||
return (*C.uchar)(resultC)
|
||||
}
|
||||
|
||||
//export returnCCPlatformType
|
||||
func returnCCPlatformType() int32 {
|
||||
return int32(attestation.CCPlatform())
|
||||
}
|
||||
|
||||
type ATLSServerListener struct {
|
||||
tlsListener *C.tls_server_connection
|
||||
}
|
||||
@@ -252,7 +246,7 @@ func (c *ATLSConn) Read(b []byte) (int, error) {
|
||||
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\n")
|
||||
return 0, fmt.Errorf("SSL error")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-53
@@ -7,41 +7,28 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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 int callVerificationValidationCallback(int platformType, const u_char* pubKey, int pubKeyLen, const u_char* quote, int quoteSize, const u_char* teeNonceByte, const u_char* vTPMNonceByte);
|
||||
extern u_char* callFetchAttestationCallback(int platformType, 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);
|
||||
extern uintptr_t getPlatformTypeHandle(int platformType, u_char *teeNonce, u_char *vtpmNonce);
|
||||
extern int returnCCPlatformType();
|
||||
|
||||
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) {
|
||||
int triggerVerificationValidationCallback(int platformType, 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, pub_key, pub_key_len, quote, quote_size, tee_nonce, vtpm_nonce);
|
||||
return callVerificationValidationCallback(platformType, pub_key, pub_key_len, quote, quote_size, tee_nonce, vtpm_nonce);
|
||||
}
|
||||
|
||||
u_char* triggerFetchAttestationCallback(uintptr_t callback_handle, u_char* pub_key, int pub_key_len, char *tee_nonce, char *vtpm_nonce, unsigned long *outlen) {
|
||||
u_char* triggerFetchAttestationCallback(int platformType, 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(callback_handle, pub_key, pub_key_len, tee_nonce, vtpm_nonce, outlen);
|
||||
}
|
||||
|
||||
int check_sev_snp() {
|
||||
int fd = open(SEV_GUEST_DRIVER_PATH, O_RDONLY);
|
||||
|
||||
if (fd == -1) {
|
||||
perror("Error opening /dev/sev-guest");
|
||||
fprintf(stderr, "SEV guest driver is not available.\n");
|
||||
return -1;
|
||||
} else {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return 1;
|
||||
return callFetchAttestationCallback(platformType, pub_key, pub_key_len, tee_nonce, vtpm_nonce, outlen);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -78,9 +65,9 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
}
|
||||
|
||||
if (ext_data != NULL) {
|
||||
if (RAND_bytes(ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE) != 1) {
|
||||
if (RAND_bytes(ext_data->er.vtpm_nonce, NONCE_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);
|
||||
SSL_get_client_random(s, ext_data->er.vtpm_nonce, NONCE_RANDOM_SIZE);
|
||||
}
|
||||
|
||||
if (RAND_bytes(ext_data->er.tee_nonce, REPORT_DATA_SIZE) != 1) {
|
||||
@@ -94,10 +81,8 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(er->vtpm_nonce, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(er->vtpm_nonce, ext_data->er.vtpm_nonce, NONCE_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;
|
||||
|
||||
*out = (const u_char *)er;
|
||||
*outlen = sizeof(evidence_request);
|
||||
@@ -116,19 +101,8 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (check_sev_snp() > 0) {
|
||||
*platform_type = AMD_TEE;
|
||||
} else {
|
||||
*platform_type = NO_TEE;
|
||||
}
|
||||
|
||||
if ((*platform_type != ext_data->er.tee_type) || (*platform_type == NO_TEE)) {
|
||||
*platform_type = NO_TEE;
|
||||
ext_data->er.tee_type = NO_TEE;
|
||||
} else {
|
||||
ext_data->er.tee_type = AMD_TEE;
|
||||
ext_data->fetch_attestation_handler = fetchAttestationCallback(ext_data->er.tee_type);
|
||||
}
|
||||
*platform_type = returnCCPlatformType();
|
||||
ext_data->platform_type = *platform_type;
|
||||
|
||||
*out = (u_char*)platform_type;
|
||||
*outlen = sizeof(int32_t);
|
||||
@@ -164,9 +138,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.vtpm_nonce, er->vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(ext_data->er.vtpm_nonce, er->vtpm_nonce, NONCE_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");
|
||||
return 0;
|
||||
@@ -175,18 +148,11 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
}
|
||||
case SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS:
|
||||
{
|
||||
int *tee_type = (int*)in;
|
||||
int *platform_type = (int*)in;
|
||||
tls_extension_data *ext_data = (tls_extension_data*)parse_arg;
|
||||
|
||||
if (ext_data != NULL) {
|
||||
ext_data->er.tee_type = *tee_type;
|
||||
|
||||
if (ext_data->er.tee_type != NO_TEE) {
|
||||
ext_data->verification_validation_handler = validationVerificationCallback(ext_data->er.tee_type);
|
||||
} else {
|
||||
fprintf(stderr, "must use a TEE for aTLS\n");
|
||||
return 0;
|
||||
}
|
||||
ext_data->platform_type = *platform_type;
|
||||
} else {
|
||||
fprintf(stderr, "parse_arg is NULL\n");
|
||||
return 0;
|
||||
@@ -253,7 +219,7 @@ int attestation_certificate_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
return -1;
|
||||
}
|
||||
|
||||
quote = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, pubkey_buf, pubkey_len, ext_data->er.tee_nonce, ext_data->er.vtpm_nonce, &len);
|
||||
quote = triggerFetchAttestationCallback(ext_data->platform_type, 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;
|
||||
@@ -291,7 +257,6 @@ int attestation_certificate_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
switch (context)
|
||||
{
|
||||
case SSL_EXT_CLIENT_HELLO:
|
||||
// Return 1 so the server can return the custom certificate extension.
|
||||
return 1;
|
||||
case SSL_EXT_TLS1_3_CERTIFICATE:
|
||||
{
|
||||
@@ -324,7 +289,7 @@ int attestation_certificate_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
}
|
||||
memcpy(quote, in, inlen);
|
||||
|
||||
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
|
||||
res = triggerVerificationValidationCallback(ext_data->platform_type,
|
||||
pubkey_buf,
|
||||
pubkey_len,
|
||||
quote,
|
||||
|
||||
+3
-11
@@ -7,25 +7,19 @@
|
||||
#define EVIDENCE_REQUEST_HELLO_EXTENSION_TYPE 65
|
||||
#define ATTESTATION_CERTIFICATE_EXTENSION_TYPE 66
|
||||
#define REPORT_DATA_SIZE 64
|
||||
#define CLIENT_RANDOM_SIZE 32
|
||||
#define NONCE_RANDOM_SIZE 32
|
||||
#define TLS_CLIENT_CTX 0
|
||||
#define TLS_SERVER_CTX 1
|
||||
|
||||
#define SEV_GUEST_DRIVER_PATH "/dev/sev-guest"
|
||||
#define NO_TEE 0
|
||||
#define AMD_TEE 1
|
||||
|
||||
typedef struct evidence_request
|
||||
{
|
||||
int tee_type;
|
||||
char vtpm_nonce[CLIENT_RANDOM_SIZE];
|
||||
char vtpm_nonce[NONCE_RANDOM_SIZE];
|
||||
char tee_nonce[REPORT_DATA_SIZE];
|
||||
} evidence_request;
|
||||
|
||||
typedef struct tls_extension_data
|
||||
{
|
||||
uintptr_t fetch_attestation_handler;
|
||||
uintptr_t verification_validation_handler;
|
||||
int platform_type;
|
||||
evidence_request er;
|
||||
} tls_extension_data;
|
||||
|
||||
@@ -37,7 +31,6 @@ typedef struct tls_server_connection
|
||||
char* key;
|
||||
int key_len;
|
||||
struct sockaddr_storage addr;
|
||||
uintptr_t fetch_attestation_handler;
|
||||
} tls_server_connection;
|
||||
|
||||
typedef struct tls_connection
|
||||
@@ -61,7 +54,6 @@ int set_socket_read_timeout(tls_connection* conn, int timeout_sec, int timeout_u
|
||||
int set_socket_write_timeout(tls_connection* conn, int timeout_sec, int timeout_usec);
|
||||
char* tls_return_addr(struct sockaddr_storage *addr);
|
||||
int tls_return_port(struct sockaddr_storage *addr);
|
||||
int compute_sha256_of_public_key(X509 *cert, unsigned char *hash);
|
||||
|
||||
// Extensions
|
||||
void evidence_request_ext_free_cb(SSL *s, unsigned int ext_type,
|
||||
|
||||
+1
-2
@@ -289,9 +289,8 @@ tls_connection* tls_server_accept(tls_server_connection *tls_server) {
|
||||
goto cleanup_fd;
|
||||
}
|
||||
|
||||
// Set file descriptor and assign handlers
|
||||
// Set file descriptor
|
||||
conn->socket_fd = client_fd;
|
||||
conn->tls_ext_data.fetch_attestation_handler = tls_server->fetch_attestation_handler;
|
||||
SSL_set_fd(conn->ssl, client_fd);
|
||||
|
||||
// Get local address
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config
|
||||
package attestation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type AttestationType int32
|
||||
type PlatformType int
|
||||
|
||||
const (
|
||||
SNP AttestationType = iota
|
||||
SNP PlatformType = iota
|
||||
VTPM
|
||||
SNPvTPM
|
||||
AzureToken
|
||||
Azure
|
||||
NoCC
|
||||
)
|
||||
|
||||
const (
|
||||
azureMetadataUrl = "http://169.254.169.254/metadata/instance"
|
||||
azureApiVersion = "2021-02-01"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -43,6 +56,21 @@ type Config struct {
|
||||
*PcrConfig
|
||||
}
|
||||
|
||||
type ccCheck struct {
|
||||
checkFunc func() bool
|
||||
platform PlatformType
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error)
|
||||
TeeAttestation(teeNonce []byte) ([]byte, error)
|
||||
VTpmAttestation(vTpmNonce []byte) ([]byte, error)
|
||||
VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error
|
||||
VerifTeeAttestation(report []byte, teeNonce []byte) error
|
||||
VerifVTpmAttestation(report []byte, vTpmNonce []byte) error
|
||||
AzureAttestationToken(tokenNonce []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config) error {
|
||||
if policyPath != "" {
|
||||
policyData, err := os.ReadFile(policyPath)
|
||||
@@ -69,3 +97,64 @@ func ReadAttestationPolicyFromByte(policyData []byte, attestationConfiguration *
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CCPlatform returns the type of the confidential computing platform.
|
||||
func CCPlatform() PlatformType {
|
||||
checks := []ccCheck{
|
||||
{SevGuestvTPMExists, SNPvTPM},
|
||||
{SevGuesDeviceExists, SNP},
|
||||
{isAzureVM, Azure},
|
||||
}
|
||||
|
||||
for _, c := range checks {
|
||||
if c.checkFunc() {
|
||||
return c.platform
|
||||
}
|
||||
}
|
||||
return NoCC
|
||||
}
|
||||
|
||||
func SevGuesDeviceExists() bool {
|
||||
d, err := client.OpenDevice()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
d.Close()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func SevGuestvTPMExists() bool {
|
||||
d, err := tpm2.OpenTPM()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
d.Close()
|
||||
|
||||
return SevGuesDeviceExists()
|
||||
}
|
||||
|
||||
func isAzureVM() bool {
|
||||
client := &http.Client{}
|
||||
url := fmt.Sprintf("%s?api-version=%s", azureMetadataUrl, azureApiVersion)
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Add("Metadata", "true")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(body) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
)
|
||||
|
||||
type EnvConfig struct {
|
||||
OSBuild string
|
||||
OSType string
|
||||
OSDistro string
|
||||
MaaURL string
|
||||
}
|
||||
|
||||
func NewEnvConfigFromAgent(agentOSBuild, agentOSType, agentOSDistro, maaURL string) *EnvConfig {
|
||||
return &EnvConfig{
|
||||
OSBuild: agentOSBuild,
|
||||
OSType: agentOSType,
|
||||
OSDistro: agentOSDistro,
|
||||
MaaURL: maaURL,
|
||||
}
|
||||
}
|
||||
|
||||
func InitializeDefaultMAAVars(config *EnvConfig) {
|
||||
maa.OSBuild = config.OSBuild
|
||||
maa.OSType = config.OSType
|
||||
maa.OSDistro = config.OSDistro
|
||||
MaaURL = config.MaaURL
|
||||
}
|
||||
|
||||
func (c *EnvConfig) InitializeOSVars(build, osType, osDistro string) {
|
||||
if build != "" {
|
||||
c.OSBuild = build
|
||||
}
|
||||
if osType != "" {
|
||||
c.OSType = osType
|
||||
}
|
||||
if osDistro != "" {
|
||||
c.OSDistro = osDistro
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewEnvConfigFromAgent(t *testing.T) {
|
||||
// Given
|
||||
expectedBuild := "CustomBuild123"
|
||||
expectedType := "Linux"
|
||||
expectedDistro := "UVC-Debian"
|
||||
expectedURL := "https://test.attest.azure.net"
|
||||
|
||||
// When
|
||||
cfg := NewEnvConfigFromAgent(expectedBuild, expectedType, expectedDistro, expectedURL)
|
||||
|
||||
// Then
|
||||
if cfg.OSBuild != expectedBuild {
|
||||
t.Errorf("expected OSBuild = %s, got %s", expectedBuild, cfg.OSBuild)
|
||||
}
|
||||
if cfg.OSType != expectedType {
|
||||
t.Errorf("expected OSType = %s, got %s", expectedType, cfg.OSType)
|
||||
}
|
||||
if cfg.OSDistro != expectedDistro {
|
||||
t.Errorf("expected OSDistro = %s, got %s", expectedDistro, cfg.OSDistro)
|
||||
}
|
||||
if cfg.MaaURL != expectedURL {
|
||||
t.Errorf("expected MaaURL = %s, got %s", expectedURL, cfg.MaaURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitializeDefaultMAAVars(t *testing.T) {
|
||||
cfg := &EnvConfig{
|
||||
OSBuild: "build123",
|
||||
OSType: "CustomOS",
|
||||
OSDistro: "DistroX",
|
||||
}
|
||||
InitializeDefaultMAAVars(cfg)
|
||||
|
||||
assert.Equal(t, "build123", maa.OSBuild)
|
||||
assert.Equal(t, "CustomOS", maa.OSType)
|
||||
assert.Equal(t, "DistroX", maa.OSDistro)
|
||||
}
|
||||
|
||||
func TestInitializeOSVars(t *testing.T) {
|
||||
cfg := &EnvConfig{}
|
||||
cfg.InitializeOSVars("buildX", "TypeY", "DistroZ")
|
||||
|
||||
assert.Equal(t, "buildX", cfg.OSBuild)
|
||||
assert.Equal(t, "TypeY", cfg.OSType)
|
||||
assert.Equal(t, "DistroZ", cfg.OSDistro)
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/kds"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var MaaURL = "https://sharedeus2.eus2.attest.azure.net"
|
||||
|
||||
var _ attestation.Provider = (*provider)(nil)
|
||||
|
||||
type provider struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func New(writer io.Writer) attestation.Provider {
|
||||
return provider{writer: writer}
|
||||
}
|
||||
|
||||
func (a provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
||||
var tokenNonce [vtpm.Nonce]byte
|
||||
copy(tokenNonce[:], teeNonce)
|
||||
|
||||
params, err := maa.NewParameters(context.Background(), tokenNonce[:], http.DefaultClient, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get report: %w", err)
|
||||
}
|
||||
|
||||
snpReport, err := report.ParseAttestation(params.SNPReport, "bin")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse SNP report: %w", err)
|
||||
}
|
||||
|
||||
quote, err := vtpm.FetchQuote(vTpmNonce)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch quote: %w", err)
|
||||
}
|
||||
|
||||
quote.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
||||
SevSnpAttestation: snpReport,
|
||||
}
|
||||
|
||||
return proto.Marshal(quote)
|
||||
}
|
||||
|
||||
func (a provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
var tokenNonce [vtpm.Nonce]byte
|
||||
copy(tokenNonce[:], teeNonce)
|
||||
|
||||
params, err := maa.NewParameters(context.Background(), tokenNonce[:], http.DefaultClient, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get report: %w", err)
|
||||
}
|
||||
|
||||
return params.SNPReport, nil
|
||||
}
|
||||
|
||||
func (a provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
||||
quote, err := vtpm.FetchQuote(vTpmNonce)
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(vtpm.ErrFetchQuote, err)
|
||||
}
|
||||
|
||||
return proto.Marshal(quote)
|
||||
}
|
||||
|
||||
func (a provider) VerifTeeAttestation(report []byte, teeNonce []byte) error {
|
||||
attestationReport, err := abi.ReportCertsToProto(report)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
}
|
||||
|
||||
return quoteprovider.VerifyAttestationReportTLS(attestationReport, teeNonce)
|
||||
}
|
||||
|
||||
func (a provider) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
|
||||
return vtpm.VerifyQuote(report, nil, vTpmNonce, a.writer)
|
||||
}
|
||||
|
||||
func (a provider) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
|
||||
var tokenNonce [vtpm.Nonce]byte
|
||||
copy(tokenNonce[:], teeNonce)
|
||||
|
||||
quote := &attest.Attestation{}
|
||||
err := proto.Unmarshal(report, quote)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal vTPM quote: %w", err)
|
||||
}
|
||||
|
||||
snpReport := quote.GetSevSnpAttestation()
|
||||
if err = quoteprovider.VerifyAttestationReportTLS(snpReport, nil); err != nil {
|
||||
return fmt.Errorf("failed to verify vTPM attestation report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
|
||||
quote, err := FetchAzureAttestationToken(tokenNonce, MaaURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(vtpm.ErrFetchAzureToken, err)
|
||||
}
|
||||
|
||||
return quote, nil
|
||||
}
|
||||
|
||||
func GenerateAttestationPolicy(token, product string, policy uint64) (*attestation.Config, error) {
|
||||
claims, err := validateToken(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate token: %w", err)
|
||||
}
|
||||
|
||||
tee, ok := claims["x-ms-isolation-tee"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get tee from claims")
|
||||
}
|
||||
|
||||
familyIdString, ok := tee["x-ms-sevsnpvm-familyId"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get familyId from claims")
|
||||
}
|
||||
|
||||
familyId, err := hex.DecodeString(familyIdString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode familyId: %w", err)
|
||||
}
|
||||
|
||||
imageIdString, ok := tee["x-ms-sevsnpvm-imageId"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get imageId from claims")
|
||||
}
|
||||
imageId, err := hex.DecodeString(imageIdString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode imageId: %w", err)
|
||||
}
|
||||
|
||||
measurementString, ok := tee["x-ms-sevsnpvm-launchmeasurement"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get measurement from claims")
|
||||
}
|
||||
measurement, err := hex.DecodeString(measurementString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode measurement: %w", err)
|
||||
}
|
||||
|
||||
bootloaderVersion, ok := tee["x-ms-sevsnpvm-bootloader-svn"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get bootloader version from claims")
|
||||
}
|
||||
|
||||
teeVersion, ok := tee["x-ms-sevsnpvm-tee-svn"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get tee version from claims")
|
||||
}
|
||||
|
||||
snpVersion, ok := tee["x-ms-sevsnpvm-snpfw-svn"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get snp version from claims")
|
||||
}
|
||||
|
||||
microcodeVersion, ok := tee["x-ms-sevsnpvm-microcode-svn"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get microcode version from claims")
|
||||
}
|
||||
|
||||
minimalTCBParts := kds.TCBParts{
|
||||
BlSpl: uint8(bootloaderVersion),
|
||||
TeeSpl: uint8(teeVersion),
|
||||
SnpSpl: uint8(snpVersion),
|
||||
UcodeSpl: uint8(microcodeVersion),
|
||||
}
|
||||
|
||||
// Minimum TCB at the moment is not valid and will be fixed in the future.
|
||||
_, err = kds.ComposeTCBParts(minimalTCBParts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compose TCB parts: %w", err)
|
||||
}
|
||||
|
||||
guestSVN, ok := tee["x-ms-sevsnpvm-guestsvn"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get guest SVN from claims")
|
||||
}
|
||||
|
||||
idKeyDigestString, ok := tee["x-ms-sevsnpvm-idkeydigest"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get idKeyDigest from claims")
|
||||
}
|
||||
idKeyDigest, err := hex.DecodeString(idKeyDigestString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode idKeyDigest: %w", err)
|
||||
}
|
||||
|
||||
reportIDString, ok := tee["x-ms-sevsnpvm-reportid"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get reportID from claims")
|
||||
}
|
||||
reportID, err := hex.DecodeString(reportIDString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode reportID: %w", err)
|
||||
}
|
||||
|
||||
sevProduct := quoteprovider.GetProductName(product)
|
||||
|
||||
return &attestation.Config{
|
||||
Config: &check.Config{
|
||||
RootOfTrust: &check.RootOfTrust{
|
||||
CheckCrl: true,
|
||||
},
|
||||
Policy: &check.Policy{
|
||||
ImageId: imageId,
|
||||
FamilyId: familyId,
|
||||
Measurement: measurement,
|
||||
MinimumGuestSvn: uint32(guestSVN),
|
||||
TrustedIdKeyHashes: [][]byte{idKeyDigest},
|
||||
ReportId: reportID,
|
||||
Product: &sevsnp.SevProduct{Name: sevProduct},
|
||||
Policy: policy,
|
||||
},
|
||||
},
|
||||
PcrConfig: &attestation.PcrConfig{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func FetchAzureAttestationToken(tokenNonce []byte, maaURL string) ([]byte, error) {
|
||||
token, err := maa.Attest(context.Background(), tokenNonce, maaURL, http.DefaultClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching azure token: %w", err)
|
||||
}
|
||||
return []byte(token), nil
|
||||
}
|
||||
|
||||
func validateToken(token string) (map[string]interface{}, error) {
|
||||
unverifiedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse token: %w", err)
|
||||
}
|
||||
|
||||
jku, jkuOk := unverifiedToken.Header["jku"].(string)
|
||||
if !jkuOk {
|
||||
return nil, fmt.Errorf("token is missing jku or kid in header")
|
||||
}
|
||||
|
||||
MaaUrlCerts := MaaURL
|
||||
if MaaURL == "" {
|
||||
MaaUrlCerts = jku
|
||||
}
|
||||
|
||||
keySet, err := maa.GetKeySet(context.Background(), MaaUrlCerts, http.DefaultClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get key set: %w", err)
|
||||
}
|
||||
|
||||
claims, err := maa.ValidateToken(token, keySet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate token: %w", err)
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package attestation
|
||||
|
||||
import cocosai "github.com/ultravioletrs/cocos"
|
||||
|
||||
var _ Provider = (*EmptyProvider)(nil)
|
||||
|
||||
type EmptyProvider struct{}
|
||||
|
||||
func (e *EmptyProvider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) AzureAttestationToken(nonce []byte) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) VerifTeeAttestation(report []byte, teeNonce []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmptyProvider) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
|
||||
return nil
|
||||
}
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
"io"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/gce-tcb-verifier/proto/endorsement"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,8 +77,8 @@ func GetLaunchEndorsement(ctx context.Context, measurement384 string) (*endorsem
|
||||
return &goldenUEFI, nil
|
||||
}
|
||||
|
||||
func GenerateAttestationPolicy(endorsement *endorsement.VMGoldenMeasurement, vcpuNum uint32) (*config.Config, error) {
|
||||
attestationPolicy := config.Config{PcrConfig: &config.PcrConfig{}, Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}}
|
||||
func GenerateAttestationPolicy(endorsement *endorsement.VMGoldenMeasurement, vcpuNum uint32) (*attestation.Config, error) {
|
||||
attestationPolicy := attestation.Config{PcrConfig: &attestation.PcrConfig{}, Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}}
|
||||
attestationPolicy.Config.Policy.Policy = endorsement.SevSnp.Policy
|
||||
attestationPolicy.Config.Policy.Measurement = endorsement.SevSnp.Measurements[vcpuNum]
|
||||
attestationPolicy.Config.RootOfTrust.DisallowNetwork = false
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// Provider is an autogenerated mock type for the Provider type
|
||||
type Provider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Provider_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Provider) EXPECT() *Provider_Expecter {
|
||||
return &Provider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Attestation provides a mock function with given fields: teeNonce, vTpmNonce
|
||||
func (_m *Provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
||||
ret := _m.Called(teeNonce, vTpmNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attestation")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]byte, []byte) ([]byte, error)); ok {
|
||||
return rf(teeNonce, vTpmNonce)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]byte, []byte) []byte); ok {
|
||||
r0 = rf(teeNonce, vTpmNonce)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]byte, []byte) error); ok {
|
||||
r1 = rf(teeNonce, vTpmNonce)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Provider_Attestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Attestation'
|
||||
type Provider_Attestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Attestation is a helper method to define mock.On call
|
||||
// - teeNonce []byte
|
||||
// - vTpmNonce []byte
|
||||
func (_e *Provider_Expecter) Attestation(teeNonce interface{}, vTpmNonce interface{}) *Provider_Attestation_Call {
|
||||
return &Provider_Attestation_Call{Call: _e.mock.On("Attestation", teeNonce, vTpmNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_Attestation_Call) Run(run func(teeNonce []byte, vTpmNonce []byte)) *Provider_Attestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte), args[1].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_Attestation_Call) Return(_a0 []byte, _a1 error) *Provider_Attestation_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_Attestation_Call) RunAndReturn(run func([]byte, []byte) ([]byte, error)) *Provider_Attestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AzureAttestationToken provides a mock function with given fields: tokenNonce
|
||||
func (_m *Provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
|
||||
ret := _m.Called(tokenNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AzureAttestationToken")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]byte) ([]byte, error)); ok {
|
||||
return rf(tokenNonce)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]byte) []byte); ok {
|
||||
r0 = rf(tokenNonce)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
||||
r1 = rf(tokenNonce)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Provider_AzureAttestationToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AzureAttestationToken'
|
||||
type Provider_AzureAttestationToken_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AzureAttestationToken is a helper method to define mock.On call
|
||||
// - tokenNonce []byte
|
||||
func (_e *Provider_Expecter) AzureAttestationToken(tokenNonce interface{}) *Provider_AzureAttestationToken_Call {
|
||||
return &Provider_AzureAttestationToken_Call{Call: _e.mock.On("AzureAttestationToken", tokenNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_AzureAttestationToken_Call) Run(run func(tokenNonce []byte)) *Provider_AzureAttestationToken_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_AzureAttestationToken_Call) Return(_a0 []byte, _a1 error) *Provider_AzureAttestationToken_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_AzureAttestationToken_Call) RunAndReturn(run func([]byte) ([]byte, error)) *Provider_AzureAttestationToken_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// TeeAttestation provides a mock function with given fields: teeNonce
|
||||
func (_m *Provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
ret := _m.Called(teeNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for TeeAttestation")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]byte) ([]byte, error)); ok {
|
||||
return rf(teeNonce)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]byte) []byte); ok {
|
||||
r0 = rf(teeNonce)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
||||
r1 = rf(teeNonce)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Provider_TeeAttestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TeeAttestation'
|
||||
type Provider_TeeAttestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// TeeAttestation is a helper method to define mock.On call
|
||||
// - teeNonce []byte
|
||||
func (_e *Provider_Expecter) TeeAttestation(teeNonce interface{}) *Provider_TeeAttestation_Call {
|
||||
return &Provider_TeeAttestation_Call{Call: _e.mock.On("TeeAttestation", teeNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_TeeAttestation_Call) Run(run func(teeNonce []byte)) *Provider_TeeAttestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_TeeAttestation_Call) Return(_a0 []byte, _a1 error) *Provider_TeeAttestation_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_TeeAttestation_Call) RunAndReturn(run func([]byte) ([]byte, error)) *Provider_TeeAttestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// VTpmAttestation provides a mock function with given fields: vTpmNonce
|
||||
func (_m *Provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
||||
ret := _m.Called(vTpmNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for VTpmAttestation")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]byte) ([]byte, error)); ok {
|
||||
return rf(vTpmNonce)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]byte) []byte); ok {
|
||||
r0 = rf(vTpmNonce)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
||||
r1 = rf(vTpmNonce)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Provider_VTpmAttestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VTpmAttestation'
|
||||
type Provider_VTpmAttestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// VTpmAttestation is a helper method to define mock.On call
|
||||
// - vTpmNonce []byte
|
||||
func (_e *Provider_Expecter) VTpmAttestation(vTpmNonce interface{}) *Provider_VTpmAttestation_Call {
|
||||
return &Provider_VTpmAttestation_Call{Call: _e.mock.On("VTpmAttestation", vTpmNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_VTpmAttestation_Call) Run(run func(vTpmNonce []byte)) *Provider_VTpmAttestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VTpmAttestation_Call) Return(_a0 []byte, _a1 error) *Provider_VTpmAttestation_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VTpmAttestation_Call) RunAndReturn(run func([]byte) ([]byte, error)) *Provider_VTpmAttestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// VerifTeeAttestation provides a mock function with given fields: report, teeNonce
|
||||
func (_m *Provider) VerifTeeAttestation(report []byte, teeNonce []byte) error {
|
||||
ret := _m.Called(report, teeNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for VerifTeeAttestation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]byte, []byte) error); ok {
|
||||
r0 = rf(report, teeNonce)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Provider_VerifTeeAttestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifTeeAttestation'
|
||||
type Provider_VerifTeeAttestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// VerifTeeAttestation is a helper method to define mock.On call
|
||||
// - report []byte
|
||||
// - teeNonce []byte
|
||||
func (_e *Provider_Expecter) VerifTeeAttestation(report interface{}, teeNonce interface{}) *Provider_VerifTeeAttestation_Call {
|
||||
return &Provider_VerifTeeAttestation_Call{Call: _e.mock.On("VerifTeeAttestation", report, teeNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifTeeAttestation_Call) Run(run func(report []byte, teeNonce []byte)) *Provider_VerifTeeAttestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte), args[1].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifTeeAttestation_Call) Return(_a0 error) *Provider_VerifTeeAttestation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifTeeAttestation_Call) RunAndReturn(run func([]byte, []byte) error) *Provider_VerifTeeAttestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// VerifVTpmAttestation provides a mock function with given fields: report, vTpmNonce
|
||||
func (_m *Provider) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
|
||||
ret := _m.Called(report, vTpmNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for VerifVTpmAttestation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]byte, []byte) error); ok {
|
||||
r0 = rf(report, vTpmNonce)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Provider_VerifVTpmAttestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifVTpmAttestation'
|
||||
type Provider_VerifVTpmAttestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// VerifVTpmAttestation is a helper method to define mock.On call
|
||||
// - report []byte
|
||||
// - vTpmNonce []byte
|
||||
func (_e *Provider_Expecter) VerifVTpmAttestation(report interface{}, vTpmNonce interface{}) *Provider_VerifVTpmAttestation_Call {
|
||||
return &Provider_VerifVTpmAttestation_Call{Call: _e.mock.On("VerifVTpmAttestation", report, vTpmNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifVTpmAttestation_Call) Run(run func(report []byte, vTpmNonce []byte)) *Provider_VerifVTpmAttestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte), args[1].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifVTpmAttestation_Call) Return(_a0 error) *Provider_VerifVTpmAttestation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifVTpmAttestation_Call) RunAndReturn(run func([]byte, []byte) error) *Provider_VerifVTpmAttestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// VerifyAttestation provides a mock function with given fields: report, teeNonce, vTpmNonce
|
||||
func (_m *Provider) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
|
||||
ret := _m.Called(report, teeNonce, vTpmNonce)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for VerifyAttestation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]byte, []byte, []byte) error); ok {
|
||||
r0 = rf(report, teeNonce, vTpmNonce)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Provider_VerifyAttestation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifyAttestation'
|
||||
type Provider_VerifyAttestation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// VerifyAttestation is a helper method to define mock.On call
|
||||
// - report []byte
|
||||
// - teeNonce []byte
|
||||
// - vTpmNonce []byte
|
||||
func (_e *Provider_Expecter) VerifyAttestation(report interface{}, teeNonce interface{}, vTpmNonce interface{}) *Provider_VerifyAttestation_Call {
|
||||
return &Provider_VerifyAttestation_Call{Call: _e.mock.On("VerifyAttestation", report, teeNonce, vTpmNonce)}
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifyAttestation_Call) Run(run func(report []byte, teeNonce []byte, vTpmNonce []byte)) *Provider_VerifyAttestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte), args[1].([]byte), args[2].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifyAttestation_Call) Return(_a0 error) *Provider_VerifyAttestation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_VerifyAttestation_Call) RunAndReturn(run func([]byte, []byte, []byte) error) *Provider_VerifyAttestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewProvider creates a new instance of Provider. 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 NewProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Provider {
|
||||
mock := &Provider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build embed
|
||||
// +build embed
|
||||
|
||||
package quoteprovider
|
||||
|
||||
import (
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
pb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
cocosai "github.com/ultravioletrs/cocos"
|
||||
)
|
||||
|
||||
const Nonce = 64
|
||||
|
||||
var _ client.LeveledQuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
|
||||
type embeddedQuoteProvider struct {
|
||||
}
|
||||
|
||||
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
return &embeddedQuoteProvider{}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsSupported returns true if the SEV platform is supported.
|
||||
func (e *embeddedQuoteProvider) IsSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Product returns the SEV product information.
|
||||
// unimplemented since it is deprecated and not used.
|
||||
func (e *embeddedQuoteProvider) Product() *pb.SevProduct {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestation *sevsnp.Attestation, reportData []byte) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
sevsnp "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// LeveledQuoteProvider is an autogenerated mock type for the LeveledQuoteProvider type
|
||||
type LeveledQuoteProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type LeveledQuoteProvider_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *LeveledQuoteProvider) EXPECT() *LeveledQuoteProvider_Expecter {
|
||||
return &LeveledQuoteProvider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// 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 GetRawQuoteAtLevel")
|
||||
}
|
||||
|
||||
var r0 []uint8
|
||||
var r1 error
|
||||
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, 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, uint) error); ok {
|
||||
r1 = rf(reportData, vmpl)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// GetRawQuoteAtLevel is a helper method to define mock.On call
|
||||
// - reportData [64]byte
|
||||
// - 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 *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), args[1].(uint))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Return(_a0 []uint8, _a1 error) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
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 no fields
|
||||
func (_m *LeveledQuoteProvider) IsSupported() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsSupported")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// 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 *LeveledQuoteProvider_Expecter) IsSupported() *LeveledQuoteProvider_IsSupported_Call {
|
||||
return &LeveledQuoteProvider_IsSupported_Call{Call: _e.mock.On("IsSupported")}
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Run(run func()) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Return(_a0 bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) RunAndReturn(run func() bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Product provides a mock function with no fields
|
||||
func (_m *LeveledQuoteProvider) Product() *sevsnp.SevProduct {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Product")
|
||||
}
|
||||
|
||||
var r0 *sevsnp.SevProduct
|
||||
if rf, ok := ret.Get(0).(func() *sevsnp.SevProduct); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*sevsnp.SevProduct)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// 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 *LeveledQuoteProvider_Expecter) Product() *LeveledQuoteProvider_Product_Call {
|
||||
return &LeveledQuoteProvider_Product_Call{Call: _e.mock.On("Product")}
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Run(run func()) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Return(_a0 *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) RunAndReturn(run func() *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// 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 NewLeveledQuoteProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *LeveledQuoteProvider {
|
||||
mock := &LeveledQuoteProvider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -21,18 +21,16 @@ 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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
cocosDirectory = ".cocos"
|
||||
caBundleName = "ask_ark.pem"
|
||||
attestationReportSize = 0x4A0
|
||||
Nonce = 64
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
sevVMPL = 2
|
||||
cocosDirectory = ".cocos"
|
||||
caBundleName = "ask_ark.pem"
|
||||
Nonce = 64
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -87,15 +85,7 @@ func verifyReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
|
||||
}
|
||||
|
||||
if cfg.Policy.Product == nil {
|
||||
productName := sevsnp.SevProduct_SEV_PRODUCT_UNKNOWN
|
||||
switch cfg.RootOfTrust.ProductLine {
|
||||
case sevProductNameMilan:
|
||||
productName = sevsnp.SevProduct_SEV_PRODUCT_MILAN
|
||||
case sevProductNameGenoa:
|
||||
productName = sevsnp.SevProduct_SEV_PRODUCT_GENOA
|
||||
default:
|
||||
}
|
||||
|
||||
productName := GetProductName(cfg.RootOfTrust.ProductLine)
|
||||
if productName == sevsnp.SevProduct_SEV_PRODUCT_UNKNOWN {
|
||||
return errProductLine
|
||||
}
|
||||
@@ -127,7 +117,7 @@ func verifyReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
|
||||
func validateReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
|
||||
opts, err := validate.PolicyToOptions(cfg.Policy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get policy for validation %v", errors.Wrap(errAttVerification, err))
|
||||
return fmt.Errorf("failed to get policy for validation: %v", errors.Wrap(errAttVerification, err))
|
||||
}
|
||||
|
||||
if err = validate.SnpAttestation(attestationPB, opts); err != nil {
|
||||
@@ -142,7 +132,7 @@ func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []byte) error {
|
||||
config, err := copyConfig(config.AttestationPolicy.Config)
|
||||
config, err := copyConfig(attestation.AttestationPolicy.Config)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create a copy of attestation policy"), err)
|
||||
}
|
||||
@@ -150,7 +140,11 @@ func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []
|
||||
// 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[:]
|
||||
|
||||
if len(reportData) != 0 {
|
||||
config.Policy.ReportData = reportData[:]
|
||||
}
|
||||
|
||||
return VerifyAndValidate(attestationPB, config)
|
||||
}
|
||||
|
||||
@@ -168,7 +162,7 @@ func VerifyAndValidate(attestationPB *sevsnp.Attestation, cfg *check.Config) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
func FetchAttestation(reportDataSlice []byte, vmpl uint) ([]byte, error) {
|
||||
var reportData [Nonce]byte
|
||||
|
||||
qp, err := GetLeveledQuoteProvider()
|
||||
@@ -181,10 +175,21 @@ func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
}
|
||||
copy(reportData[:], reportDataSlice)
|
||||
|
||||
rawQuote, err := qp.GetRawQuoteAtLevel(reportData, sevVMPL)
|
||||
rawQuote, err := qp.GetRawQuoteAtLevel(reportData, vmpl)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to get raw quote")
|
||||
}
|
||||
|
||||
return rawQuote, nil
|
||||
}
|
||||
|
||||
func GetProductName(product string) sevsnp.SevProduct_SevProductName {
|
||||
switch product {
|
||||
case sevProductNameMilan:
|
||||
return sevsnp.SevProduct_SEV_PRODUCT_MILAN
|
||||
case sevProductNameGenoa:
|
||||
return sevsnp.SevProduct_SEV_PRODUCT_GENOA
|
||||
default:
|
||||
return sevsnp.SevProduct_SEV_PRODUCT_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,8 @@ func prepVerifyAttReport(t *testing.T) (*sevsnp.Attestation, []byte) {
|
||||
file, err := os.ReadFile("../../../attestation.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(file) < attestationReportSize {
|
||||
file = append(file, make([]byte, attestationReportSize-len(file))...)
|
||||
if len(file) < abi.ReportSize {
|
||||
file = append(file, make([]byte, abi.ReportSize-len(file))...)
|
||||
}
|
||||
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
|
||||
+156
-89
@@ -8,7 +8,6 @@ import (
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -17,47 +16,52 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/proto/tpm"
|
||||
ptpm "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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var _ attestation.Provider = (*provider)(nil)
|
||||
|
||||
const (
|
||||
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
|
||||
Nonce = 32
|
||||
PCR15 = 15
|
||||
Hash1 = 20
|
||||
Hash256 = 32
|
||||
Hash384 = 48
|
||||
)
|
||||
|
||||
var (
|
||||
ExternalTPM io.ReadWriteCloser
|
||||
ErrNoHashAlgo = errors.New("hash algo is not supported")
|
||||
ExternalTPM io.ReadWriteCloser
|
||||
ErrNoHashAlgo = errors.New("hash algo is not supported")
|
||||
ErrFetchQuote = errors.New("failed to fetch vTPM quote")
|
||||
ErrFetchAzureToken = errors.New("failed to fetch Azure token")
|
||||
)
|
||||
|
||||
type VtpmAttest func(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error)
|
||||
|
||||
type tpmWrapper struct {
|
||||
type tpm struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (et tpmWrapper) EventLog() ([]byte, error) {
|
||||
func (et tpm) EventLog() ([]byte, error) {
|
||||
return os.ReadFile(eventLog)
|
||||
}
|
||||
|
||||
func OpenTpm() (io.ReadWriteCloser, error) {
|
||||
if ExternalTPM != nil {
|
||||
return tpmWrapper{ExternalTPM}, nil
|
||||
return tpm{ExternalTPM}, nil
|
||||
}
|
||||
|
||||
tw := tpmWrapper{}
|
||||
tw := tpm{}
|
||||
var err error
|
||||
|
||||
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpmrm0")
|
||||
@@ -88,14 +92,69 @@ func ExtendPCR(pcrIndex int, value []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
type provider struct {
|
||||
pubKey []byte
|
||||
teeAttestaion bool
|
||||
vmpl uint
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func New(pubKey []byte, teeAttestation bool, vmpl uint, writer io.Writer) attestation.Provider {
|
||||
return &provider{
|
||||
pubKey: pubKey,
|
||||
teeAttestaion: teeAttestation,
|
||||
vmpl: vmpl,
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (v provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
||||
return Attest(teeNonce, vTpmNonce, v.teeAttestaion, v.vmpl)
|
||||
}
|
||||
|
||||
func (v provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
return quoteprovider.FetchAttestation(teeNonce, v.vmpl)
|
||||
}
|
||||
|
||||
func (v provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
||||
quote, err := FetchQuote(vTpmNonce)
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(ErrFetchQuote, err)
|
||||
}
|
||||
|
||||
return proto.Marshal(quote)
|
||||
}
|
||||
|
||||
func (v provider) VerifTeeAttestation(report []byte, teeNonce []byte) error {
|
||||
attestReport, err := abi.ReportToProto(report)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
}
|
||||
|
||||
attestationReport := sevsnp.Attestation{Report: attestReport, CertificateChain: nil}
|
||||
return quoteprovider.VerifyAttestationReportTLS(&attestationReport, teeNonce)
|
||||
}
|
||||
|
||||
func (v provider) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
|
||||
return VerifyQuote(report, v.pubKey, vTpmNonce, v.writer)
|
||||
}
|
||||
|
||||
func (v provider) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
|
||||
return VTPMVerify(report, v.pubKey, teeNonce, vTpmNonce, v.writer)
|
||||
}
|
||||
|
||||
func (v provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
|
||||
return nil, errors.New("Azure attestation token is not supported")
|
||||
}
|
||||
|
||||
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool, vmpl uint) ([]byte, error) {
|
||||
attestation, err := FetchQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
if teeAttestaion {
|
||||
attestation, err = addTEEAttestation(attestation, teeNonce)
|
||||
err = addTEEAttestation(attestation, teeNonce, vmpl)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
@@ -104,26 +163,26 @@ func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, erro
|
||||
return marshalQuote(attestation)
|
||||
}
|
||||
|
||||
func FetchATLSQuote(pubKey, teeNonce, vTPMNonce []byte) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte, writer io.Writer) error {
|
||||
if err := VerifyQuote(quote, pubKeyTLS, vtpmNonce, writer); err != nil {
|
||||
return fmt.Errorf("failed to verify vTPM quote: %v", err)
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKey, attestation.GetAkPub(), teeNonce)
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
err := proto.Unmarshal(quote, attestation)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
|
||||
}
|
||||
|
||||
attestation, err = addTEEAttestation(attestation, reportData)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), teeNonce); err != nil {
|
||||
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
return marshalQuote(attestation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte) error {
|
||||
func VerifyQuote(quote []byte, pubKeyTLS []byte, vtpmNonce []byte, writer io.Writer) error {
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
err := proto.Unmarshal(quote, attestation)
|
||||
@@ -142,16 +201,7 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
|
||||
return err
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKeyTLS, ak, teeNonce)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create TEE attestation report nonce"), 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}})
|
||||
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to verify attestation"), err)
|
||||
}
|
||||
@@ -162,44 +212,22 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
|
||||
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
|
||||
}
|
||||
|
||||
if writer != nil {
|
||||
marshalOptions := prototext.MarshalOptions{Multiline: true, EmitASCII: true}
|
||||
|
||||
out, err := marshalOptions.Marshal(ms)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := writer.Write(out); err != nil {
|
||||
return fmt.Errorf("failed to write verified attestation report: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyAttest is a dummy attestation function that returns an empty attestation report.
|
||||
func EmptyAttest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
|
||||
return []byte{}, 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 {
|
||||
@@ -209,7 +237,7 @@ func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
func FetchQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -240,46 +268,54 @@ func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.Attestation, error) {
|
||||
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce)
|
||||
func addTEEAttestation(attestation *attest.Attestation, nonce []byte, vmpl uint) error {
|
||||
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce, vmpl)
|
||||
if err != nil {
|
||||
return attestation, fmt.Errorf("failed to fetch TEE attestation report: %v", err)
|
||||
return fmt.Errorf("failed to fetch TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
|
||||
if err != nil {
|
||||
return attestation, errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
}
|
||||
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
||||
SevSnpAttestation: extReport,
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []byte) error {
|
||||
quotes := attestation.GetQuotes()
|
||||
func checkExpectedPCRValues(attQuote *attest.Attestation, ePcr256, ePcr384 []byte) error {
|
||||
quotes := attQuote.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
|
||||
case tpm.HashAlgo_SHA1:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha1
|
||||
case ptpm.HashAlgo_SHA256:
|
||||
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha256
|
||||
if ePcr256 == nil {
|
||||
pcr15 = make([]byte, 32)
|
||||
} else {
|
||||
pcr15 = ePcr256
|
||||
}
|
||||
case ptpm.HashAlgo_SHA384:
|
||||
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha384
|
||||
if ePcr384 == nil {
|
||||
pcr15 = make([]byte, 48)
|
||||
} else {
|
||||
pcr15 = ePcr384
|
||||
}
|
||||
case ptpm.HashAlgo_SHA1:
|
||||
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha1
|
||||
pcr15 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
default:
|
||||
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
|
||||
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", ptpm.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]))
|
||||
return fmt.Errorf("for algo %s PCR[15] expected %s but found %s", ptpm.HashAlgo_name[int32(quote.Pcrs.Hash)], hex.EncodeToString(pcr15), hex.EncodeToString(quote.Pcrs.Pcrs[pcr15Index]))
|
||||
}
|
||||
|
||||
for i, v := range pcrMap {
|
||||
@@ -292,7 +328,7 @@ func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []
|
||||
return errors.Wrap(fmt.Errorf("error converting PCR value to byte"), 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 fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", ptpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,6 +337,10 @@ func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []
|
||||
|
||||
// Return SHA256 and SHA384 values of the input public key.
|
||||
func calculatePCRTLSKey(pubKey []byte) ([]byte, []byte) {
|
||||
if len(pubKey) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
init256 := make([]byte, Hash256)
|
||||
init384 := make([]byte, Hash384)
|
||||
|
||||
@@ -315,3 +355,30 @@ func calculatePCRTLSKey(pubKey []byte) ([]byte, []byte) {
|
||||
|
||||
return newPcr256[:], newPcr384[:]
|
||||
}
|
||||
|
||||
func getPCRValue(index int, algorithm tpm2.Algorithm) ([]byte, error) {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rwc.Close()
|
||||
|
||||
pcrValue, err := tpm2.ReadPCR(rwc, index, algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pcrValue, nil
|
||||
}
|
||||
|
||||
func GetPCRSHA1Value(index int) ([]byte, error) {
|
||||
return getPCRValue(index, tpm2.AlgSHA1)
|
||||
}
|
||||
|
||||
func GetPCRSHA256Value(index int) ([]byte, error) {
|
||||
return getPCRValue(index, tpm2.AlgSHA256)
|
||||
}
|
||||
|
||||
func GetPCRSHA384Value(index int) ([]byte, error) {
|
||||
return getPCRValue(index, tpm2.AlgSHA384)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func NewAgentClient(ctx context.Context, cfg grpc.AgentClientConfig) (grpc.Clien
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if client.Secure() != grpc.WithATLS && client.Secure() != grpc.WithTLS {
|
||||
if client.Secure() != grpc.WithMATLS && client.Secure() != grpc.WithATLS && client.Secure() != grpc.WithTLS {
|
||||
health := grpchealth.NewHealthClient(client.Connection())
|
||||
resp, err := health.Check(ctx, &grpchealth.HealthCheckRequest{
|
||||
Service: "agent",
|
||||
|
||||
@@ -15,7 +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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
@@ -113,7 +113,7 @@ func TestAgentClientIntegration(t *testing.T) {
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
err: config.ErrAttestationPolicyMissing,
|
||||
err: attestation.ErrAttestationPolicyMissing,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,15 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
|
||||
err := config.ReadAttestationPolicy(cfg.AttestationPolicy, &config.AttestationPolicy)
|
||||
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, security, error) {
|
||||
security := withaTLS
|
||||
err := attestation.ReadAttestationPolicy(cfg.AttestationPolicy, &attestation.AttestationPolicy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
|
||||
return nil, withoutTLS, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
|
||||
}
|
||||
|
||||
var insecureSkipVerify bool = true
|
||||
@@ -37,23 +38,25 @@ func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error)
|
||||
// Read the certificate file
|
||||
certPEM, err := os.ReadFile(cfg.ServerCAFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to read certificate file"), err)
|
||||
return nil, withoutTLS, errors.Wrap(fmt.Errorf("failed to read certificate file"), err)
|
||||
}
|
||||
|
||||
// Decode the PEM block
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
return nil, withoutTLS, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
|
||||
// Parse the certificate
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to parse certificate"), err)
|
||||
return nil, withoutTLS, errors.Wrap(fmt.Errorf("failed to parse certificate"), err)
|
||||
}
|
||||
|
||||
rootCAs = x509.NewCertPool()
|
||||
rootCAs.AddCert(cert)
|
||||
|
||||
security = withmaTLS
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
@@ -63,7 +66,16 @@ func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error)
|
||||
return verifyPeerCertificateATLS(rawCerts, verifiedChains, cfg)
|
||||
},
|
||||
}
|
||||
return credentials.NewTLS(tlsConfig), nil
|
||||
|
||||
if cfg.ClientCert != "" || cfg.ClientKey != "" {
|
||||
certificate, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
|
||||
if err != nil {
|
||||
return nil, withoutTLS, errors.Wrap(errFailedToLoadClientCertKey, err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{certificate}
|
||||
}
|
||||
|
||||
return credentials.NewTLS(tlsConfig), security, nil
|
||||
}
|
||||
|
||||
func CustomDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
|
||||
@@ -19,7 +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"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
@@ -188,7 +188,12 @@ func TestClientSecure(t *testing.T) {
|
||||
{
|
||||
name: "With aTLS",
|
||||
secure: withaTLS,
|
||||
expected: WithATLS,
|
||||
expected: "with aTLS",
|
||||
},
|
||||
{
|
||||
name: "With maTLS",
|
||||
secure: withmaTLS,
|
||||
expected: WithMATLS,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -221,25 +226,25 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
name: "Invalid JSON",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSON,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
err: attestation.ErrAttestationPolicyDecode,
|
||||
},
|
||||
{
|
||||
name: "Non-existent file",
|
||||
manifestPath: "nonexistent.json",
|
||||
fileContent: "",
|
||||
err: att.ErrAttestationPolicyOpen,
|
||||
err: attestation.ErrAttestationPolicyOpen,
|
||||
},
|
||||
{
|
||||
name: "Empty manifest path",
|
||||
manifestPath: "",
|
||||
fileContent: "",
|
||||
err: att.ErrAttestationPolicyMissing,
|
||||
err: attestation.ErrAttestationPolicyMissing,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON PCR",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSONPCR,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
err: attestation.ErrAttestationPolicyDecode,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -251,8 +256,8 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
defer os.Remove(tt.manifestPath)
|
||||
}
|
||||
|
||||
config := att.Config{Config: &check.Config{}, PcrConfig: &att.PcrConfig{}}
|
||||
err := att.ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
config := attestation.Config{Config: &check.Config{}, PcrConfig: &attestation.PcrConfig{}}
|
||||
err := attestation.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 {
|
||||
|
||||
+16
-10
@@ -24,10 +24,12 @@ const (
|
||||
withTLS
|
||||
withmTLS
|
||||
withaTLS
|
||||
withmaTLS
|
||||
)
|
||||
|
||||
const (
|
||||
AttestationReportSize = 0x4A0
|
||||
WithMATLS = "with maTLS"
|
||||
WithATLS = "with aTLS"
|
||||
WithTLS = "with TLS"
|
||||
)
|
||||
@@ -36,7 +38,7 @@ var (
|
||||
errGrpcConnect = errors.New("failed to connect to grpc server")
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
errCertificateParse = errors.New("failed to parse x509 certificate")
|
||||
errAttVerification = errors.New("certificat is not sefl signed")
|
||||
errAttVerification = errors.New("certificat is not self signed")
|
||||
errFailedToLoadClientCertKey = errors.New("failed to load client certificate and key")
|
||||
errFailedToLoadRootCA = errors.New("failed to load root ca file")
|
||||
)
|
||||
@@ -57,6 +59,7 @@ type AgentClientConfig struct {
|
||||
BaseConfig
|
||||
AttestationPolicy string `env:"ATTESTATION_POLICY" envDefault:""`
|
||||
AttestedTLS bool `env:"ATTESTED_TLS" envDefault:"false"`
|
||||
ProductName string `env:"PRODUCT_NAME" envDefault:"Milan"`
|
||||
}
|
||||
|
||||
type ManagerClientConfig struct {
|
||||
@@ -120,7 +123,9 @@ func (c *client) Secure() string {
|
||||
case withmTLS:
|
||||
return "with mTLS"
|
||||
case withaTLS:
|
||||
return WithATLS
|
||||
return "with aTLS"
|
||||
case withmaTLS:
|
||||
return WithMATLS
|
||||
default:
|
||||
return "without TLS"
|
||||
}
|
||||
@@ -137,17 +142,18 @@ func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
|
||||
secure := withoutTLS
|
||||
|
||||
if agcfg, ok := cfg.(AgentClientConfig); ok && agcfg.AttestedTLS {
|
||||
tc, err := setupATLS(agcfg)
|
||||
tc, sec, err := setupATLS(agcfg)
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(tc))
|
||||
opts = append(opts, grpc.WithContextDialer(CustomDialer))
|
||||
secure = withaTLS
|
||||
|
||||
secure = sec
|
||||
} else {
|
||||
conf := cfg.GetBaseConfig()
|
||||
transportCreds, err, sec := loadTLSConfig(conf.ServerCAFile, conf.ClientCert, conf.ClientKey)
|
||||
transportCreds, sec, err := loadTLSConfig(conf.ServerCAFile, conf.ClientCert, conf.ClientKey)
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
@@ -162,7 +168,7 @@ func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
|
||||
return conn, secure, nil
|
||||
}
|
||||
|
||||
func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.TransportCredentials, error, security) {
|
||||
func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.TransportCredentials, security, error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
secure := withoutTLS
|
||||
tc := insecure.NewCredentials()
|
||||
@@ -170,12 +176,12 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
if serverCAFile != "" {
|
||||
rootCA, err := os.ReadFile(serverCAFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errFailedToLoadRootCA, err), secure
|
||||
return nil, secure, errors.Wrap(errFailedToLoadRootCA, err)
|
||||
}
|
||||
if len(rootCA) > 0 {
|
||||
capool := x509.NewCertPool()
|
||||
if !capool.AppendCertsFromPEM(rootCA) {
|
||||
return nil, fmt.Errorf("failed to append root ca to tls.Config"), secure
|
||||
return nil, secure, fmt.Errorf("failed to append root ca to tls.Config")
|
||||
}
|
||||
tlsConfig.RootCAs = capool
|
||||
secure = withTLS
|
||||
@@ -186,12 +192,12 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
if clientCert != "" || clientKey != "" {
|
||||
certificate, err := tls.LoadX509KeyPair(clientCert, clientKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errFailedToLoadClientCertKey, err), secure
|
||||
return nil, secure, errors.Wrap(errFailedToLoadClientCertKey, err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{certificate}
|
||||
secure = withmTLS
|
||||
tc = credentials.NewTLS(tlsConfig)
|
||||
}
|
||||
|
||||
return tc, nil, secure
|
||||
return tc, secure, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
@@ -338,6 +339,22 @@ func (p *ProgressBar) ReceiveResult(description string, totalSize int, stream ag
|
||||
}, resultFile)
|
||||
}
|
||||
|
||||
func (p *ProgressBar) ReceiveIMAMeasurements(description string, totalSize int, stream agent.AgentService_IMAMeasurementsClient, resultFile *os.File) ([]byte, error) {
|
||||
pcr10 := make([]byte, vtpm.Hash1)
|
||||
err := p.receiveStream(description, totalSize, func() ([]byte, error) {
|
||||
response, err := stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copy(pcr10, response.Pcr10[:20])
|
||||
|
||||
return response.File, nil
|
||||
}, resultFile)
|
||||
|
||||
return pcr10, err
|
||||
}
|
||||
|
||||
func (p *ProgressBar) ReceiveAttestation(description string, totalSize int, stream agent.AgentService_AttestationClient, attestationFile *os.File) error {
|
||||
return p.receiveStream(description, totalSize, func() ([]byte, error) {
|
||||
response, err := stream.Recv()
|
||||
|
||||
+57
-6
@@ -27,15 +27,18 @@ type SDK interface {
|
||||
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, nonce [size32]byte, attType int, attestationFile *os.File) error
|
||||
IMAMeasurements(ctx context.Context, resultFile *os.File) ([]byte, error)
|
||||
AttestationResult(ctx context.Context, nonce [size32]byte, attType int, attestationFile *os.File) error
|
||||
}
|
||||
|
||||
const (
|
||||
size64 = 64
|
||||
size32 = 32
|
||||
algoProgressBarDescription = "Uploading algorithm"
|
||||
dataProgressBarDescription = "Uploading data"
|
||||
resultProgressDescription = "Downloading result"
|
||||
attestationProgressDescription = "Downloading attestation"
|
||||
size64 = 64
|
||||
size32 = 32
|
||||
algoProgressBarDescription = "Uploading algorithm"
|
||||
dataProgressBarDescription = "Uploading data"
|
||||
resultProgressDescription = "Downloading result"
|
||||
attestationProgressDescription = "Downloading attestation"
|
||||
imaMeasurementsProgressDescription = "Downloading Linux IMA measurements"
|
||||
)
|
||||
|
||||
type agentSDK struct {
|
||||
@@ -154,6 +157,25 @@ func (sdk *agentSDK) Attestation(ctx context.Context, reportData [size64]byte, n
|
||||
return pb.ReceiveAttestation(attestationProgressDescription, fileSize, stream, attestationFile)
|
||||
}
|
||||
|
||||
func (sdk *agentSDK) AttestationResult(ctx context.Context, nonce [size32]byte, attType int, attestationResultFile *os.File) error {
|
||||
request := &agent.AttestationResultRequest{
|
||||
TokenNonce: nonce[:],
|
||||
Type: int32(attType),
|
||||
}
|
||||
|
||||
result, err := sdk.client.AttestationResult(ctx, request)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.New("failed to fetch attestation token"), err)
|
||||
}
|
||||
|
||||
_, err = attestationResultFile.Write(result.GetFile())
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.New("failed to write attestation result to file"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func signData(userID string, privKey crypto.Signer) ([]byte, error) {
|
||||
var signature []byte
|
||||
var err error
|
||||
@@ -186,3 +208,32 @@ func generateMetadata(userID string, privateKey crypto.PrivateKey) (metadata.MD,
|
||||
kv[auth.SignatureMetadataKey] = base64.StdEncoding.EncodeToString(signature)
|
||||
return metadata.New(kv), nil
|
||||
}
|
||||
|
||||
func (sdk *agentSDK) IMAMeasurements(ctx context.Context, resultFile *os.File) ([]byte, error) {
|
||||
request := &agent.IMAMeasurementsRequest{}
|
||||
|
||||
stream, err := sdk.client.IMAMeasurements(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
incomingmd, err := stream.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileSizeStr := incomingmd.Get(grpc.FileSizeKey)
|
||||
|
||||
if len(fileSizeStr) == 0 {
|
||||
fileSizeStr = append(fileSizeStr, "0")
|
||||
}
|
||||
|
||||
fileSize, err := strconv.Atoi(fileSizeStr[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pb := progressbar.New(true)
|
||||
|
||||
return pb.ReceiveIMAMeasurements(imaMeasurementsProgressDescription, fileSize, stream, resultFile)
|
||||
}
|
||||
|
||||
@@ -475,6 +475,89 @@ func TestAttestation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttestationResult(t *testing.T) {
|
||||
reportData := make([]byte, 64)
|
||||
nonce := make([]byte, 64)
|
||||
report := []byte{
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(bufDialer))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dial bufnet: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := agent.NewAgentServiceClient(conn)
|
||||
|
||||
sdk := sdk.NewAgentSDK(client)
|
||||
|
||||
_, err = rand.Read(reportData)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
nonce [vtpm.Nonce]byte
|
||||
response *agent.AttestationResultResponse
|
||||
svcRes []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "fetch attestation report successfully",
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResultResponse{
|
||||
File: report,
|
||||
},
|
||||
svcRes: report,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "failed to fetch attestation report",
|
||||
nonce: [vtpm.Nonce]byte(nonce),
|
||||
response: &agent.AttestationResultResponse{
|
||||
File: []byte{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svcCall := svc.On("AttestationResult", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.svcRes, tc.err)
|
||||
|
||||
file, err := os.CreateTemp("", "attestation")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
os.Remove(file.Name())
|
||||
})
|
||||
|
||||
err = sdk.AttestationResult(context.Background(), tc.nonce, 0, file)
|
||||
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
t.Fatalf("Expected gRPC status error, but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.err != nil {
|
||||
if st.Message() != tc.err.Error() {
|
||||
t.Errorf("%s: Expected error message %q, but got %q", tc.name, tc.err.Error(), st.Message())
|
||||
}
|
||||
}
|
||||
|
||||
res, err := os.ReadFile(file.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.response.File, res, tc.name)
|
||||
|
||||
svcCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateKeys(t *testing.T, keyType string) (priv any, pub []byte) {
|
||||
switch keyType {
|
||||
case "ecdsa":
|
||||
|
||||
@@ -124,6 +124,55 @@ func (_c *SDK_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte,
|
||||
return _c
|
||||
}
|
||||
|
||||
// AttestationResult provides a mock function with given fields: ctx, nonce, attType, attestationFile
|
||||
func (_m *SDK) AttestationResult(ctx context.Context, nonce [32]byte, attType int, attestationFile *os.File) error {
|
||||
ret := _m.Called(ctx, nonce, attType, attestationFile)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AttestationResult")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [32]byte, int, *os.File) error); ok {
|
||||
r0 = rf(ctx, nonce, attType, attestationFile)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SDK_AttestationResult_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AttestationResult'
|
||||
type SDK_AttestationResult_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AttestationResult is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - nonce [32]byte
|
||||
// - attType int
|
||||
// - attestationFile *os.File
|
||||
func (_e *SDK_Expecter) AttestationResult(ctx interface{}, nonce interface{}, attType interface{}, attestationFile interface{}) *SDK_AttestationResult_Call {
|
||||
return &SDK_AttestationResult_Call{Call: _e.mock.On("AttestationResult", ctx, nonce, attType, attestationFile)}
|
||||
}
|
||||
|
||||
func (_c *SDK_AttestationResult_Call) Run(run func(ctx context.Context, nonce [32]byte, attType int, attestationFile *os.File)) *SDK_AttestationResult_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([32]byte), args[2].(int), args[3].(*os.File))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SDK_AttestationResult_Call) Return(_a0 error) *SDK_AttestationResult_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SDK_AttestationResult_Call) RunAndReturn(run func(context.Context, [32]byte, int, *os.File) error) *SDK_AttestationResult_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Data provides a mock function with given fields: ctx, dataset, filename, privKey
|
||||
func (_m *SDK) Data(ctx context.Context, dataset *os.File, filename string, privKey interface{}) error {
|
||||
ret := _m.Called(ctx, dataset, filename, privKey)
|
||||
@@ -173,6 +222,65 @@ func (_c *SDK_Data_Call) RunAndReturn(run func(context.Context, *os.File, string
|
||||
return _c
|
||||
}
|
||||
|
||||
// IMAMeasurements provides a mock function with given fields: ctx, resultFile
|
||||
func (_m *SDK) IMAMeasurements(ctx context.Context, resultFile *os.File) ([]byte, error) {
|
||||
ret := _m.Called(ctx, resultFile)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IMAMeasurements")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *os.File) ([]byte, error)); ok {
|
||||
return rf(ctx, resultFile)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *os.File) []byte); ok {
|
||||
r0 = rf(ctx, resultFile)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *os.File) error); ok {
|
||||
r1 = rf(ctx, resultFile)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SDK_IMAMeasurements_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IMAMeasurements'
|
||||
type SDK_IMAMeasurements_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IMAMeasurements is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - resultFile *os.File
|
||||
func (_e *SDK_Expecter) IMAMeasurements(ctx interface{}, resultFile interface{}) *SDK_IMAMeasurements_Call {
|
||||
return &SDK_IMAMeasurements_Call{Call: _e.mock.On("IMAMeasurements", ctx, resultFile)}
|
||||
}
|
||||
|
||||
func (_c *SDK_IMAMeasurements_Call) Run(run func(ctx context.Context, resultFile *os.File)) *SDK_IMAMeasurements_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*os.File))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SDK_IMAMeasurements_Call) Return(_a0 []byte, _a1 error) *SDK_IMAMeasurements_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SDK_IMAMeasurements_Call) RunAndReturn(run func(context.Context, *os.File) ([]byte, error)) *SDK_IMAMeasurements_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Result provides a mock function with given fields: ctx, privKey, resultFile
|
||||
func (_m *SDK) Result(ctx context.Context, privKey interface{}, resultFile *os.File) error {
|
||||
ret := _m.Called(ctx, privKey, resultFile)
|
||||
|
||||
+16
-6
@@ -15,10 +15,20 @@ The service is configured using the environment variables from the following tab
|
||||
|
||||
## Running
|
||||
```shell
|
||||
go run main.go <algo_path> <public_key_path> <attested_tls_bool> <dataset(s)_path>
|
||||
```
|
||||
Usage of tests/cvms/main.go:
|
||||
-algo-path string
|
||||
Path to the algorithm
|
||||
-attested-tls-bool string
|
||||
Should aTLS be used, must be 'true' or 'false'
|
||||
-ca-url string
|
||||
URL for certificate authority, optional flag that can only be used if aTLS is enabled
|
||||
-cvm-id string
|
||||
UUID for a CVM, optional flag that can only be used if aTLS is enabled
|
||||
-data-paths string
|
||||
Paths to data sources, list of string separated with commas
|
||||
-public-key-path string
|
||||
Path to the public key file
|
||||
|
||||
- `algo_path`: Path to the algorithm file (python file,docker image file, wasm, compiled binary) \
|
||||
- `public_key_path`: Path to the public key file (PEM format) \
|
||||
- `attested_tls_bool`: Boolean flag to enable/disable attested TLS (true/false) \
|
||||
- `dataset(s)_path`: Path to one or more dataset files.
|
||||
# Example
|
||||
go run ./tests/cvms/main.go -algo-path <alog_path> -attested-tls-bool false -data-paths <data_paths> -public-key-path <public_key_path>
|
||||
```
|
||||
|
||||
+10
-7
@@ -42,6 +42,7 @@ var (
|
||||
pubKeyFile string
|
||||
caUrl string
|
||||
cvmId string
|
||||
clientCAFile string
|
||||
)
|
||||
|
||||
type svc struct {
|
||||
@@ -89,8 +90,9 @@ func (s *svc) Run(ctx context.Context, ipAddress string, sendMessage cvmsgrpc.Se
|
||||
Algorithm: &cvms.Algorithm{Hash: algoHash[:], UserKey: pubPem.Bytes},
|
||||
ResultConsumers: []*cvms.ResultConsumer{{UserKey: pubPem.Bytes}},
|
||||
AgentConfig: &cvms.AgentConfig{
|
||||
Port: "7002",
|
||||
AttestedTls: attestedTLS,
|
||||
Port: "7002",
|
||||
AttestedTls: attestedTLS,
|
||||
ClientCaFile: clientCAFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -108,6 +110,7 @@ func main() {
|
||||
flagSet.StringVar(&dataPathString, "data-paths", "", "Paths to data sources, list of string separated with commas")
|
||||
flagSet.StringVar(&caUrl, "ca-url", "", "URL for certificate authority, must be specified if aTLS is used")
|
||||
flagSet.StringVar(&cvmId, "cvm-id", "", "UUID for a CVM, must be specified if aTLS is used")
|
||||
flagSet.StringVar(&clientCAFile, "client-ca-file", "", "Client CA root certificate file path")
|
||||
|
||||
flagSetParseError := flagSet.Parse(os.Args[1:])
|
||||
if flagSetParseError != nil {
|
||||
@@ -145,13 +148,13 @@ func main() {
|
||||
dataPaths = strings.Split(dataPathString, ",")
|
||||
}
|
||||
|
||||
if err == nil && attestedTLS && caUrl == "" {
|
||||
parsingErrorString.WriteString("CA URL is required if attested TLS is used\n")
|
||||
if err == nil && caUrl != "" && !attestedTLS {
|
||||
parsingErrorString.WriteString("CA URL is only available with attested TLS\n")
|
||||
parsingError = true
|
||||
}
|
||||
|
||||
if err == nil && attestedTLS && cvmId == "" {
|
||||
parsingErrorString.WriteString("CVM UUID is required if attested TLS is used\n")
|
||||
if err == nil && cvmId != "" && !attestedTLS {
|
||||
parsingErrorString.WriteString("CVM UUID is only available with attested TLS\n")
|
||||
parsingError = true
|
||||
}
|
||||
|
||||
@@ -191,7 +194,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, nil, nil, caUrl, cvmId)
|
||||
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, nil, caUrl, cvmId)
|
||||
|
||||
g.Go(func() error {
|
||||
return gs.Start()
|
||||
|
||||
Reference in New Issue
Block a user