diff --git a/agent/agent.pb.go b/agent/agent.pb.go index f64b6077..afee5645 100644 --- a/agent/agent.pb.go +++ b/agent/agent.pb.go @@ -384,6 +384,94 @@ 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 +} + var File_agent_agent_proto protoreflect.FileDescriptor var file_agent_agent_proto_rawDesc = string([]byte{ @@ -412,24 +500,35 @@ 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, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12, - 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04, - 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, - 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, - 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b, - 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 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, 0x32, 0xd3, 0x02, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12, 0x12, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, + 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x30, 0x01, 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, 0x42, 0x09, 0x5a, 0x07, 0x2e, + 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -444,28 +543,32 @@ 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, 10) 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 } 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 + 8, // 4: agent.AgentService.IMAMeasurements:input_type -> agent.IMAMeasurementsRequest + 1, // 5: agent.AgentService.Algo:output_type -> agent.AlgoResponse + 3, // 6: agent.AgentService.Data:output_type -> agent.DataResponse + 5, // 7: agent.AgentService.Result:output_type -> agent.ResultResponse + 7, // 8: agent.AgentService.Attestation:output_type -> agent.AttestationResponse + 9, // 9: agent.AgentService.IMAMeasurements:output_type -> agent.IMAMeasurementsResponse + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] 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 @@ -482,7 +585,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: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/agent.proto b/agent/agent.proto index d9877426..2b6420bd 100644 --- a/agent/agent.proto +++ b/agent/agent.proto @@ -12,6 +12,7 @@ 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) {} } message AlgoRequest { @@ -44,3 +45,11 @@ message AttestationRequest { message AttestationResponse { bytes file = 1; } + +message IMAMeasurementsRequest { +} + +message IMAMeasurementsResponse { + bytes file = 1; + bytes pcr10 = 2; +} diff --git a/agent/agent_grpc.pb.go b/agent/agent_grpc.pb.go index 6459ecec..cfd588d2 100644 --- a/agent/agent_grpc.pb.go +++ b/agent/agent_grpc.pb.go @@ -22,10 +22,11 @@ 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" ) // AgentServiceClient is the client API for AgentService service. @@ -36,6 +37,7 @@ 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) } type agentServiceClient struct { @@ -110,6 +112,25 @@ 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] + // AgentServiceServer is the server API for AgentService service. // All implementations must embed UnimplementedAgentServiceServer // for forward compatibility. @@ -118,6 +139,7 @@ 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 mustEmbedUnimplementedAgentServiceServer() } @@ -140,6 +162,9 @@ 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) mustEmbedUnimplementedAgentServiceServer() {} func (UnimplementedAgentServiceServer) testEmbeddedByValue() {} @@ -197,6 +222,17 @@ 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] + // 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) @@ -225,6 +261,11 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{ Handler: _AgentService_Attestation_Handler, ServerStreams: true, }, + { + StreamName: "IMAMeasurements", + Handler: _AgentService_IMAMeasurements_Handler, + ServerStreams: true, + }, }, Metadata: "agent/agent.proto", } diff --git a/agent/api/grpc/endpoint.go b/agent/api/grpc/endpoint.go index 95c43f71..63c5848a 100644 --- a/agent/api/grpc/endpoint.go +++ b/agent/api/grpc/endpoint.go @@ -79,3 +79,19 @@ 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 + } +} diff --git a/agent/api/grpc/requests.go b/agent/api/grpc/requests.go index 1d5e2b5d..7cba8007 100644 --- a/agent/api/grpc/requests.go +++ b/agent/api/grpc/requests.go @@ -55,3 +55,10 @@ func (req attestationReq) validate() error { return errors.New("invalid attestation type in attestation request") } } + +type imaMeasurementsReq struct{} + +func (req imaMeasurementsReq) validate() error { + // No request parameters to validate, so no validation logic needed + return nil +} diff --git a/agent/api/grpc/responses.go b/agent/api/grpc/responses.go index b4f7d5ae..e4984d66 100644 --- a/agent/api/grpc/responses.go +++ b/agent/api/grpc/responses.go @@ -13,3 +13,8 @@ type resultRes struct { type attestationRes struct { File []byte } + +type imaMeasurementsRes struct { + File []byte + PCR10 []byte +} diff --git a/agent/api/grpc/server.go b/agent/api/grpc/server.go index 3bf19a7c..62f3ef61 100644 --- a/agent/api/grpc/server.go +++ b/agent/api/grpc/server.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "strconv" "github.com/go-kit/kit/transport/grpc" "github.com/ultravioletrs/cocos/agent" @@ -32,10 +33,11 @@ var ( 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 agent.UnimplementedAgentServiceServer } @@ -62,6 +64,11 @@ func NewServer(svc agent.Service) agent.AgentServiceServer { decodeAttestationRequest, encodeAttestationResponse, ), + imaMeasurements: grpc.NewServer( + imaMeasurementsEndpoint(svc), + decodeIMAMeasurementsRequest, + encodeIMAMeasurementsResponse, + ), } } @@ -235,3 +242,56 @@ 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 +} diff --git a/agent/api/logging.go b/agent/api/logging.go index 851dcb9c..508fe5d4 100644 --- a/agent/api/logging.go +++ b/agent/api/logging.go @@ -118,3 +118,16 @@ 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) +} diff --git a/agent/api/metrics.go b/agent/api/metrics.go index 8b9403ab..6236c526 100644 --- a/agent/api/metrics.go +++ b/agent/api/metrics.go @@ -100,3 +100,12 @@ func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quotep return ms.svc.Attestation(ctx, reportData, 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) +} diff --git a/agent/mocks/agent.go b/agent/mocks/agent.go index b4a48dad..17882fad 100644 --- a/agent/mocks/agent.go +++ b/agent/mocks/agent.go @@ -182,6 +182,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) diff --git a/agent/service.go b/agent/service.go index 959d6851..9fb89772 100644 --- a/agent/service.go +++ b/agent/service.go @@ -75,6 +75,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). @@ -113,6 +118,7 @@ type Service interface { 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) + IMAMeasurements(ctx context.Context) ([]byte, []byte, error) State() string } @@ -495,3 +501,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 +} diff --git a/cli/ima_measurements.go b/cli/ima_measurements.go new file mode 100644 index 00000000..634dfdf6 --- /dev/null +++ b/cli/ima_measurements.go @@ -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 ", + 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!")) + } + }, + } +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go index f6a2a2d9..ab99b4e9 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -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()) diff --git a/hal/cloud/config.yaml b/hal/cloud/config.yaml index 24985428..9c39d1f1 100644 --- a/hal/cloud/config.yaml +++ b/hal/cloud/config.yaml @@ -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 diff --git a/hal/cloud/qemu.sh b/hal/cloud/qemu.sh index e7ffe171..e6695279 100755 --- a/hal/cloud/qemu.sh +++ b/hal/cloud/qemu.sh @@ -111,7 +111,6 @@ construct_qemu_args() { fi args+=("-monitor" "$MONITOR") - args+=("-no-reboot") args+=("-vnc" ":9") echo "${args[@]}" diff --git a/hal/linux/board/cocos/linux.config b/hal/linux/board/cocos/linux.config index 10cb02f8..b74695de 100644 --- a/hal/linux/board/cocos/linux.config +++ b/hal/linux/board/cocos/linux.config @@ -76,3 +76,17 @@ 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 diff --git a/pkg/attestation/vtpm/vtpm.go b/pkg/attestation/vtpm/vtpm.go index 44ae4ef1..9b43e794 100644 --- a/pkg/attestation/vtpm/vtpm.go +++ b/pkg/attestation/vtpm/vtpm.go @@ -33,6 +33,7 @@ const ( eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements" Nonce = 32 PCR15 = 15 + Hash1 = 20 Hash256 = 32 Hash384 = 48 ) @@ -315,3 +316,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) +} diff --git a/pkg/progressbar/progressbar.go b/pkg/progressbar/progressbar.go index b0187f62..a32fd27d 100644 --- a/pkg/progressbar/progressbar.go +++ b/pkg/progressbar/progressbar.go @@ -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() diff --git a/pkg/sdk/agent.go b/pkg/sdk/agent.go index 5639c29a..5efefef5 100644 --- a/pkg/sdk/agent.go +++ b/pkg/sdk/agent.go @@ -27,15 +27,17 @@ 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) } 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 { @@ -186,3 +188,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) +} diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index 9a4bca0e..7e1674e7 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -173,6 +173,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)