From cbd77c58e819f124b7d294b700dbe1e208cf1842 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 16 Mar 2026 19:45:56 -0700 Subject: [PATCH] Implement transit encryption engine with versioned key management Add complete transit engine supporting symmetric encryption (AES-256-GCM, XChaCha20-Poly1305), asymmetric signing (Ed25519, ECDSA P-256/P-384), and HMAC (SHA-256/SHA-512) with versioned key rotation, min decryption version enforcement, key trimming, batch operations, and rewrap. Includes proto definitions, gRPC handlers, REST routes, and comprehensive tests covering all 18 operations, auth enforcement, and edge cases. Co-Authored-By: Claude Opus 4.6 --- cmd/metacrypt/server.go | 2 + gen/metacrypt/v2/transit.pb.go | 2197 +++++++++++++++++++++++ gen/metacrypt/v2/transit_grpc.pb.go | 777 ++++++++ internal/engine/transit/transit.go | 1602 +++++++++++++++++ internal/engine/transit/transit_test.go | 1025 +++++++++++ internal/engine/transit/types.go | 15 + internal/grpcserver/server.go | 46 +- internal/grpcserver/transit.go | 486 +++++ internal/server/routes.go | 281 ++- proto/metacrypt/v2/transit.proto | 291 +++ 10 files changed, 6718 insertions(+), 4 deletions(-) create mode 100644 gen/metacrypt/v2/transit.pb.go create mode 100644 gen/metacrypt/v2/transit_grpc.pb.go create mode 100644 internal/engine/transit/transit.go create mode 100644 internal/engine/transit/transit_test.go create mode 100644 internal/engine/transit/types.go create mode 100644 internal/grpcserver/transit.go create mode 100644 proto/metacrypt/v2/transit.proto diff --git a/cmd/metacrypt/server.go b/cmd/metacrypt/server.go index 9c4f4ad..e44ee70 100644 --- a/cmd/metacrypt/server.go +++ b/cmd/metacrypt/server.go @@ -16,6 +16,7 @@ import ( "git.wntrmute.dev/kyle/metacrypt/internal/db" "git.wntrmute.dev/kyle/metacrypt/internal/engine" "git.wntrmute.dev/kyle/metacrypt/internal/engine/ca" + "git.wntrmute.dev/kyle/metacrypt/internal/engine/transit" "git.wntrmute.dev/kyle/metacrypt/internal/grpcserver" "git.wntrmute.dev/kyle/metacrypt/internal/policy" "git.wntrmute.dev/kyle/metacrypt/internal/seal" @@ -74,6 +75,7 @@ func runServer(cmd *cobra.Command, args []string) error { policyEngine := policy.NewEngine(b) engineRegistry := engine.NewRegistry(b, logger) engineRegistry.RegisterFactory(engine.EngineTypeCA, ca.NewCAEngine) + engineRegistry.RegisterFactory(engine.EngineTypeTransit, transit.NewTransitEngine) srv := server.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger, version) grpcSrv := grpcserver.New(cfg, sealMgr, authenticator, policyEngine, engineRegistry, logger) diff --git a/gen/metacrypt/v2/transit.pb.go b/gen/metacrypt/v2/transit.pb.go new file mode 100644 index 0000000..8bfec8a --- /dev/null +++ b/gen/metacrypt/v2/transit.pb.go @@ -0,0 +1,2197 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.20.3 +// source: proto/metacrypt/v2/transit.proto + +package metacryptv2 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateTransitKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // type is the key algorithm: aes256-gcm, chacha20-poly, ed25519, + // ecdsa-p256, ecdsa-p384, hmac-sha256, hmac-sha512. + // Defaults to aes256-gcm if empty. + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTransitKeyRequest) Reset() { + *x = CreateTransitKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTransitKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTransitKeyRequest) ProtoMessage() {} + +func (x *CreateTransitKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[0] + 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 CreateTransitKeyRequest.ProtoReflect.Descriptor instead. +func (*CreateTransitKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateTransitKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *CreateTransitKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTransitKeyRequest) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +type CreateTransitKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTransitKeyResponse) Reset() { + *x = CreateTransitKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTransitKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTransitKeyResponse) ProtoMessage() {} + +func (x *CreateTransitKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[1] + 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 CreateTransitKeyResponse.ProtoReflect.Descriptor instead. +func (*CreateTransitKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateTransitKeyResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateTransitKeyResponse) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *CreateTransitKeyResponse) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +type DeleteTransitKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTransitKeyRequest) Reset() { + *x = DeleteTransitKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTransitKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTransitKeyRequest) ProtoMessage() {} + +func (x *DeleteTransitKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[2] + 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 DeleteTransitKeyRequest.ProtoReflect.Descriptor instead. +func (*DeleteTransitKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{2} +} + +func (x *DeleteTransitKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *DeleteTransitKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTransitKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTransitKeyResponse) Reset() { + *x = DeleteTransitKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTransitKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTransitKeyResponse) ProtoMessage() {} + +func (x *DeleteTransitKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[3] + 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 DeleteTransitKeyResponse.ProtoReflect.Descriptor instead. +func (*DeleteTransitKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{3} +} + +type GetTransitKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransitKeyRequest) Reset() { + *x = GetTransitKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransitKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransitKeyRequest) ProtoMessage() {} + +func (x *GetTransitKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[4] + 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 GetTransitKeyRequest.ProtoReflect.Descriptor instead. +func (*GetTransitKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{4} +} + +func (x *GetTransitKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *GetTransitKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetTransitKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + CurrentVersion int32 `protobuf:"varint,3,opt,name=current_version,json=currentVersion,proto3" json:"current_version,omitempty"` + MinDecryptionVersion int32 `protobuf:"varint,4,opt,name=min_decryption_version,json=minDecryptionVersion,proto3" json:"min_decryption_version,omitempty"` + AllowDeletion bool `protobuf:"varint,5,opt,name=allow_deletion,json=allowDeletion,proto3" json:"allow_deletion,omitempty"` + Versions []int32 `protobuf:"varint,6,rep,packed,name=versions,proto3" json:"versions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransitKeyResponse) Reset() { + *x = GetTransitKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransitKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransitKeyResponse) ProtoMessage() {} + +func (x *GetTransitKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[5] + 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 GetTransitKeyResponse.ProtoReflect.Descriptor instead. +func (*GetTransitKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{5} +} + +func (x *GetTransitKeyResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetTransitKeyResponse) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GetTransitKeyResponse) GetCurrentVersion() int32 { + if x != nil { + return x.CurrentVersion + } + return 0 +} + +func (x *GetTransitKeyResponse) GetMinDecryptionVersion() int32 { + if x != nil { + return x.MinDecryptionVersion + } + return 0 +} + +func (x *GetTransitKeyResponse) GetAllowDeletion() bool { + if x != nil { + return x.AllowDeletion + } + return false +} + +func (x *GetTransitKeyResponse) GetVersions() []int32 { + if x != nil { + return x.Versions + } + return nil +} + +type ListTransitKeysRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTransitKeysRequest) Reset() { + *x = ListTransitKeysRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTransitKeysRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTransitKeysRequest) ProtoMessage() {} + +func (x *ListTransitKeysRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[6] + 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 ListTransitKeysRequest.ProtoReflect.Descriptor instead. +func (*ListTransitKeysRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{6} +} + +func (x *ListTransitKeysRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +type ListTransitKeysResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Keys []string `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTransitKeysResponse) Reset() { + *x = ListTransitKeysResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTransitKeysResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTransitKeysResponse) ProtoMessage() {} + +func (x *ListTransitKeysResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[7] + 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 ListTransitKeysResponse.ProtoReflect.Descriptor instead. +func (*ListTransitKeysResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{7} +} + +func (x *ListTransitKeysResponse) GetKeys() []string { + if x != nil { + return x.Keys + } + return nil +} + +type RotateTransitKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RotateTransitKeyRequest) Reset() { + *x = RotateTransitKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RotateTransitKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RotateTransitKeyRequest) ProtoMessage() {} + +func (x *RotateTransitKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_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 RotateTransitKeyRequest.ProtoReflect.Descriptor instead. +func (*RotateTransitKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{8} +} + +func (x *RotateTransitKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *RotateTransitKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type RotateTransitKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RotateTransitKeyResponse) Reset() { + *x = RotateTransitKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RotateTransitKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RotateTransitKeyResponse) ProtoMessage() {} + +func (x *RotateTransitKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_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 RotateTransitKeyResponse.ProtoReflect.Descriptor instead. +func (*RotateTransitKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{9} +} + +func (x *RotateTransitKeyResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RotateTransitKeyResponse) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +type UpdateTransitKeyConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + MinDecryptionVersion int32 `protobuf:"varint,3,opt,name=min_decryption_version,json=minDecryptionVersion,proto3" json:"min_decryption_version,omitempty"` + AllowDeletion bool `protobuf:"varint,4,opt,name=allow_deletion,json=allowDeletion,proto3" json:"allow_deletion,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTransitKeyConfigRequest) Reset() { + *x = UpdateTransitKeyConfigRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTransitKeyConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTransitKeyConfigRequest) ProtoMessage() {} + +func (x *UpdateTransitKeyConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_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 UpdateTransitKeyConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateTransitKeyConfigRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateTransitKeyConfigRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *UpdateTransitKeyConfigRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateTransitKeyConfigRequest) GetMinDecryptionVersion() int32 { + if x != nil { + return x.MinDecryptionVersion + } + return 0 +} + +func (x *UpdateTransitKeyConfigRequest) GetAllowDeletion() bool { + if x != nil { + return x.AllowDeletion + } + return false +} + +type UpdateTransitKeyConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTransitKeyConfigResponse) Reset() { + *x = UpdateTransitKeyConfigResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTransitKeyConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTransitKeyConfigResponse) ProtoMessage() {} + +func (x *UpdateTransitKeyConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_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 UpdateTransitKeyConfigResponse.ProtoReflect.Descriptor instead. +func (*UpdateTransitKeyConfigResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{11} +} + +type TrimTransitKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrimTransitKeyRequest) Reset() { + *x = TrimTransitKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TrimTransitKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TrimTransitKeyRequest) ProtoMessage() {} + +func (x *TrimTransitKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[12] + 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 TrimTransitKeyRequest.ProtoReflect.Descriptor instead. +func (*TrimTransitKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{12} +} + +func (x *TrimTransitKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TrimTransitKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type TrimTransitKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Trimmed int32 `protobuf:"varint,1,opt,name=trimmed,proto3" json:"trimmed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrimTransitKeyResponse) Reset() { + *x = TrimTransitKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TrimTransitKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TrimTransitKeyResponse) ProtoMessage() {} + +func (x *TrimTransitKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[13] + 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 TrimTransitKeyResponse.ProtoReflect.Descriptor instead. +func (*TrimTransitKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{13} +} + +func (x *TrimTransitKeyResponse) GetTrimmed() int32 { + if x != nil { + return x.Trimmed + } + return 0 +} + +type TransitEncryptRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // plaintext is base64-encoded data to encrypt. + Plaintext string `protobuf:"bytes,3,opt,name=plaintext,proto3" json:"plaintext,omitempty"` + // context is optional base64-encoded additional authenticated data (AAD). + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitEncryptRequest) Reset() { + *x = TransitEncryptRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitEncryptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitEncryptRequest) ProtoMessage() {} + +func (x *TransitEncryptRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[14] + 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 TransitEncryptRequest.ProtoReflect.Descriptor instead. +func (*TransitEncryptRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{14} +} + +func (x *TransitEncryptRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitEncryptRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitEncryptRequest) GetPlaintext() string { + if x != nil { + return x.Plaintext + } + return "" +} + +func (x *TransitEncryptRequest) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type TransitEncryptResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ciphertext in format "metacrypt:v{version}:{base64(nonce+ciphertext+tag)}" + Ciphertext string `protobuf:"bytes,1,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitEncryptResponse) Reset() { + *x = TransitEncryptResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitEncryptResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitEncryptResponse) ProtoMessage() {} + +func (x *TransitEncryptResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[15] + 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 TransitEncryptResponse.ProtoReflect.Descriptor instead. +func (*TransitEncryptResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{15} +} + +func (x *TransitEncryptResponse) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +type TransitDecryptRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Ciphertext string `protobuf:"bytes,3,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitDecryptRequest) Reset() { + *x = TransitDecryptRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitDecryptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitDecryptRequest) ProtoMessage() {} + +func (x *TransitDecryptRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[16] + 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 TransitDecryptRequest.ProtoReflect.Descriptor instead. +func (*TransitDecryptRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{16} +} + +func (x *TransitDecryptRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitDecryptRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitDecryptRequest) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +func (x *TransitDecryptRequest) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type TransitDecryptResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // plaintext is base64-encoded decrypted data. + Plaintext string `protobuf:"bytes,1,opt,name=plaintext,proto3" json:"plaintext,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitDecryptResponse) Reset() { + *x = TransitDecryptResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitDecryptResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitDecryptResponse) ProtoMessage() {} + +func (x *TransitDecryptResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_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 TransitDecryptResponse.ProtoReflect.Descriptor instead. +func (*TransitDecryptResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{17} +} + +func (x *TransitDecryptResponse) GetPlaintext() string { + if x != nil { + return x.Plaintext + } + return "" +} + +type TransitRewrapRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Ciphertext string `protobuf:"bytes,3,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitRewrapRequest) Reset() { + *x = TransitRewrapRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitRewrapRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitRewrapRequest) ProtoMessage() {} + +func (x *TransitRewrapRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[18] + 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 TransitRewrapRequest.ProtoReflect.Descriptor instead. +func (*TransitRewrapRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{18} +} + +func (x *TransitRewrapRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitRewrapRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitRewrapRequest) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +func (x *TransitRewrapRequest) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type TransitRewrapResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ciphertext string `protobuf:"bytes,1,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitRewrapResponse) Reset() { + *x = TransitRewrapResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitRewrapResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitRewrapResponse) ProtoMessage() {} + +func (x *TransitRewrapResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[19] + 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 TransitRewrapResponse.ProtoReflect.Descriptor instead. +func (*TransitRewrapResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{19} +} + +func (x *TransitRewrapResponse) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +type TransitBatchItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Plaintext string `protobuf:"bytes,1,opt,name=plaintext,proto3" json:"plaintext,omitempty"` + Ciphertext string `protobuf:"bytes,2,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + Context string `protobuf:"bytes,3,opt,name=context,proto3" json:"context,omitempty"` + Reference string `protobuf:"bytes,4,opt,name=reference,proto3" json:"reference,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchItem) Reset() { + *x = TransitBatchItem{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchItem) ProtoMessage() {} + +func (x *TransitBatchItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[20] + 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 TransitBatchItem.ProtoReflect.Descriptor instead. +func (*TransitBatchItem) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{20} +} + +func (x *TransitBatchItem) GetPlaintext() string { + if x != nil { + return x.Plaintext + } + return "" +} + +func (x *TransitBatchItem) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +func (x *TransitBatchItem) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *TransitBatchItem) GetReference() string { + if x != nil { + return x.Reference + } + return "" +} + +type TransitBatchResultItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Plaintext string `protobuf:"bytes,1,opt,name=plaintext,proto3" json:"plaintext,omitempty"` + Ciphertext string `protobuf:"bytes,2,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` + Reference string `protobuf:"bytes,3,opt,name=reference,proto3" json:"reference,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchResultItem) Reset() { + *x = TransitBatchResultItem{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchResultItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchResultItem) ProtoMessage() {} + +func (x *TransitBatchResultItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[21] + 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 TransitBatchResultItem.ProtoReflect.Descriptor instead. +func (*TransitBatchResultItem) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{21} +} + +func (x *TransitBatchResultItem) GetPlaintext() string { + if x != nil { + return x.Plaintext + } + return "" +} + +func (x *TransitBatchResultItem) GetCiphertext() string { + if x != nil { + return x.Ciphertext + } + return "" +} + +func (x *TransitBatchResultItem) GetReference() string { + if x != nil { + return x.Reference + } + return "" +} + +func (x *TransitBatchResultItem) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type TransitBatchEncryptRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Items []*TransitBatchItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchEncryptRequest) Reset() { + *x = TransitBatchEncryptRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchEncryptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchEncryptRequest) ProtoMessage() {} + +func (x *TransitBatchEncryptRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[22] + 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 TransitBatchEncryptRequest.ProtoReflect.Descriptor instead. +func (*TransitBatchEncryptRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{22} +} + +func (x *TransitBatchEncryptRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitBatchEncryptRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitBatchEncryptRequest) GetItems() []*TransitBatchItem { + if x != nil { + return x.Items + } + return nil +} + +type TransitBatchDecryptRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Items []*TransitBatchItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchDecryptRequest) Reset() { + *x = TransitBatchDecryptRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchDecryptRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchDecryptRequest) ProtoMessage() {} + +func (x *TransitBatchDecryptRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[23] + 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 TransitBatchDecryptRequest.ProtoReflect.Descriptor instead. +func (*TransitBatchDecryptRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{23} +} + +func (x *TransitBatchDecryptRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitBatchDecryptRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitBatchDecryptRequest) GetItems() []*TransitBatchItem { + if x != nil { + return x.Items + } + return nil +} + +type TransitBatchRewrapRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Items []*TransitBatchItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchRewrapRequest) Reset() { + *x = TransitBatchRewrapRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchRewrapRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchRewrapRequest) ProtoMessage() {} + +func (x *TransitBatchRewrapRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[24] + 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 TransitBatchRewrapRequest.ProtoReflect.Descriptor instead. +func (*TransitBatchRewrapRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{24} +} + +func (x *TransitBatchRewrapRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitBatchRewrapRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitBatchRewrapRequest) GetItems() []*TransitBatchItem { + if x != nil { + return x.Items + } + return nil +} + +type TransitBatchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Results []*TransitBatchResultItem `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitBatchResponse) Reset() { + *x = TransitBatchResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitBatchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitBatchResponse) ProtoMessage() {} + +func (x *TransitBatchResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[25] + 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 TransitBatchResponse.ProtoReflect.Descriptor instead. +func (*TransitBatchResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{25} +} + +func (x *TransitBatchResponse) GetResults() []*TransitBatchResultItem { + if x != nil { + return x.Results + } + return nil +} + +type TransitSignRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // input is base64-encoded data to sign. + Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitSignRequest) Reset() { + *x = TransitSignRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitSignRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitSignRequest) ProtoMessage() {} + +func (x *TransitSignRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[26] + 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 TransitSignRequest.ProtoReflect.Descriptor instead. +func (*TransitSignRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{26} +} + +func (x *TransitSignRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitSignRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitSignRequest) GetInput() string { + if x != nil { + return x.Input + } + return "" +} + +type TransitSignResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // signature in format "metacrypt:v{version}:{base64(signature_bytes)}" + Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitSignResponse) Reset() { + *x = TransitSignResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitSignResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitSignResponse) ProtoMessage() {} + +func (x *TransitSignResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[27] + 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 TransitSignResponse.ProtoReflect.Descriptor instead. +func (*TransitSignResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{27} +} + +func (x *TransitSignResponse) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +type TransitVerifyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` + Signature string `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitVerifyRequest) Reset() { + *x = TransitVerifyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitVerifyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitVerifyRequest) ProtoMessage() {} + +func (x *TransitVerifyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[28] + 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 TransitVerifyRequest.ProtoReflect.Descriptor instead. +func (*TransitVerifyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{28} +} + +func (x *TransitVerifyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitVerifyRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitVerifyRequest) GetInput() string { + if x != nil { + return x.Input + } + return "" +} + +func (x *TransitVerifyRequest) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +type TransitVerifyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitVerifyResponse) Reset() { + *x = TransitVerifyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitVerifyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitVerifyResponse) ProtoMessage() {} + +func (x *TransitVerifyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[29] + 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 TransitVerifyResponse.ProtoReflect.Descriptor instead. +func (*TransitVerifyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{29} +} + +func (x *TransitVerifyResponse) GetValid() bool { + if x != nil { + return x.Valid + } + return false +} + +type TransitHmacRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // input is base64-encoded data to HMAC. + Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` + // hmac, if set, switches to verify mode. + Hmac string `protobuf:"bytes,4,opt,name=hmac,proto3" json:"hmac,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitHmacRequest) Reset() { + *x = TransitHmacRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitHmacRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitHmacRequest) ProtoMessage() {} + +func (x *TransitHmacRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[30] + 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 TransitHmacRequest.ProtoReflect.Descriptor instead. +func (*TransitHmacRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{30} +} + +func (x *TransitHmacRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *TransitHmacRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *TransitHmacRequest) GetInput() string { + if x != nil { + return x.Input + } + return "" +} + +func (x *TransitHmacRequest) GetHmac() string { + if x != nil { + return x.Hmac + } + return "" +} + +type TransitHmacResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // hmac is set in compute mode. + Hmac string `protobuf:"bytes,1,opt,name=hmac,proto3" json:"hmac,omitempty"` + // valid is set in verify mode. + Valid bool `protobuf:"varint,2,opt,name=valid,proto3" json:"valid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransitHmacResponse) Reset() { + *x = TransitHmacResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransitHmacResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransitHmacResponse) ProtoMessage() {} + +func (x *TransitHmacResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[31] + 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 TransitHmacResponse.ProtoReflect.Descriptor instead. +func (*TransitHmacResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{31} +} + +func (x *TransitHmacResponse) GetHmac() string { + if x != nil { + return x.Hmac + } + return "" +} + +func (x *TransitHmacResponse) GetValid() bool { + if x != nil { + return x.Valid + } + return false +} + +type GetTransitPublicKeyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransitPublicKeyRequest) Reset() { + *x = GetTransitPublicKeyRequest{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransitPublicKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransitPublicKeyRequest) ProtoMessage() {} + +func (x *GetTransitPublicKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[32] + 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 GetTransitPublicKeyRequest.ProtoReflect.Descriptor instead. +func (*GetTransitPublicKeyRequest) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{32} +} + +func (x *GetTransitPublicKeyRequest) GetMount() string { + if x != nil { + return x.Mount + } + return "" +} + +func (x *GetTransitPublicKeyRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetTransitPublicKeyRequest) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +type GetTransitPublicKeyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // public_key is base64-encoded PKIX DER public key. + PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTransitPublicKeyResponse) Reset() { + *x = GetTransitPublicKeyResponse{} + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTransitPublicKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTransitPublicKeyResponse) ProtoMessage() {} + +func (x *GetTransitPublicKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_metacrypt_v2_transit_proto_msgTypes[33] + 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 GetTransitPublicKeyResponse.ProtoReflect.Descriptor instead. +func (*GetTransitPublicKeyResponse) Descriptor() ([]byte, []int) { + return file_proto_metacrypt_v2_transit_proto_rawDescGZIP(), []int{33} +} + +func (x *GetTransitPublicKeyResponse) GetPublicKey() string { + if x != nil { + return x.PublicKey + } + return "" +} + +func (x *GetTransitPublicKeyResponse) GetVersion() int32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *GetTransitPublicKeyResponse) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +var File_proto_metacrypt_v2_transit_proto protoreflect.FileDescriptor + +const file_proto_metacrypt_v2_transit_proto_rawDesc = "" + + "\n" + + " proto/metacrypt/v2/transit.proto\x12\fmetacrypt.v2\"W\n" + + "\x17CreateTransitKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\"\\\n" + + "\x18CreateTransitKeyResponse\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x18\n" + + "\aversion\x18\x03 \x01(\x05R\aversion\"C\n" + + "\x17DeleteTransitKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\x1a\n" + + "\x18DeleteTransitKeyResponse\"@\n" + + "\x14GetTransitKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\xe1\x01\n" + + "\x15GetTransitKeyResponse\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12'\n" + + "\x0fcurrent_version\x18\x03 \x01(\x05R\x0ecurrentVersion\x124\n" + + "\x16min_decryption_version\x18\x04 \x01(\x05R\x14minDecryptionVersion\x12%\n" + + "\x0eallow_deletion\x18\x05 \x01(\bR\rallowDeletion\x12\x1a\n" + + "\bversions\x18\x06 \x03(\x05R\bversions\".\n" + + "\x16ListTransitKeysRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\"-\n" + + "\x17ListTransitKeysResponse\x12\x12\n" + + "\x04keys\x18\x01 \x03(\tR\x04keys\"C\n" + + "\x17RotateTransitKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"H\n" + + "\x18RotateTransitKeyResponse\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aversion\x18\x02 \x01(\x05R\aversion\"\xa6\x01\n" + + "\x1dUpdateTransitKeyConfigRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x124\n" + + "\x16min_decryption_version\x18\x03 \x01(\x05R\x14minDecryptionVersion\x12%\n" + + "\x0eallow_deletion\x18\x04 \x01(\bR\rallowDeletion\" \n" + + "\x1eUpdateTransitKeyConfigResponse\"A\n" + + "\x15TrimTransitKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"2\n" + + "\x16TrimTransitKeyResponse\x12\x18\n" + + "\atrimmed\x18\x01 \x01(\x05R\atrimmed\"w\n" + + "\x15TransitEncryptRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x1c\n" + + "\tplaintext\x18\x03 \x01(\tR\tplaintext\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"8\n" + + "\x16TransitEncryptResponse\x12\x1e\n" + + "\n" + + "ciphertext\x18\x01 \x01(\tR\n" + + "ciphertext\"y\n" + + "\x15TransitDecryptRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x1e\n" + + "\n" + + "ciphertext\x18\x03 \x01(\tR\n" + + "ciphertext\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"6\n" + + "\x16TransitDecryptResponse\x12\x1c\n" + + "\tplaintext\x18\x01 \x01(\tR\tplaintext\"x\n" + + "\x14TransitRewrapRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x1e\n" + + "\n" + + "ciphertext\x18\x03 \x01(\tR\n" + + "ciphertext\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"7\n" + + "\x15TransitRewrapResponse\x12\x1e\n" + + "\n" + + "ciphertext\x18\x01 \x01(\tR\n" + + "ciphertext\"\x88\x01\n" + + "\x10TransitBatchItem\x12\x1c\n" + + "\tplaintext\x18\x01 \x01(\tR\tplaintext\x12\x1e\n" + + "\n" + + "ciphertext\x18\x02 \x01(\tR\n" + + "ciphertext\x12\x18\n" + + "\acontext\x18\x03 \x01(\tR\acontext\x12\x1c\n" + + "\treference\x18\x04 \x01(\tR\treference\"\x8a\x01\n" + + "\x16TransitBatchResultItem\x12\x1c\n" + + "\tplaintext\x18\x01 \x01(\tR\tplaintext\x12\x1e\n" + + "\n" + + "ciphertext\x18\x02 \x01(\tR\n" + + "ciphertext\x12\x1c\n" + + "\treference\x18\x03 \x01(\tR\treference\x12\x14\n" + + "\x05error\x18\x04 \x01(\tR\x05error\"z\n" + + "\x1aTransitBatchEncryptRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x124\n" + + "\x05items\x18\x03 \x03(\v2\x1e.metacrypt.v2.TransitBatchItemR\x05items\"z\n" + + "\x1aTransitBatchDecryptRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x124\n" + + "\x05items\x18\x03 \x03(\v2\x1e.metacrypt.v2.TransitBatchItemR\x05items\"y\n" + + "\x19TransitBatchRewrapRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x124\n" + + "\x05items\x18\x03 \x03(\v2\x1e.metacrypt.v2.TransitBatchItemR\x05items\"V\n" + + "\x14TransitBatchResponse\x12>\n" + + "\aresults\x18\x01 \x03(\v2$.metacrypt.v2.TransitBatchResultItemR\aresults\"R\n" + + "\x12TransitSignRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x14\n" + + "\x05input\x18\x03 \x01(\tR\x05input\"3\n" + + "\x13TransitSignResponse\x12\x1c\n" + + "\tsignature\x18\x01 \x01(\tR\tsignature\"r\n" + + "\x14TransitVerifyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x14\n" + + "\x05input\x18\x03 \x01(\tR\x05input\x12\x1c\n" + + "\tsignature\x18\x04 \x01(\tR\tsignature\"-\n" + + "\x15TransitVerifyResponse\x12\x14\n" + + "\x05valid\x18\x01 \x01(\bR\x05valid\"f\n" + + "\x12TransitHmacRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x14\n" + + "\x05input\x18\x03 \x01(\tR\x05input\x12\x12\n" + + "\x04hmac\x18\x04 \x01(\tR\x04hmac\"?\n" + + "\x13TransitHmacResponse\x12\x12\n" + + "\x04hmac\x18\x01 \x01(\tR\x04hmac\x12\x14\n" + + "\x05valid\x18\x02 \x01(\bR\x05valid\"`\n" + + "\x1aGetTransitPublicKeyRequest\x12\x14\n" + + "\x05mount\x18\x01 \x01(\tR\x05mount\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + + "\aversion\x18\x03 \x01(\x05R\aversion\"j\n" + + "\x1bGetTransitPublicKeyResponse\x12\x1d\n" + + "\n" + + "public_key\x18\x01 \x01(\tR\tpublicKey\x12\x18\n" + + "\aversion\x18\x02 \x01(\x05R\aversion\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type2\xfd\v\n" + + "\x0eTransitService\x12Z\n" + + "\tCreateKey\x12%.metacrypt.v2.CreateTransitKeyRequest\x1a&.metacrypt.v2.CreateTransitKeyResponse\x12Z\n" + + "\tDeleteKey\x12%.metacrypt.v2.DeleteTransitKeyRequest\x1a&.metacrypt.v2.DeleteTransitKeyResponse\x12Q\n" + + "\x06GetKey\x12\".metacrypt.v2.GetTransitKeyRequest\x1a#.metacrypt.v2.GetTransitKeyResponse\x12W\n" + + "\bListKeys\x12$.metacrypt.v2.ListTransitKeysRequest\x1a%.metacrypt.v2.ListTransitKeysResponse\x12Z\n" + + "\tRotateKey\x12%.metacrypt.v2.RotateTransitKeyRequest\x1a&.metacrypt.v2.RotateTransitKeyResponse\x12l\n" + + "\x0fUpdateKeyConfig\x12+.metacrypt.v2.UpdateTransitKeyConfigRequest\x1a,.metacrypt.v2.UpdateTransitKeyConfigResponse\x12T\n" + + "\aTrimKey\x12#.metacrypt.v2.TrimTransitKeyRequest\x1a$.metacrypt.v2.TrimTransitKeyResponse\x12T\n" + + "\aEncrypt\x12#.metacrypt.v2.TransitEncryptRequest\x1a$.metacrypt.v2.TransitEncryptResponse\x12T\n" + + "\aDecrypt\x12#.metacrypt.v2.TransitDecryptRequest\x1a$.metacrypt.v2.TransitDecryptResponse\x12Q\n" + + "\x06Rewrap\x12\".metacrypt.v2.TransitRewrapRequest\x1a#.metacrypt.v2.TransitRewrapResponse\x12\\\n" + + "\fBatchEncrypt\x12(.metacrypt.v2.TransitBatchEncryptRequest\x1a\".metacrypt.v2.TransitBatchResponse\x12\\\n" + + "\fBatchDecrypt\x12(.metacrypt.v2.TransitBatchDecryptRequest\x1a\".metacrypt.v2.TransitBatchResponse\x12Z\n" + + "\vBatchRewrap\x12'.metacrypt.v2.TransitBatchRewrapRequest\x1a\".metacrypt.v2.TransitBatchResponse\x12K\n" + + "\x04Sign\x12 .metacrypt.v2.TransitSignRequest\x1a!.metacrypt.v2.TransitSignResponse\x12Q\n" + + "\x06Verify\x12\".metacrypt.v2.TransitVerifyRequest\x1a#.metacrypt.v2.TransitVerifyResponse\x12K\n" + + "\x04Hmac\x12 .metacrypt.v2.TransitHmacRequest\x1a!.metacrypt.v2.TransitHmacResponse\x12c\n" + + "\fGetPublicKey\x12(.metacrypt.v2.GetTransitPublicKeyRequest\x1a).metacrypt.v2.GetTransitPublicKeyResponseB>Z metacrypt.v2.TransitBatchItem + 20, // 1: metacrypt.v2.TransitBatchDecryptRequest.items:type_name -> metacrypt.v2.TransitBatchItem + 20, // 2: metacrypt.v2.TransitBatchRewrapRequest.items:type_name -> metacrypt.v2.TransitBatchItem + 21, // 3: metacrypt.v2.TransitBatchResponse.results:type_name -> metacrypt.v2.TransitBatchResultItem + 0, // 4: metacrypt.v2.TransitService.CreateKey:input_type -> metacrypt.v2.CreateTransitKeyRequest + 2, // 5: metacrypt.v2.TransitService.DeleteKey:input_type -> metacrypt.v2.DeleteTransitKeyRequest + 4, // 6: metacrypt.v2.TransitService.GetKey:input_type -> metacrypt.v2.GetTransitKeyRequest + 6, // 7: metacrypt.v2.TransitService.ListKeys:input_type -> metacrypt.v2.ListTransitKeysRequest + 8, // 8: metacrypt.v2.TransitService.RotateKey:input_type -> metacrypt.v2.RotateTransitKeyRequest + 10, // 9: metacrypt.v2.TransitService.UpdateKeyConfig:input_type -> metacrypt.v2.UpdateTransitKeyConfigRequest + 12, // 10: metacrypt.v2.TransitService.TrimKey:input_type -> metacrypt.v2.TrimTransitKeyRequest + 14, // 11: metacrypt.v2.TransitService.Encrypt:input_type -> metacrypt.v2.TransitEncryptRequest + 16, // 12: metacrypt.v2.TransitService.Decrypt:input_type -> metacrypt.v2.TransitDecryptRequest + 18, // 13: metacrypt.v2.TransitService.Rewrap:input_type -> metacrypt.v2.TransitRewrapRequest + 22, // 14: metacrypt.v2.TransitService.BatchEncrypt:input_type -> metacrypt.v2.TransitBatchEncryptRequest + 23, // 15: metacrypt.v2.TransitService.BatchDecrypt:input_type -> metacrypt.v2.TransitBatchDecryptRequest + 24, // 16: metacrypt.v2.TransitService.BatchRewrap:input_type -> metacrypt.v2.TransitBatchRewrapRequest + 26, // 17: metacrypt.v2.TransitService.Sign:input_type -> metacrypt.v2.TransitSignRequest + 28, // 18: metacrypt.v2.TransitService.Verify:input_type -> metacrypt.v2.TransitVerifyRequest + 30, // 19: metacrypt.v2.TransitService.Hmac:input_type -> metacrypt.v2.TransitHmacRequest + 32, // 20: metacrypt.v2.TransitService.GetPublicKey:input_type -> metacrypt.v2.GetTransitPublicKeyRequest + 1, // 21: metacrypt.v2.TransitService.CreateKey:output_type -> metacrypt.v2.CreateTransitKeyResponse + 3, // 22: metacrypt.v2.TransitService.DeleteKey:output_type -> metacrypt.v2.DeleteTransitKeyResponse + 5, // 23: metacrypt.v2.TransitService.GetKey:output_type -> metacrypt.v2.GetTransitKeyResponse + 7, // 24: metacrypt.v2.TransitService.ListKeys:output_type -> metacrypt.v2.ListTransitKeysResponse + 9, // 25: metacrypt.v2.TransitService.RotateKey:output_type -> metacrypt.v2.RotateTransitKeyResponse + 11, // 26: metacrypt.v2.TransitService.UpdateKeyConfig:output_type -> metacrypt.v2.UpdateTransitKeyConfigResponse + 13, // 27: metacrypt.v2.TransitService.TrimKey:output_type -> metacrypt.v2.TrimTransitKeyResponse + 15, // 28: metacrypt.v2.TransitService.Encrypt:output_type -> metacrypt.v2.TransitEncryptResponse + 17, // 29: metacrypt.v2.TransitService.Decrypt:output_type -> metacrypt.v2.TransitDecryptResponse + 19, // 30: metacrypt.v2.TransitService.Rewrap:output_type -> metacrypt.v2.TransitRewrapResponse + 25, // 31: metacrypt.v2.TransitService.BatchEncrypt:output_type -> metacrypt.v2.TransitBatchResponse + 25, // 32: metacrypt.v2.TransitService.BatchDecrypt:output_type -> metacrypt.v2.TransitBatchResponse + 25, // 33: metacrypt.v2.TransitService.BatchRewrap:output_type -> metacrypt.v2.TransitBatchResponse + 27, // 34: metacrypt.v2.TransitService.Sign:output_type -> metacrypt.v2.TransitSignResponse + 29, // 35: metacrypt.v2.TransitService.Verify:output_type -> metacrypt.v2.TransitVerifyResponse + 31, // 36: metacrypt.v2.TransitService.Hmac:output_type -> metacrypt.v2.TransitHmacResponse + 33, // 37: metacrypt.v2.TransitService.GetPublicKey:output_type -> metacrypt.v2.GetTransitPublicKeyResponse + 21, // [21:38] is the sub-list for method output_type + 4, // [4:21] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_proto_metacrypt_v2_transit_proto_init() } +func file_proto_metacrypt_v2_transit_proto_init() { + if File_proto_metacrypt_v2_transit_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_metacrypt_v2_transit_proto_rawDesc), len(file_proto_metacrypt_v2_transit_proto_rawDesc)), + NumEnums: 0, + NumMessages: 34, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_metacrypt_v2_transit_proto_goTypes, + DependencyIndexes: file_proto_metacrypt_v2_transit_proto_depIdxs, + MessageInfos: file_proto_metacrypt_v2_transit_proto_msgTypes, + }.Build() + File_proto_metacrypt_v2_transit_proto = out.File + file_proto_metacrypt_v2_transit_proto_goTypes = nil + file_proto_metacrypt_v2_transit_proto_depIdxs = nil +} diff --git a/gen/metacrypt/v2/transit_grpc.pb.go b/gen/metacrypt/v2/transit_grpc.pb.go new file mode 100644 index 0000000..27beafe --- /dev/null +++ b/gen/metacrypt/v2/transit_grpc.pb.go @@ -0,0 +1,777 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v3.20.3 +// source: proto/metacrypt/v2/transit.proto + +package metacryptv2 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + TransitService_CreateKey_FullMethodName = "/metacrypt.v2.TransitService/CreateKey" + TransitService_DeleteKey_FullMethodName = "/metacrypt.v2.TransitService/DeleteKey" + TransitService_GetKey_FullMethodName = "/metacrypt.v2.TransitService/GetKey" + TransitService_ListKeys_FullMethodName = "/metacrypt.v2.TransitService/ListKeys" + TransitService_RotateKey_FullMethodName = "/metacrypt.v2.TransitService/RotateKey" + TransitService_UpdateKeyConfig_FullMethodName = "/metacrypt.v2.TransitService/UpdateKeyConfig" + TransitService_TrimKey_FullMethodName = "/metacrypt.v2.TransitService/TrimKey" + TransitService_Encrypt_FullMethodName = "/metacrypt.v2.TransitService/Encrypt" + TransitService_Decrypt_FullMethodName = "/metacrypt.v2.TransitService/Decrypt" + TransitService_Rewrap_FullMethodName = "/metacrypt.v2.TransitService/Rewrap" + TransitService_BatchEncrypt_FullMethodName = "/metacrypt.v2.TransitService/BatchEncrypt" + TransitService_BatchDecrypt_FullMethodName = "/metacrypt.v2.TransitService/BatchDecrypt" + TransitService_BatchRewrap_FullMethodName = "/metacrypt.v2.TransitService/BatchRewrap" + TransitService_Sign_FullMethodName = "/metacrypt.v2.TransitService/Sign" + TransitService_Verify_FullMethodName = "/metacrypt.v2.TransitService/Verify" + TransitService_Hmac_FullMethodName = "/metacrypt.v2.TransitService/Hmac" + TransitService_GetPublicKey_FullMethodName = "/metacrypt.v2.TransitService/GetPublicKey" +) + +// TransitServiceClient is the client API for TransitService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// TransitService provides typed, authenticated access to transit engine +// operations: symmetric encryption, signing, HMAC, and versioned key +// management. All RPCs require the service to be unsealed. +type TransitServiceClient interface { + // CreateKey creates a new named encryption key. Admin only. + CreateKey(ctx context.Context, in *CreateTransitKeyRequest, opts ...grpc.CallOption) (*CreateTransitKeyResponse, error) + // DeleteKey permanently removes a named key. Admin only. + // Only succeeds if allow_deletion is true on the key config. + DeleteKey(ctx context.Context, in *DeleteTransitKeyRequest, opts ...grpc.CallOption) (*DeleteTransitKeyResponse, error) + // GetKey returns metadata for a named key (no raw material). Auth required. + GetKey(ctx context.Context, in *GetTransitKeyRequest, opts ...grpc.CallOption) (*GetTransitKeyResponse, error) + // ListKeys returns the names of all configured keys. Auth required. + ListKeys(ctx context.Context, in *ListTransitKeysRequest, opts ...grpc.CallOption) (*ListTransitKeysResponse, error) + // RotateKey creates a new version of the named key. Admin only. + RotateKey(ctx context.Context, in *RotateTransitKeyRequest, opts ...grpc.CallOption) (*RotateTransitKeyResponse, error) + // UpdateKeyConfig updates key configuration (e.g. min_decryption_version). + // Admin only. + UpdateKeyConfig(ctx context.Context, in *UpdateTransitKeyConfigRequest, opts ...grpc.CallOption) (*UpdateTransitKeyConfigResponse, error) + // TrimKey deletes versions below min_decryption_version. Admin only. + TrimKey(ctx context.Context, in *TrimTransitKeyRequest, opts ...grpc.CallOption) (*TrimTransitKeyResponse, error) + // Encrypt encrypts plaintext with the latest key version. Auth required. + Encrypt(ctx context.Context, in *TransitEncryptRequest, opts ...grpc.CallOption) (*TransitEncryptResponse, error) + // Decrypt decrypts ciphertext. Auth required. + Decrypt(ctx context.Context, in *TransitDecryptRequest, opts ...grpc.CallOption) (*TransitDecryptResponse, error) + // Rewrap re-encrypts ciphertext with the latest key version without + // exposing plaintext. Auth required. + Rewrap(ctx context.Context, in *TransitRewrapRequest, opts ...grpc.CallOption) (*TransitRewrapResponse, error) + // BatchEncrypt encrypts multiple items in a single request. Auth required. + BatchEncrypt(ctx context.Context, in *TransitBatchEncryptRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) + // BatchDecrypt decrypts multiple items in a single request. Auth required. + BatchDecrypt(ctx context.Context, in *TransitBatchDecryptRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) + // BatchRewrap re-encrypts multiple items in a single request. Auth required. + BatchRewrap(ctx context.Context, in *TransitBatchRewrapRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) + // Sign signs input data with an asymmetric key. Auth required. + Sign(ctx context.Context, in *TransitSignRequest, opts ...grpc.CallOption) (*TransitSignResponse, error) + // Verify verifies a signature against input data. Auth required. + Verify(ctx context.Context, in *TransitVerifyRequest, opts ...grpc.CallOption) (*TransitVerifyResponse, error) + // Hmac computes or verifies an HMAC. Auth required. + Hmac(ctx context.Context, in *TransitHmacRequest, opts ...grpc.CallOption) (*TransitHmacResponse, error) + // GetPublicKey returns the public key for an asymmetric key. Auth required. + GetPublicKey(ctx context.Context, in *GetTransitPublicKeyRequest, opts ...grpc.CallOption) (*GetTransitPublicKeyResponse, error) +} + +type transitServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTransitServiceClient(cc grpc.ClientConnInterface) TransitServiceClient { + return &transitServiceClient{cc} +} + +func (c *transitServiceClient) CreateKey(ctx context.Context, in *CreateTransitKeyRequest, opts ...grpc.CallOption) (*CreateTransitKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateTransitKeyResponse) + err := c.cc.Invoke(ctx, TransitService_CreateKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) DeleteKey(ctx context.Context, in *DeleteTransitKeyRequest, opts ...grpc.CallOption) (*DeleteTransitKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteTransitKeyResponse) + err := c.cc.Invoke(ctx, TransitService_DeleteKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) GetKey(ctx context.Context, in *GetTransitKeyRequest, opts ...grpc.CallOption) (*GetTransitKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetTransitKeyResponse) + err := c.cc.Invoke(ctx, TransitService_GetKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) ListKeys(ctx context.Context, in *ListTransitKeysRequest, opts ...grpc.CallOption) (*ListTransitKeysResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListTransitKeysResponse) + err := c.cc.Invoke(ctx, TransitService_ListKeys_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) RotateKey(ctx context.Context, in *RotateTransitKeyRequest, opts ...grpc.CallOption) (*RotateTransitKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RotateTransitKeyResponse) + err := c.cc.Invoke(ctx, TransitService_RotateKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) UpdateKeyConfig(ctx context.Context, in *UpdateTransitKeyConfigRequest, opts ...grpc.CallOption) (*UpdateTransitKeyConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateTransitKeyConfigResponse) + err := c.cc.Invoke(ctx, TransitService_UpdateKeyConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) TrimKey(ctx context.Context, in *TrimTransitKeyRequest, opts ...grpc.CallOption) (*TrimTransitKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TrimTransitKeyResponse) + err := c.cc.Invoke(ctx, TransitService_TrimKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Encrypt(ctx context.Context, in *TransitEncryptRequest, opts ...grpc.CallOption) (*TransitEncryptResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitEncryptResponse) + err := c.cc.Invoke(ctx, TransitService_Encrypt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Decrypt(ctx context.Context, in *TransitDecryptRequest, opts ...grpc.CallOption) (*TransitDecryptResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitDecryptResponse) + err := c.cc.Invoke(ctx, TransitService_Decrypt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Rewrap(ctx context.Context, in *TransitRewrapRequest, opts ...grpc.CallOption) (*TransitRewrapResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitRewrapResponse) + err := c.cc.Invoke(ctx, TransitService_Rewrap_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) BatchEncrypt(ctx context.Context, in *TransitBatchEncryptRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitBatchResponse) + err := c.cc.Invoke(ctx, TransitService_BatchEncrypt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) BatchDecrypt(ctx context.Context, in *TransitBatchDecryptRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitBatchResponse) + err := c.cc.Invoke(ctx, TransitService_BatchDecrypt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) BatchRewrap(ctx context.Context, in *TransitBatchRewrapRequest, opts ...grpc.CallOption) (*TransitBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitBatchResponse) + err := c.cc.Invoke(ctx, TransitService_BatchRewrap_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Sign(ctx context.Context, in *TransitSignRequest, opts ...grpc.CallOption) (*TransitSignResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitSignResponse) + err := c.cc.Invoke(ctx, TransitService_Sign_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Verify(ctx context.Context, in *TransitVerifyRequest, opts ...grpc.CallOption) (*TransitVerifyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitVerifyResponse) + err := c.cc.Invoke(ctx, TransitService_Verify_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) Hmac(ctx context.Context, in *TransitHmacRequest, opts ...grpc.CallOption) (*TransitHmacResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransitHmacResponse) + err := c.cc.Invoke(ctx, TransitService_Hmac_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *transitServiceClient) GetPublicKey(ctx context.Context, in *GetTransitPublicKeyRequest, opts ...grpc.CallOption) (*GetTransitPublicKeyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetTransitPublicKeyResponse) + err := c.cc.Invoke(ctx, TransitService_GetPublicKey_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TransitServiceServer is the server API for TransitService service. +// All implementations must embed UnimplementedTransitServiceServer +// for forward compatibility. +// +// TransitService provides typed, authenticated access to transit engine +// operations: symmetric encryption, signing, HMAC, and versioned key +// management. All RPCs require the service to be unsealed. +type TransitServiceServer interface { + // CreateKey creates a new named encryption key. Admin only. + CreateKey(context.Context, *CreateTransitKeyRequest) (*CreateTransitKeyResponse, error) + // DeleteKey permanently removes a named key. Admin only. + // Only succeeds if allow_deletion is true on the key config. + DeleteKey(context.Context, *DeleteTransitKeyRequest) (*DeleteTransitKeyResponse, error) + // GetKey returns metadata for a named key (no raw material). Auth required. + GetKey(context.Context, *GetTransitKeyRequest) (*GetTransitKeyResponse, error) + // ListKeys returns the names of all configured keys. Auth required. + ListKeys(context.Context, *ListTransitKeysRequest) (*ListTransitKeysResponse, error) + // RotateKey creates a new version of the named key. Admin only. + RotateKey(context.Context, *RotateTransitKeyRequest) (*RotateTransitKeyResponse, error) + // UpdateKeyConfig updates key configuration (e.g. min_decryption_version). + // Admin only. + UpdateKeyConfig(context.Context, *UpdateTransitKeyConfigRequest) (*UpdateTransitKeyConfigResponse, error) + // TrimKey deletes versions below min_decryption_version. Admin only. + TrimKey(context.Context, *TrimTransitKeyRequest) (*TrimTransitKeyResponse, error) + // Encrypt encrypts plaintext with the latest key version. Auth required. + Encrypt(context.Context, *TransitEncryptRequest) (*TransitEncryptResponse, error) + // Decrypt decrypts ciphertext. Auth required. + Decrypt(context.Context, *TransitDecryptRequest) (*TransitDecryptResponse, error) + // Rewrap re-encrypts ciphertext with the latest key version without + // exposing plaintext. Auth required. + Rewrap(context.Context, *TransitRewrapRequest) (*TransitRewrapResponse, error) + // BatchEncrypt encrypts multiple items in a single request. Auth required. + BatchEncrypt(context.Context, *TransitBatchEncryptRequest) (*TransitBatchResponse, error) + // BatchDecrypt decrypts multiple items in a single request. Auth required. + BatchDecrypt(context.Context, *TransitBatchDecryptRequest) (*TransitBatchResponse, error) + // BatchRewrap re-encrypts multiple items in a single request. Auth required. + BatchRewrap(context.Context, *TransitBatchRewrapRequest) (*TransitBatchResponse, error) + // Sign signs input data with an asymmetric key. Auth required. + Sign(context.Context, *TransitSignRequest) (*TransitSignResponse, error) + // Verify verifies a signature against input data. Auth required. + Verify(context.Context, *TransitVerifyRequest) (*TransitVerifyResponse, error) + // Hmac computes or verifies an HMAC. Auth required. + Hmac(context.Context, *TransitHmacRequest) (*TransitHmacResponse, error) + // GetPublicKey returns the public key for an asymmetric key. Auth required. + GetPublicKey(context.Context, *GetTransitPublicKeyRequest) (*GetTransitPublicKeyResponse, error) + mustEmbedUnimplementedTransitServiceServer() +} + +// UnimplementedTransitServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedTransitServiceServer struct{} + +func (UnimplementedTransitServiceServer) CreateKey(context.Context, *CreateTransitKeyRequest) (*CreateTransitKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateKey not implemented") +} +func (UnimplementedTransitServiceServer) DeleteKey(context.Context, *DeleteTransitKeyRequest) (*DeleteTransitKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteKey not implemented") +} +func (UnimplementedTransitServiceServer) GetKey(context.Context, *GetTransitKeyRequest) (*GetTransitKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetKey not implemented") +} +func (UnimplementedTransitServiceServer) ListKeys(context.Context, *ListTransitKeysRequest) (*ListTransitKeysResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListKeys not implemented") +} +func (UnimplementedTransitServiceServer) RotateKey(context.Context, *RotateTransitKeyRequest) (*RotateTransitKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RotateKey not implemented") +} +func (UnimplementedTransitServiceServer) UpdateKeyConfig(context.Context, *UpdateTransitKeyConfigRequest) (*UpdateTransitKeyConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateKeyConfig not implemented") +} +func (UnimplementedTransitServiceServer) TrimKey(context.Context, *TrimTransitKeyRequest) (*TrimTransitKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TrimKey not implemented") +} +func (UnimplementedTransitServiceServer) Encrypt(context.Context, *TransitEncryptRequest) (*TransitEncryptResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Encrypt not implemented") +} +func (UnimplementedTransitServiceServer) Decrypt(context.Context, *TransitDecryptRequest) (*TransitDecryptResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Decrypt not implemented") +} +func (UnimplementedTransitServiceServer) Rewrap(context.Context, *TransitRewrapRequest) (*TransitRewrapResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Rewrap not implemented") +} +func (UnimplementedTransitServiceServer) BatchEncrypt(context.Context, *TransitBatchEncryptRequest) (*TransitBatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchEncrypt not implemented") +} +func (UnimplementedTransitServiceServer) BatchDecrypt(context.Context, *TransitBatchDecryptRequest) (*TransitBatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchDecrypt not implemented") +} +func (UnimplementedTransitServiceServer) BatchRewrap(context.Context, *TransitBatchRewrapRequest) (*TransitBatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchRewrap not implemented") +} +func (UnimplementedTransitServiceServer) Sign(context.Context, *TransitSignRequest) (*TransitSignResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Sign not implemented") +} +func (UnimplementedTransitServiceServer) Verify(context.Context, *TransitVerifyRequest) (*TransitVerifyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Verify not implemented") +} +func (UnimplementedTransitServiceServer) Hmac(context.Context, *TransitHmacRequest) (*TransitHmacResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Hmac not implemented") +} +func (UnimplementedTransitServiceServer) GetPublicKey(context.Context, *GetTransitPublicKeyRequest) (*GetTransitPublicKeyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetPublicKey not implemented") +} +func (UnimplementedTransitServiceServer) mustEmbedUnimplementedTransitServiceServer() {} +func (UnimplementedTransitServiceServer) testEmbeddedByValue() {} + +// UnsafeTransitServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TransitServiceServer will +// result in compilation errors. +type UnsafeTransitServiceServer interface { + mustEmbedUnimplementedTransitServiceServer() +} + +func RegisterTransitServiceServer(s grpc.ServiceRegistrar, srv TransitServiceServer) { + // If the following call panics, it indicates UnimplementedTransitServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&TransitService_ServiceDesc, srv) +} + +func _TransitService_CreateKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTransitKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).CreateKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_CreateKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).CreateKey(ctx, req.(*CreateTransitKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_DeleteKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTransitKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).DeleteKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_DeleteKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).DeleteKey(ctx, req.(*DeleteTransitKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_GetKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTransitKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).GetKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_GetKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).GetKey(ctx, req.(*GetTransitKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_ListKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTransitKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).ListKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_ListKeys_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).ListKeys(ctx, req.(*ListTransitKeysRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_RotateKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RotateTransitKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).RotateKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_RotateKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).RotateKey(ctx, req.(*RotateTransitKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_UpdateKeyConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTransitKeyConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).UpdateKeyConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_UpdateKeyConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).UpdateKeyConfig(ctx, req.(*UpdateTransitKeyConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_TrimKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TrimTransitKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).TrimKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_TrimKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).TrimKey(ctx, req.(*TrimTransitKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Encrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitEncryptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Encrypt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Encrypt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Encrypt(ctx, req.(*TransitEncryptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Decrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitDecryptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Decrypt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Decrypt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Decrypt(ctx, req.(*TransitDecryptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Rewrap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitRewrapRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Rewrap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Rewrap_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Rewrap(ctx, req.(*TransitRewrapRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_BatchEncrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitBatchEncryptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).BatchEncrypt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_BatchEncrypt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).BatchEncrypt(ctx, req.(*TransitBatchEncryptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_BatchDecrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitBatchDecryptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).BatchDecrypt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_BatchDecrypt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).BatchDecrypt(ctx, req.(*TransitBatchDecryptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_BatchRewrap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitBatchRewrapRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).BatchRewrap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_BatchRewrap_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).BatchRewrap(ctx, req.(*TransitBatchRewrapRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitSignRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Sign(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Sign_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Sign(ctx, req.(*TransitSignRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Verify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitVerifyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Verify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Verify_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Verify(ctx, req.(*TransitVerifyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_Hmac_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransitHmacRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).Hmac(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_Hmac_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).Hmac(ctx, req.(*TransitHmacRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TransitService_GetPublicKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTransitPublicKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TransitServiceServer).GetPublicKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TransitService_GetPublicKey_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TransitServiceServer).GetPublicKey(ctx, req.(*GetTransitPublicKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// TransitService_ServiceDesc is the grpc.ServiceDesc for TransitService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TransitService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "metacrypt.v2.TransitService", + HandlerType: (*TransitServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateKey", + Handler: _TransitService_CreateKey_Handler, + }, + { + MethodName: "DeleteKey", + Handler: _TransitService_DeleteKey_Handler, + }, + { + MethodName: "GetKey", + Handler: _TransitService_GetKey_Handler, + }, + { + MethodName: "ListKeys", + Handler: _TransitService_ListKeys_Handler, + }, + { + MethodName: "RotateKey", + Handler: _TransitService_RotateKey_Handler, + }, + { + MethodName: "UpdateKeyConfig", + Handler: _TransitService_UpdateKeyConfig_Handler, + }, + { + MethodName: "TrimKey", + Handler: _TransitService_TrimKey_Handler, + }, + { + MethodName: "Encrypt", + Handler: _TransitService_Encrypt_Handler, + }, + { + MethodName: "Decrypt", + Handler: _TransitService_Decrypt_Handler, + }, + { + MethodName: "Rewrap", + Handler: _TransitService_Rewrap_Handler, + }, + { + MethodName: "BatchEncrypt", + Handler: _TransitService_BatchEncrypt_Handler, + }, + { + MethodName: "BatchDecrypt", + Handler: _TransitService_BatchDecrypt_Handler, + }, + { + MethodName: "BatchRewrap", + Handler: _TransitService_BatchRewrap_Handler, + }, + { + MethodName: "Sign", + Handler: _TransitService_Sign_Handler, + }, + { + MethodName: "Verify", + Handler: _TransitService_Verify_Handler, + }, + { + MethodName: "Hmac", + Handler: _TransitService_Hmac_Handler, + }, + { + MethodName: "GetPublicKey", + Handler: _TransitService_GetPublicKey_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/metacrypt/v2/transit.proto", +} diff --git a/internal/engine/transit/transit.go b/internal/engine/transit/transit.go new file mode 100644 index 0000000..415d6ca --- /dev/null +++ b/internal/engine/transit/transit.go @@ -0,0 +1,1602 @@ +// Package transit implements the transit encryption engine for symmetric +// encryption, signing, and HMAC operations with versioned key management. +package transit + +import ( + "context" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "hash" + "sort" + "strconv" + "strings" + "sync" + + "golang.org/x/crypto/chacha20poly1305" + + "git.wntrmute.dev/kyle/metacrypt/internal/barrier" + mcrypto "git.wntrmute.dev/kyle/metacrypt/internal/crypto" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" +) + +const maxBatchSize = 500 + +var ( + ErrSealed = errors.New("transit: engine is sealed") + ErrKeyNotFound = errors.New("transit: key not found") + ErrKeyExists = errors.New("transit: key already exists") + ErrForbidden = errors.New("transit: forbidden") + ErrUnauthorized = errors.New("transit: authentication required") + ErrDeletionDenied = errors.New("transit: deletion not allowed") + ErrInvalidKeyType = errors.New("transit: invalid key type") + ErrUnsupportedOp = errors.New("transit: unsupported operation for key type") + ErrDecryptVersion = errors.New("transit: ciphertext version below minimum decryption version") + ErrInvalidFormat = errors.New("transit: invalid ciphertext format") + ErrBatchTooLarge = errors.New("transit: batch size exceeds maximum") + ErrInvalidMinVer = errors.New("transit: min_decryption_version can only increase and cannot exceed current version") +) + +// keyVersion holds a single version of key material. +type keyVersion struct { + version int + key []byte // symmetric key material + privKey crypto.PrivateKey // asymmetric (nil for symmetric) + pubKey crypto.PublicKey // asymmetric (nil for symmetric) +} + +// keyState holds in-memory state for a loaded key. +type keyState struct { + config *KeyConfig + versions map[int]*keyVersion +} + +// TransitEngine implements the transit encryption engine. +type TransitEngine struct { + barrier barrier.Barrier + config *TransitConfig + keys map[string]*keyState + mountPath string + mu sync.RWMutex +} + +// NewTransitEngine creates a new transit engine instance. +func NewTransitEngine() engine.Engine { + return &TransitEngine{ + keys: make(map[string]*keyState), + } +} + +func (e *TransitEngine) Type() engine.EngineType { + return engine.EngineTypeTransit +} + +// Initialize sets up the transit engine for first use. +func (e *TransitEngine) Initialize(ctx context.Context, b barrier.Barrier, mountPath string, config map[string]interface{}) error { + e.mu.Lock() + defer e.mu.Unlock() + + e.barrier = b + e.mountPath = mountPath + + cfg := &TransitConfig{} + if config != nil { + if v, ok := config["max_key_versions"]; ok { + switch val := v.(type) { + case float64: + cfg.MaxKeyVersions = int(val) + case int: + cfg.MaxKeyVersions = val + } + } + } + e.config = cfg + + configData, err := json.Marshal(cfg) + if err != nil { + return fmt.Errorf("transit: marshal config: %w", err) + } + if err := b.Put(ctx, mountPath+"config.json", configData); err != nil { + return fmt.Errorf("transit: store config: %w", err) + } + + e.keys = make(map[string]*keyState) + return nil +} + +// Unseal loads the transit state from the barrier into memory. +func (e *TransitEngine) Unseal(ctx context.Context, b barrier.Barrier, mountPath string) error { + e.mu.Lock() + defer e.mu.Unlock() + + e.barrier = b + e.mountPath = mountPath + + // Load config. + configData, err := b.Get(ctx, mountPath+"config.json") + if err != nil { + return fmt.Errorf("transit: load config: %w", err) + } + var cfg TransitConfig + if err := json.Unmarshal(configData, &cfg); err != nil { + return fmt.Errorf("transit: parse config: %w", err) + } + e.config = &cfg + e.keys = make(map[string]*keyState) + + // Load all keys. + keyPaths, err := b.List(ctx, mountPath+"keys/") + if err != nil { + return nil // no keys yet + } + + // Collect unique key names from paths like "mykey/config.json", "mykey/v1.key". + keyNames := make(map[string]bool) + for _, p := range keyPaths { + parts := strings.SplitN(p, "/", 2) + if len(parts) > 0 && parts[0] != "" { + keyNames[parts[0]] = true + } + } + + for name := range keyNames { + ks, err := e.loadKey(ctx, b, mountPath, name) + if err != nil { + return fmt.Errorf("transit: load key %q: %w", name, err) + } + e.keys[name] = ks + } + + return nil +} + +func (e *TransitEngine) loadKey(ctx context.Context, b barrier.Barrier, mountPath, name string) (*keyState, error) { + prefix := mountPath + "keys/" + name + "/" + + configData, err := b.Get(ctx, prefix+"config.json") + if err != nil { + return nil, fmt.Errorf("load config: %w", err) + } + var cfg KeyConfig + if err := json.Unmarshal(configData, &cfg); err != nil { + return nil, fmt.Errorf("parse config: %w", err) + } + + ks := &keyState{ + config: &cfg, + versions: make(map[int]*keyVersion), + } + + // Load all versions. + for v := 1; v <= cfg.CurrentVersion; v++ { + kv, err := e.loadKeyVersion(ctx, b, prefix, &cfg, v) + if err != nil { + // Version may have been trimmed; skip. + continue + } + ks.versions[v] = kv + } + + return ks, nil +} + +func (e *TransitEngine) loadKeyVersion(ctx context.Context, b barrier.Barrier, prefix string, cfg *KeyConfig, version int) (*keyVersion, error) { + path := fmt.Sprintf("%sv%d.key", prefix, version) + data, err := b.Get(ctx, path) + if err != nil { + return nil, err + } + + kv := &keyVersion{version: version} + + switch cfg.Type { + case "aes256-gcm", "chacha20-poly", "hmac-sha256", "hmac-sha512": + kv.key = data + case "ed25519": + privKey := ed25519.PrivateKey(data) + kv.key = data + kv.privKey = privKey + kv.pubKey = privKey.Public() + case "ecdsa-p256", "ecdsa-p384": + privKey, err := x509.ParsePKCS8PrivateKey(data) + if err != nil { + return nil, fmt.Errorf("parse PKCS8 key: %w", err) + } + ecKey, ok := privKey.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("expected ECDSA key, got %T", privKey) + } + kv.privKey = ecKey + kv.pubKey = &ecKey.PublicKey + default: + return nil, fmt.Errorf("unknown key type: %s", cfg.Type) + } + + return kv, nil +} + +// Seal zeroizes all in-memory key material. +func (e *TransitEngine) Seal() error { + e.mu.Lock() + defer e.mu.Unlock() + + for name, ks := range e.keys { + for _, kv := range ks.versions { + if kv.key != nil { + mcrypto.Zeroize(kv.key) + } + zeroizeKey(kv.privKey) + } + delete(e.keys, name) + } + e.keys = nil + e.config = nil + + return nil +} + +// HandleRequest dispatches transit operations. +func (e *TransitEngine) HandleRequest(ctx context.Context, req *engine.Request) (*engine.Response, error) { + switch req.Operation { + case "create-key": + return e.handleCreateKey(ctx, req) + case "delete-key": + return e.handleDeleteKey(ctx, req) + case "get-key": + return e.handleGetKey(ctx, req) + case "list-keys": + return e.handleListKeys(ctx, req) + case "rotate-key": + return e.handleRotateKey(ctx, req) + case "update-key-config": + return e.handleUpdateKeyConfig(ctx, req) + case "trim-key": + return e.handleTrimKey(ctx, req) + case "encrypt": + return e.handleEncrypt(ctx, req) + case "decrypt": + return e.handleDecrypt(ctx, req) + case "rewrap": + return e.handleRewrap(ctx, req) + case "batch-encrypt": + return e.handleBatchEncrypt(ctx, req) + case "batch-decrypt": + return e.handleBatchDecrypt(ctx, req) + case "batch-rewrap": + return e.handleBatchRewrap(ctx, req) + case "sign": + return e.handleSign(ctx, req) + case "verify": + return e.handleVerify(ctx, req) + case "hmac": + return e.handleHMAC(ctx, req) + case "get-public-key": + return e.handleGetPublicKey(ctx, req) + default: + return nil, fmt.Errorf("transit: unknown operation: %s", req.Operation) + } +} + +// --- Authorization helpers --- + +func (e *TransitEngine) requireAdmin(req *engine.Request) error { + if req.CallerInfo == nil { + return ErrUnauthorized + } + if !req.CallerInfo.IsAdmin { + return ErrForbidden + } + return nil +} + +func (e *TransitEngine) requireUser(req *engine.Request) error { + if req.CallerInfo == nil { + return ErrUnauthorized + } + if !req.CallerInfo.IsUser() { + return ErrForbidden + } + return nil +} + +func (e *TransitEngine) requireUserWithPolicy(req *engine.Request, keyName string) error { + if req.CallerInfo == nil { + return ErrUnauthorized + } + if req.CallerInfo.IsAdmin { + return nil + } + if !req.CallerInfo.IsUser() { + return ErrForbidden + } + + // Check policy for the specific key. + if req.CheckPolicy != nil { + resource := fmt.Sprintf("transit/%s/key/%s", e.mountName(), keyName) + action := operationToAction(req.Operation) + effect, matched := req.CheckPolicy(resource, action) + if matched { + if effect == "allow" { + return nil + } + return ErrForbidden + } + } + + // Default: users can access transit operations without explicit policy. + return nil +} + +func operationToAction(op string) string { + switch op { + case "get-key", "list-keys", "get-public-key": + return "read" + case "decrypt", "rewrap", "batch-decrypt", "batch-rewrap": + return "decrypt" + default: + return "write" + } +} + +// mountName extracts the user-facing mount name from the mount path. +func (e *TransitEngine) mountName() string { + parts := strings.Split(strings.TrimSuffix(e.mountPath, "/"), "/") + if len(parts) >= 3 { + return parts[2] + } + return "" +} + +func (e *TransitEngine) sealed() bool { + return e.config == nil +} + +// --- Key Management Operations --- + +func (e *TransitEngine) handleCreateKey(ctx context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireAdmin(req); err != nil { + return nil, err + } + + e.mu.Lock() + defer e.mu.Unlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + keyType, _ := req.Data["type"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + if keyType == "" { + keyType = "aes256-gcm" + } + + if !isValidKeyType(keyType) { + return nil, ErrInvalidKeyType + } + + if _, exists := e.keys[name]; exists { + return nil, ErrKeyExists + } + + // Generate key version 1. + kv, err := generateKeyVersion(keyType, 1) + if err != nil { + return nil, fmt.Errorf("transit: generate key: %w", err) + } + + cfg := &KeyConfig{ + Name: name, + Type: keyType, + CurrentVersion: 1, + MinDecryptionVersion: 1, + AllowDeletion: false, + } + + // Store config and key. + prefix := e.mountPath + "keys/" + name + "/" + if err := e.storeKeyConfig(ctx, prefix, cfg); err != nil { + return nil, err + } + if err := e.storeKeyVersion(ctx, prefix, cfg, kv); err != nil { + return nil, err + } + + e.keys[name] = &keyState{ + config: cfg, + versions: map[int]*keyVersion{1: kv}, + } + + return &engine.Response{ + Data: map[string]interface{}{ + "name": name, + "type": keyType, + "version": 1, + }, + }, nil +} + +func (e *TransitEngine) handleDeleteKey(ctx context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireAdmin(req); err != nil { + return nil, err + } + + e.mu.Lock() + defer e.mu.Unlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + if !ks.config.AllowDeletion { + return nil, ErrDeletionDenied + } + + // Delete all versions and config from barrier. + prefix := e.mountPath + "keys/" + name + "/" + for v := range ks.versions { + path := fmt.Sprintf("%sv%d.key", prefix, v) + _ = e.barrier.Delete(ctx, path) + } + _ = e.barrier.Delete(ctx, prefix+"config.json") + + // Zeroize in-memory material. + for _, kv := range ks.versions { + if kv.key != nil { + mcrypto.Zeroize(kv.key) + } + zeroizeKey(kv.privKey) + } + delete(e.keys, name) + + return &engine.Response{ + Data: map[string]interface{}{"ok": true}, + }, nil +} + +func (e *TransitEngine) handleGetKey(_ context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireUser(req); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + versions := make([]int, 0, len(ks.versions)) + for v := range ks.versions { + versions = append(versions, v) + } + sort.Ints(versions) + + return &engine.Response{ + Data: map[string]interface{}{ + "name": ks.config.Name, + "type": ks.config.Type, + "current_version": ks.config.CurrentVersion, + "min_decryption_version": ks.config.MinDecryptionVersion, + "allow_deletion": ks.config.AllowDeletion, + "versions": versions, + }, + }, nil +} + +func (e *TransitEngine) handleListKeys(_ context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireUser(req); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + keys := make([]string, 0, len(e.keys)) + for name := range e.keys { + keys = append(keys, name) + } + sort.Strings(keys) + + return &engine.Response{ + Data: map[string]interface{}{"keys": keys}, + }, nil +} + +func (e *TransitEngine) handleRotateKey(ctx context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireAdmin(req); err != nil { + return nil, err + } + + e.mu.Lock() + defer e.mu.Unlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + newVersion := ks.config.CurrentVersion + 1 + kv, err := generateKeyVersion(ks.config.Type, newVersion) + if err != nil { + return nil, fmt.Errorf("transit: generate key version: %w", err) + } + + ks.config.CurrentVersion = newVersion + prefix := e.mountPath + "keys/" + name + "/" + if err := e.storeKeyConfig(ctx, prefix, ks.config); err != nil { + return nil, err + } + if err := e.storeKeyVersion(ctx, prefix, ks.config, kv); err != nil { + return nil, err + } + + ks.versions[newVersion] = kv + + // Prune old versions if max_key_versions is set. + if e.config.MaxKeyVersions > 0 && len(ks.versions) > e.config.MaxKeyVersions { + e.pruneVersions(ctx, ks, prefix) + } + + return &engine.Response{ + Data: map[string]interface{}{ + "name": name, + "version": newVersion, + }, + }, nil +} + +func (e *TransitEngine) pruneVersions(ctx context.Context, ks *keyState, prefix string) { + versions := make([]int, 0, len(ks.versions)) + for v := range ks.versions { + versions = append(versions, v) + } + sort.Ints(versions) + + for len(versions) > e.config.MaxKeyVersions { + v := versions[0] + if v >= ks.config.MinDecryptionVersion { + break + } + path := fmt.Sprintf("%sv%d.key", prefix, v) + _ = e.barrier.Delete(ctx, path) + if kv, ok := ks.versions[v]; ok { + if kv.key != nil { + mcrypto.Zeroize(kv.key) + } + zeroizeKey(kv.privKey) + } + delete(ks.versions, v) + versions = versions[1:] + } +} + +func (e *TransitEngine) handleUpdateKeyConfig(ctx context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireAdmin(req); err != nil { + return nil, err + } + + e.mu.Lock() + defer e.mu.Unlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + if v, ok := req.Data["min_decryption_version"]; ok { + newMin := toInt(v) + if newMin < ks.config.MinDecryptionVersion || newMin > ks.config.CurrentVersion { + return nil, ErrInvalidMinVer + } + ks.config.MinDecryptionVersion = newMin + } + + if v, ok := req.Data["allow_deletion"]; ok { + if b, ok := v.(bool); ok { + ks.config.AllowDeletion = b + } + } + + prefix := e.mountPath + "keys/" + name + "/" + if err := e.storeKeyConfig(ctx, prefix, ks.config); err != nil { + return nil, err + } + + return &engine.Response{ + Data: map[string]interface{}{"ok": true}, + }, nil +} + +func (e *TransitEngine) handleTrimKey(ctx context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireAdmin(req); err != nil { + return nil, err + } + + e.mu.Lock() + defer e.mu.Unlock() + + if e.sealed() { + return nil, ErrSealed + } + + name, _ := req.Data["name"].(string) + if name == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + + prefix := e.mountPath + "keys/" + name + "/" + trimmed := 0 + for v, kv := range ks.versions { + if v < ks.config.MinDecryptionVersion { + path := fmt.Sprintf("%sv%d.key", prefix, v) + _ = e.barrier.Delete(ctx, path) + if kv.key != nil { + mcrypto.Zeroize(kv.key) + } + zeroizeKey(kv.privKey) + delete(ks.versions, v) + trimmed++ + } + } + + return &engine.Response{ + Data: map[string]interface{}{ + "trimmed": trimmed, + }, + }, nil +} + +// --- Crypto Operations --- + +func (e *TransitEngine) handleEncrypt(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + plaintextB64, _ := req.Data["plaintext"].(string) + contextB64, _ := req.Data["context"].(string) + + ciphertext, err := e.encryptWithKey(keyName, plaintextB64, contextB64) + if err != nil { + return nil, err + } + + return &engine.Response{ + Data: map[string]interface{}{"ciphertext": ciphertext}, + }, nil +} + +func (e *TransitEngine) encryptWithKey(keyName, plaintextB64, contextB64 string) (string, error) { + ks, ok := e.keys[keyName] + if !ok { + return "", ErrKeyNotFound + } + + if !isSymmetric(ks.config.Type) { + return "", ErrUnsupportedOp + } + + plaintext, err := base64.StdEncoding.DecodeString(plaintextB64) + if err != nil { + return "", fmt.Errorf("transit: invalid base64 plaintext: %w", err) + } + + var aad []byte + if contextB64 != "" { + aad, err = base64.StdEncoding.DecodeString(contextB64) + if err != nil { + return "", fmt.Errorf("transit: invalid base64 context: %w", err) + } + } + + currentVersion := ks.config.CurrentVersion + kv, ok := ks.versions[currentVersion] + if !ok { + return "", fmt.Errorf("transit: current key version %d not found", currentVersion) + } + + encrypted, err := encryptData(ks.config.Type, kv.key, plaintext, aad) + if err != nil { + return "", err + } + + return formatCiphertext(currentVersion, encrypted), nil +} + +func (e *TransitEngine) handleDecrypt(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + ciphertextStr, _ := req.Data["ciphertext"].(string) + contextB64, _ := req.Data["context"].(string) + + plaintext, err := e.decryptWithKey(keyName, ciphertextStr, contextB64) + if err != nil { + return nil, err + } + + return &engine.Response{ + Data: map[string]interface{}{"plaintext": base64.StdEncoding.EncodeToString(plaintext)}, + }, nil +} + +func (e *TransitEngine) decryptWithKey(keyName, ciphertextStr, contextB64 string) ([]byte, error) { + ks, ok := e.keys[keyName] + if !ok { + return nil, ErrKeyNotFound + } + + if !isSymmetric(ks.config.Type) { + return nil, ErrUnsupportedOp + } + + version, data, err := parseCiphertext(ciphertextStr) + if err != nil { + return nil, err + } + + if version < ks.config.MinDecryptionVersion { + return nil, ErrDecryptVersion + } + + kv, ok := ks.versions[version] + if !ok { + return nil, fmt.Errorf("transit: key version %d not found", version) + } + + var aad []byte + if contextB64 != "" { + aad, err = base64.StdEncoding.DecodeString(contextB64) + if err != nil { + return nil, fmt.Errorf("transit: invalid base64 context: %w", err) + } + } + + return decryptData(ks.config.Type, kv.key, data, aad) +} + +func (e *TransitEngine) handleRewrap(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + ciphertextStr, _ := req.Data["ciphertext"].(string) + contextB64, _ := req.Data["context"].(string) + + // Decrypt with old version. + plaintext, err := e.decryptWithKey(keyName, ciphertextStr, contextB64) + if err != nil { + return nil, err + } + + // Re-encrypt with latest version (reuse the decoded plaintext as raw bytes). + plaintextB64 := base64.StdEncoding.EncodeToString(plaintext) + newCiphertext, err := e.encryptWithKey(keyName, plaintextB64, contextB64) + if err != nil { + return nil, err + } + + return &engine.Response{ + Data: map[string]interface{}{"ciphertext": newCiphertext}, + }, nil +} + +// --- Batch Operations --- + +type batchItem struct { + Plaintext string `json:"plaintext"` + Ciphertext string `json:"ciphertext"` + Context string `json:"context"` + Reference string `json:"reference"` +} + +type batchResult struct { + Plaintext string `json:"plaintext,omitempty"` + Ciphertext string `json:"ciphertext,omitempty"` + Reference string `json:"reference,omitempty"` + Error string `json:"error,omitempty"` +} + +func (e *TransitEngine) handleBatchEncrypt(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + items, err := extractBatchItems(req.Data["items"]) + if err != nil { + return nil, err + } + if len(items) > maxBatchSize { + return nil, ErrBatchTooLarge + } + + results := make([]interface{}, len(items)) + for i, item := range items { + ct, err := e.encryptWithKey(keyName, item.Plaintext, item.Context) + r := batchResult{Reference: item.Reference} + if err != nil { + r.Error = err.Error() + } else { + r.Ciphertext = ct + } + results[i] = r + } + + return &engine.Response{ + Data: map[string]interface{}{"results": results}, + }, nil +} + +func (e *TransitEngine) handleBatchDecrypt(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + items, err := extractBatchItems(req.Data["items"]) + if err != nil { + return nil, err + } + if len(items) > maxBatchSize { + return nil, ErrBatchTooLarge + } + + results := make([]interface{}, len(items)) + for i, item := range items { + pt, err := e.decryptWithKey(keyName, item.Ciphertext, item.Context) + r := batchResult{Reference: item.Reference} + if err != nil { + r.Error = err.Error() + } else { + r.Plaintext = base64.StdEncoding.EncodeToString(pt) + } + results[i] = r + } + + return &engine.Response{ + Data: map[string]interface{}{"results": results}, + }, nil +} + +func (e *TransitEngine) handleBatchRewrap(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + items, err := extractBatchItems(req.Data["items"]) + if err != nil { + return nil, err + } + if len(items) > maxBatchSize { + return nil, ErrBatchTooLarge + } + + results := make([]interface{}, len(items)) + for i, item := range items { + r := batchResult{Reference: item.Reference} + // Decrypt with old version. + pt, err := e.decryptWithKey(keyName, item.Ciphertext, item.Context) + if err != nil { + r.Error = err.Error() + results[i] = r + continue + } + // Re-encrypt with latest version. + ptB64 := base64.StdEncoding.EncodeToString(pt) + ct, err := e.encryptWithKey(keyName, ptB64, item.Context) + if err != nil { + r.Error = err.Error() + } else { + r.Ciphertext = ct + } + results[i] = r + } + + return &engine.Response{ + Data: map[string]interface{}{"results": results}, + }, nil +} + +// --- Sign/Verify Operations --- + +func (e *TransitEngine) handleSign(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + ks, ok := e.keys[keyName] + if !ok { + return nil, ErrKeyNotFound + } + + if !isAsymmetric(ks.config.Type) { + return nil, ErrUnsupportedOp + } + + inputB64, _ := req.Data["input"].(string) + input, err := base64.StdEncoding.DecodeString(inputB64) + if err != nil { + return nil, fmt.Errorf("transit: invalid base64 input: %w", err) + } + + currentVersion := ks.config.CurrentVersion + kv, ok := ks.versions[currentVersion] + if !ok { + return nil, fmt.Errorf("transit: current key version %d not found", currentVersion) + } + + var sig []byte + switch ks.config.Type { + case "ed25519": + edKey, ok := kv.privKey.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("transit: expected ed25519 key") + } + sig = ed25519.Sign(edKey, input) + case "ecdsa-p256": + ecKey, ok := kv.privKey.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("transit: expected ECDSA key") + } + h := sha256.Sum256(input) + sig, err = ecdsa.SignASN1(rand.Reader, ecKey, h[:]) + if err != nil { + return nil, fmt.Errorf("transit: sign: %w", err) + } + case "ecdsa-p384": + ecKey, ok := kv.privKey.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("transit: expected ECDSA key") + } + h := sha512.Sum384(input) + sig, err = ecdsa.SignASN1(rand.Reader, ecKey, h[:]) + if err != nil { + return nil, fmt.Errorf("transit: sign: %w", err) + } + default: + return nil, ErrUnsupportedOp + } + + return &engine.Response{ + Data: map[string]interface{}{ + "signature": formatSignature(currentVersion, sig), + }, + }, nil +} + +func (e *TransitEngine) handleVerify(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + ks, ok := e.keys[keyName] + if !ok { + return nil, ErrKeyNotFound + } + + if !isAsymmetric(ks.config.Type) { + return nil, ErrUnsupportedOp + } + + inputB64, _ := req.Data["input"].(string) + input, err := base64.StdEncoding.DecodeString(inputB64) + if err != nil { + return nil, fmt.Errorf("transit: invalid base64 input: %w", err) + } + + signatureStr, _ := req.Data["signature"].(string) + version, sigBytes, err := parseVersionedData(signatureStr) + if err != nil { + return nil, fmt.Errorf("transit: invalid signature format: %w", err) + } + + kv, ok := ks.versions[version] + if !ok { + return nil, fmt.Errorf("transit: key version %d not found", version) + } + + valid := false + switch ks.config.Type { + case "ed25519": + edPub, ok := kv.pubKey.(ed25519.PublicKey) + if !ok { + return nil, fmt.Errorf("transit: expected ed25519 public key") + } + valid = ed25519.Verify(edPub, input, sigBytes) + case "ecdsa-p256": + ecPub, ok := kv.pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("transit: expected ECDSA public key") + } + h := sha256.Sum256(input) + valid = ecdsa.VerifyASN1(ecPub, h[:], sigBytes) + case "ecdsa-p384": + ecPub, ok := kv.pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("transit: expected ECDSA public key") + } + h := sha512.Sum384(input) + valid = ecdsa.VerifyASN1(ecPub, h[:], sigBytes) + default: + return nil, ErrUnsupportedOp + } + + return &engine.Response{ + Data: map[string]interface{}{"valid": valid}, + }, nil +} + +// --- HMAC Operation --- + +func (e *TransitEngine) handleHMAC(_ context.Context, req *engine.Request) (*engine.Response, error) { + keyName, _ := req.Data["key"].(string) + if keyName == "" { + keyName, _ = req.Data["name"].(string) + } + if err := e.requireUserWithPolicy(req, keyName); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + if keyName == "" { + return nil, fmt.Errorf("transit: key name is required") + } + + ks, ok := e.keys[keyName] + if !ok { + return nil, ErrKeyNotFound + } + + if !isHMAC(ks.config.Type) { + return nil, ErrUnsupportedOp + } + + inputB64, _ := req.Data["input"].(string) + input, err := base64.StdEncoding.DecodeString(inputB64) + if err != nil { + return nil, fmt.Errorf("transit: invalid base64 input: %w", err) + } + + // Verify mode: if hmac is provided, verify it. + if hmacStr, ok := req.Data["hmac"].(string); ok && hmacStr != "" { + version, macBytes, err := parseVersionedData(hmacStr) + if err != nil { + return nil, fmt.Errorf("transit: invalid hmac format: %w", err) + } + + kv, ok := ks.versions[version] + if !ok { + return nil, fmt.Errorf("transit: key version %d not found", version) + } + + expected := computeHMAC(ks.config.Type, kv.key, input) + valid := hmac.Equal(macBytes, expected) + + return &engine.Response{ + Data: map[string]interface{}{"valid": valid}, + }, nil + } + + // Compute mode. + currentVersion := ks.config.CurrentVersion + kv, ok := ks.versions[currentVersion] + if !ok { + return nil, fmt.Errorf("transit: current key version %d not found", currentVersion) + } + + mac := computeHMAC(ks.config.Type, kv.key, input) + return &engine.Response{ + Data: map[string]interface{}{ + "hmac": formatHMAC(currentVersion, mac), + }, + }, nil +} + +// --- Get Public Key --- + +func (e *TransitEngine) handleGetPublicKey(_ context.Context, req *engine.Request) (*engine.Response, error) { + if err := e.requireUser(req); err != nil { + return nil, err + } + + e.mu.RLock() + defer e.mu.RUnlock() + + if e.sealed() { + return nil, ErrSealed + } + + keyName, _ := req.Data["name"].(string) + if keyName == "" { + return nil, fmt.Errorf("transit: name is required") + } + + ks, ok := e.keys[keyName] + if !ok { + return nil, ErrKeyNotFound + } + + if !isAsymmetric(ks.config.Type) { + return nil, ErrUnsupportedOp + } + + version := ks.config.CurrentVersion + if v, ok := req.Data["version"]; ok { + version = toInt(v) + } + + kv, ok := ks.versions[version] + if !ok { + return nil, fmt.Errorf("transit: key version %d not found", version) + } + + pubKeyBytes, err := x509.MarshalPKIXPublicKey(kv.pubKey) + if err != nil { + return nil, fmt.Errorf("transit: marshal public key: %w", err) + } + + return &engine.Response{ + Data: map[string]interface{}{ + "public_key": base64.StdEncoding.EncodeToString(pubKeyBytes), + "version": version, + "type": ks.config.Type, + }, + }, nil +} + +// --- Storage helpers --- + +func (e *TransitEngine) storeKeyConfig(ctx context.Context, prefix string, cfg *KeyConfig) error { + data, err := json.Marshal(cfg) + if err != nil { + return fmt.Errorf("transit: marshal key config: %w", err) + } + return e.barrier.Put(ctx, prefix+"config.json", data) +} + +func (e *TransitEngine) storeKeyVersion(ctx context.Context, prefix string, cfg *KeyConfig, kv *keyVersion) error { + path := fmt.Sprintf("%sv%d.key", prefix, kv.version) + + var data []byte + switch cfg.Type { + case "aes256-gcm", "chacha20-poly", "hmac-sha256", "hmac-sha512": + data = kv.key + case "ed25519": + data = kv.key // raw 64-byte private key + case "ecdsa-p256", "ecdsa-p384": + var err error + data, err = x509.MarshalPKCS8PrivateKey(kv.privKey) + if err != nil { + return fmt.Errorf("transit: marshal PKCS8 key: %w", err) + } + default: + return fmt.Errorf("transit: unknown key type: %s", cfg.Type) + } + + return e.barrier.Put(ctx, path, data) +} + +// --- Key generation --- + +func generateKeyVersion(keyType string, version int) (*keyVersion, error) { + kv := &keyVersion{version: version} + + switch keyType { + case "aes256-gcm": + key := make([]byte, 32) + if _, err := rand.Read(key); err != nil { + return nil, err + } + kv.key = key + case "chacha20-poly": + key := make([]byte, 32) + if _, err := rand.Read(key); err != nil { + return nil, err + } + kv.key = key + case "ed25519": + _, privKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + kv.key = []byte(privKey) + kv.privKey = privKey + kv.pubKey = privKey.Public() + case "ecdsa-p256": + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + kv.privKey = privKey + kv.pubKey = &privKey.PublicKey + case "ecdsa-p384": + privKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, err + } + kv.privKey = privKey + kv.pubKey = &privKey.PublicKey + case "hmac-sha256": + key := make([]byte, 32) + if _, err := rand.Read(key); err != nil { + return nil, err + } + kv.key = key + case "hmac-sha512": + key := make([]byte, 64) + if _, err := rand.Read(key); err != nil { + return nil, err + } + kv.key = key + default: + return nil, fmt.Errorf("unknown key type: %s", keyType) + } + + return kv, nil +} + +// --- Encryption/Decryption helpers --- + +func encryptData(keyType string, key, plaintext, aad []byte) ([]byte, error) { + aead, err := newAEAD(keyType, key) + if err != nil { + return nil, err + } + + nonce := make([]byte, aead.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + return nil, fmt.Errorf("transit: generate nonce: %w", err) + } + + ciphertext := aead.Seal(nil, nonce, plaintext, aad) + + // Format: nonce + ciphertext (includes tag) + result := make([]byte, len(nonce)+len(ciphertext)) + copy(result, nonce) + copy(result[len(nonce):], ciphertext) + return result, nil +} + +func decryptData(keyType string, key, data, aad []byte) ([]byte, error) { + aead, err := newAEAD(keyType, key) + if err != nil { + return nil, err + } + + nonceSize := aead.NonceSize() + if len(data) < nonceSize { + return nil, ErrInvalidFormat + } + + nonce := data[:nonceSize] + ciphertext := data[nonceSize:] + + plaintext, err := aead.Open(nil, nonce, ciphertext, aad) + if err != nil { + return nil, fmt.Errorf("transit: decryption failed: %w", err) + } + return plaintext, nil +} + +func newAEAD(keyType string, key []byte) (cipher.AEAD, error) { + switch keyType { + case "aes256-gcm": + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("transit: new AES cipher: %w", err) + } + return cipher.NewGCM(block) + case "chacha20-poly": + return chacha20poly1305.NewX(key) + default: + return nil, fmt.Errorf("transit: unsupported encryption type: %s", keyType) + } +} + +// --- HMAC helpers --- + +func computeHMAC(keyType string, key, input []byte) []byte { + var h func() hash.Hash + switch keyType { + case "hmac-sha256": + h = sha256.New + case "hmac-sha512": + h = sha512.New + default: + return nil + } + mac := hmac.New(h, key) + mac.Write(input) + return mac.Sum(nil) +} + +// --- Format helpers --- + +func formatCiphertext(version int, data []byte) string { + return fmt.Sprintf("metacrypt:v%d:%s", version, base64.StdEncoding.EncodeToString(data)) +} + +func formatSignature(version int, sig []byte) string { + return fmt.Sprintf("metacrypt:v%d:%s", version, base64.StdEncoding.EncodeToString(sig)) +} + +func formatHMAC(version int, mac []byte) string { + return fmt.Sprintf("metacrypt:v%d:%s", version, base64.StdEncoding.EncodeToString(mac)) +} + +func parseCiphertext(s string) (int, []byte, error) { + return parseVersionedData(s) +} + +func parseVersionedData(s string) (int, []byte, error) { + parts := strings.SplitN(s, ":", 3) + if len(parts) != 3 || parts[0] != "metacrypt" { + return 0, nil, ErrInvalidFormat + } + + if !strings.HasPrefix(parts[1], "v") { + return 0, nil, ErrInvalidFormat + } + + version, err := strconv.Atoi(parts[1][1:]) + if err != nil { + return 0, nil, ErrInvalidFormat + } + + data, err := base64.StdEncoding.DecodeString(parts[2]) + if err != nil { + return 0, nil, fmt.Errorf("transit: invalid base64: %w", err) + } + + return version, data, nil +} + +// --- Type helpers --- + +func isValidKeyType(t string) bool { + switch t { + case "aes256-gcm", "chacha20-poly", "ed25519", "ecdsa-p256", "ecdsa-p384", "hmac-sha256", "hmac-sha512": + return true + } + return false +} + +func isSymmetric(t string) bool { + return t == "aes256-gcm" || t == "chacha20-poly" +} + +func isAsymmetric(t string) bool { + return t == "ed25519" || t == "ecdsa-p256" || t == "ecdsa-p384" +} + +func isHMAC(t string) bool { + return t == "hmac-sha256" || t == "hmac-sha512" +} + +// --- Utility --- + +func toInt(v interface{}) int { + switch val := v.(type) { + case float64: + return int(val) + case int: + return val + case int64: + return int(val) + case json.Number: + n, _ := val.Int64() + return int(n) + } + return 0 +} + +func extractBatchItems(v interface{}) ([]batchItem, error) { + raw, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("transit: items must be an array") + } + + items := make([]batchItem, len(raw)) + for i, r := range raw { + m, ok := r.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("transit: item %d is not an object", i) + } + items[i].Plaintext, _ = m["plaintext"].(string) + items[i].Ciphertext, _ = m["ciphertext"].(string) + items[i].Context, _ = m["context"].(string) + items[i].Reference, _ = m["reference"].(string) + } + return items, nil +} + +func zeroizeKey(key crypto.PrivateKey) { + if key == nil { + return + } + switch k := key.(type) { + case *ecdsa.PrivateKey: + k.D.SetInt64(0) + case ed25519.PrivateKey: + for i := range k { + k[i] = 0 + } + } +} diff --git a/internal/engine/transit/transit_test.go b/internal/engine/transit/transit_test.go new file mode 100644 index 0000000..66a281b --- /dev/null +++ b/internal/engine/transit/transit_test.go @@ -0,0 +1,1025 @@ +package transit + +import ( + "context" + "encoding/base64" + "strings" + "sync" + "testing" + + "git.wntrmute.dev/kyle/metacrypt/internal/barrier" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" +) + +// memBarrier is an in-memory barrier for testing. +type memBarrier struct { + data map[string][]byte + mu sync.RWMutex +} + +func newMemBarrier() *memBarrier { + return &memBarrier{data: make(map[string][]byte)} +} + +func (m *memBarrier) Unseal(_ []byte) error { return nil } +func (m *memBarrier) Seal() error { return nil } +func (m *memBarrier) IsSealed() bool { return false } + +func (m *memBarrier) Get(_ context.Context, path string) ([]byte, error) { + m.mu.RLock() + defer m.mu.RUnlock() + v, ok := m.data[path] + if !ok { + return nil, barrier.ErrNotFound + } + cp := make([]byte, len(v)) + copy(cp, v) + return cp, nil +} + +func (m *memBarrier) Put(_ context.Context, path string, value []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + cp := make([]byte, len(value)) + copy(cp, value) + m.data[path] = cp + return nil +} + +func (m *memBarrier) Delete(_ context.Context, path string) error { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.data, path) + return nil +} + +func (m *memBarrier) List(_ context.Context, prefix string) ([]string, error) { + m.mu.RLock() + defer m.mu.RUnlock() + var paths []string + for k := range m.data { + if strings.HasPrefix(k, prefix) { + paths = append(paths, strings.TrimPrefix(k, prefix)) + } + } + return paths, nil +} + +func adminCaller() *engine.CallerInfo { + return &engine.CallerInfo{Username: "admin", Roles: []string{"admin"}, IsAdmin: true} +} + +func userCaller() *engine.CallerInfo { + return &engine.CallerInfo{Username: "user", Roles: []string{"user"}, IsAdmin: false} +} + +func guestCaller() *engine.CallerInfo { + return &engine.CallerInfo{Username: "guest", Roles: []string{"guest"}, IsAdmin: false} +} + +func setupEngine(t *testing.T) (*TransitEngine, *memBarrier) { + t.Helper() + b := newMemBarrier() + eng := NewTransitEngine() + ctx := context.Background() + mountPath := "engine/transit/test/" + + err := eng.Initialize(ctx, b, mountPath, nil) + if err != nil { + t.Fatalf("Initialize: %v", err) + } + + te := eng.(*TransitEngine) + return te, b +} + +func setupEngineWithUnseal(t *testing.T) (*TransitEngine, *memBarrier) { + t.Helper() + te, b := setupEngine(t) + + // Seal and unseal to test the full lifecycle. + if err := te.Seal(); err != nil { + t.Fatalf("Seal: %v", err) + } + + eng2 := NewTransitEngine() + ctx := context.Background() + if err := eng2.Unseal(ctx, b, "engine/transit/test/"); err != nil { + t.Fatalf("Unseal: %v", err) + } + return eng2.(*TransitEngine), b +} + +func createKey(t *testing.T, te *TransitEngine, name, keyType string) { + t.Helper() + ctx := context.Background() + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{ + "name": name, + "type": keyType, + }, + }) + if err != nil { + t.Fatalf("create-key %s: %v", name, err) + } +} + +func TestInitializeAndUnseal(t *testing.T) { + te, b := setupEngine(t) + + // Create a key so unseal has something to load. + createKey(t, te, "mykey", "aes256-gcm") + + if err := te.Seal(); err != nil { + t.Fatalf("Seal: %v", err) + } + + eng2 := NewTransitEngine() + if err := eng2.Unseal(context.Background(), b, "engine/transit/test/"); err != nil { + t.Fatalf("Unseal: %v", err) + } + + te2 := eng2.(*TransitEngine) + if _, ok := te2.keys["mykey"]; !ok { + t.Fatal("expected key 'mykey' after unseal") + } +} + +func TestCreateKeyAllTypes(t *testing.T) { + types := []string{"aes256-gcm", "chacha20-poly", "ed25519", "ecdsa-p256", "ecdsa-p384", "hmac-sha256", "hmac-sha512"} + for _, kt := range types { + t.Run(kt, func(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "key-"+kt, kt) + + ks, ok := te.keys["key-"+kt] + if !ok { + t.Fatal("key not created") + } + if ks.config.CurrentVersion != 1 { + t.Fatalf("expected version 1, got %d", ks.config.CurrentVersion) + } + if ks.config.MinDecryptionVersion != 1 { + t.Fatalf("expected min_decryption_version 1, got %d", ks.config.MinDecryptionVersion) + } + }) + } +} + +func TestEncryptDecryptAES(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "aes-key", "aes256-gcm") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("hello world")) + + // Encrypt. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{ + "key": "aes-key", + "plaintext": plaintext, + }, + }) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + + ct, _ := resp.Data["ciphertext"].(string) + if !strings.HasPrefix(ct, "metacrypt:v1:") { + t.Fatalf("unexpected ciphertext format: %s", ct) + } + + // Decrypt. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{ + "key": "aes-key", + "ciphertext": ct, + }, + }) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + + ptB64, _ := resp.Data["plaintext"].(string) + decoded, _ := base64.StdEncoding.DecodeString(ptB64) + if string(decoded) != "hello world" { + t.Fatalf("expected 'hello world', got %q", string(decoded)) + } +} + +func TestEncryptDecryptChaCha(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "chacha-key", "chacha20-poly") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("secret data")) + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "chacha-key", "plaintext": plaintext}, + }) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + + ct, _ := resp.Data["ciphertext"].(string) + if !strings.HasPrefix(ct, "metacrypt:v1:") { + t.Fatalf("unexpected ciphertext format: %s", ct) + } + + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "chacha-key", "ciphertext": ct}, + }) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + + ptB64, _ := resp.Data["plaintext"].(string) + decoded, _ := base64.StdEncoding.DecodeString(ptB64) + if string(decoded) != "secret data" { + t.Fatalf("expected 'secret data', got %q", string(decoded)) + } +} + +func TestEncryptWithContext(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "ctx-key", "aes256-gcm") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("context test")) + aadB64 := base64.StdEncoding.EncodeToString([]byte("my-context")) + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ctx-key", "plaintext": plaintext, "context": aadB64}, + }) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + ct, _ := resp.Data["ciphertext"].(string) + + // Decrypt with correct context. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ctx-key", "ciphertext": ct, "context": aadB64}, + }) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + + // Decrypt with wrong context should fail. + wrongCtx := base64.StdEncoding.EncodeToString([]byte("wrong-context")) + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ctx-key", "ciphertext": ct, "context": wrongCtx}, + }) + if err == nil { + t.Fatal("expected decrypt to fail with wrong context") + } +} + +func TestKeyRotationAndDecrypt(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "rot-key", "aes256-gcm") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("rotate me")) + + // Encrypt with v1. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rot-key", "plaintext": plaintext}, + }) + if err != nil { + t.Fatalf("encrypt v1: %v", err) + } + ctV1, _ := resp.Data["ciphertext"].(string) + + // Rotate. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "rotate-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "rot-key"}, + }) + if err != nil { + t.Fatalf("rotate: %v", err) + } + + // Encrypt with v2. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rot-key", "plaintext": plaintext}, + }) + if err != nil { + t.Fatalf("encrypt v2: %v", err) + } + ctV2, _ := resp.Data["ciphertext"].(string) + + if !strings.HasPrefix(ctV2, "metacrypt:v2:") { + t.Fatalf("expected v2 ciphertext, got %s", ctV2) + } + + // Both ciphertexts should decrypt. + for _, ct := range []string{ctV1, ctV2} { + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rot-key", "ciphertext": ct}, + }) + if err != nil { + t.Fatalf("decrypt: %v", err) + } + ptB64, _ := resp.Data["plaintext"].(string) + decoded, _ := base64.StdEncoding.DecodeString(ptB64) + if string(decoded) != "rotate me" { + t.Fatalf("expected 'rotate me', got %q", string(decoded)) + } + } +} + +func TestUpdateKeyConfig(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "cfg-key", "aes256-gcm") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("old data")) + + // Encrypt with v1. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "cfg-key", "plaintext": plaintext}, + }) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + ctV1, _ := resp.Data["ciphertext"].(string) + + // Rotate to v2. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "rotate-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "cfg-key"}, + }) + if err != nil { + t.Fatalf("rotate: %v", err) + } + + // Advance min_decryption_version to 2. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "update-key-config", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "cfg-key", "min_decryption_version": float64(2)}, + }) + if err != nil { + t.Fatalf("update-key-config: %v", err) + } + + // v1 ciphertext should be rejected. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "cfg-key", "ciphertext": ctV1}, + }) + if err == nil { + t.Fatal("expected decrypt to fail for v1 ciphertext") + } + + // Cannot decrease min_decryption_version. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "update-key-config", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "cfg-key", "min_decryption_version": float64(1)}, + }) + if err == nil { + t.Fatal("expected error decreasing min_decryption_version") + } + + // Cannot exceed current version. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "update-key-config", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "cfg-key", "min_decryption_version": float64(99)}, + }) + if err == nil { + t.Fatal("expected error exceeding current version") + } +} + +func TestTrimKey(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "trim-key", "aes256-gcm") + ctx := context.Background() + + // Rotate to v2, v3. + for i := 0; i < 2; i++ { + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "rotate-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "trim-key"}, + }) + if err != nil { + t.Fatalf("rotate: %v", err) + } + } + + // Set min_decryption_version to 2. + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "update-key-config", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "trim-key", "min_decryption_version": float64(2)}, + }) + if err != nil { + t.Fatalf("update-key-config: %v", err) + } + + // Trim. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "trim-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "trim-key"}, + }) + if err != nil { + t.Fatalf("trim-key: %v", err) + } + + trimmed, _ := resp.Data["trimmed"].(int) + if trimmed != 1 { + t.Fatalf("expected 1 trimmed, got %d", trimmed) + } + + // Version 1 should be gone. + if _, ok := te.keys["trim-key"].versions[1]; ok { + t.Fatal("version 1 should have been trimmed") + } + if _, ok := te.keys["trim-key"].versions[2]; !ok { + t.Fatal("version 2 should still exist") + } +} + +func TestSignVerifyEd25519(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "ed-key", "ed25519") + ctx := context.Background() + + input := base64.StdEncoding.EncodeToString([]byte("sign this")) + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "sign", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ed-key", "input": input}, + }) + if err != nil { + t.Fatalf("sign: %v", err) + } + sig, _ := resp.Data["signature"].(string) + if !strings.HasPrefix(sig, "metacrypt:v1:") { + t.Fatalf("unexpected signature format: %s", sig) + } + + // Verify. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "verify", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ed-key", "input": input, "signature": sig}, + }) + if err != nil { + t.Fatalf("verify: %v", err) + } + valid, _ := resp.Data["valid"].(bool) + if !valid { + t.Fatal("expected valid signature") + } + + // Wrong input should fail verification. + wrongInput := base64.StdEncoding.EncodeToString([]byte("wrong data")) + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "verify", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ed-key", "input": wrongInput, "signature": sig}, + }) + if err != nil { + t.Fatalf("verify: %v", err) + } + valid, _ = resp.Data["valid"].(bool) + if valid { + t.Fatal("expected invalid signature for wrong input") + } +} + +func TestSignVerifyECDSA(t *testing.T) { + for _, keyType := range []string{"ecdsa-p256", "ecdsa-p384"} { + t.Run(keyType, func(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "ec-key", keyType) + ctx := context.Background() + + input := base64.StdEncoding.EncodeToString([]byte("ecdsa test")) + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "sign", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ec-key", "input": input}, + }) + if err != nil { + t.Fatalf("sign: %v", err) + } + sig, _ := resp.Data["signature"].(string) + + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "verify", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "ec-key", "input": input, "signature": sig}, + }) + if err != nil { + t.Fatalf("verify: %v", err) + } + valid, _ := resp.Data["valid"].(bool) + if !valid { + t.Fatal("expected valid signature") + } + }) + } +} + +func TestSignRejectsSymmetricAndHMAC(t *testing.T) { + for _, keyType := range []string{"aes256-gcm", "hmac-sha256"} { + t.Run(keyType, func(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "sym-key", keyType) + ctx := context.Background() + + input := base64.StdEncoding.EncodeToString([]byte("test")) + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "sign", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "sym-key", "input": input}, + }) + if err == nil { + t.Fatal("expected error signing with non-asymmetric key") + } + }) + } +} + +func TestHMACComputeAndVerify(t *testing.T) { + for _, keyType := range []string{"hmac-sha256", "hmac-sha512"} { + t.Run(keyType, func(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "hmac-key", keyType) + ctx := context.Background() + + input := base64.StdEncoding.EncodeToString([]byte("hmac me")) + + // Compute. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "hmac", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "hmac-key", "input": input}, + }) + if err != nil { + t.Fatalf("hmac compute: %v", err) + } + hmacStr, _ := resp.Data["hmac"].(string) + if !strings.HasPrefix(hmacStr, "metacrypt:v1:") { + t.Fatalf("unexpected hmac format: %s", hmacStr) + } + + // Verify. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "hmac", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "hmac-key", "input": input, "hmac": hmacStr}, + }) + if err != nil { + t.Fatalf("hmac verify: %v", err) + } + valid, _ := resp.Data["valid"].(bool) + if !valid { + t.Fatal("expected valid HMAC") + } + + // Wrong input should fail. + wrongInput := base64.StdEncoding.EncodeToString([]byte("wrong data")) + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "hmac", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "hmac-key", "input": wrongInput, "hmac": hmacStr}, + }) + if err != nil { + t.Fatalf("hmac verify wrong: %v", err) + } + valid, _ = resp.Data["valid"].(bool) + if valid { + t.Fatal("expected invalid HMAC for wrong input") + } + }) + } +} + +func TestBatchEncryptDecrypt(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "batch-key", "aes256-gcm") + ctx := context.Background() + + items := []interface{}{ + map[string]interface{}{ + "plaintext": base64.StdEncoding.EncodeToString([]byte("item1")), + "reference": "ref1", + }, + map[string]interface{}{ + "plaintext": base64.StdEncoding.EncodeToString([]byte("item2")), + "reference": "ref2", + }, + } + + // Batch encrypt. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "batch-encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "batch-key", "items": items}, + }) + if err != nil { + t.Fatalf("batch-encrypt: %v", err) + } + + results, _ := resp.Data["results"].([]interface{}) + if len(results) != 2 { + t.Fatalf("expected 2 results, got %d", len(results)) + } + + // Build decrypt items. + decryptItems := make([]interface{}, len(results)) + for i, r := range results { + br, ok := r.(batchResult) + if !ok { + t.Fatalf("expected batchResult, got %T", r) + } + if br.Error != "" { + t.Fatalf("batch encrypt item %d error: %s", i, br.Error) + } + decryptItems[i] = map[string]interface{}{ + "ciphertext": br.Ciphertext, + "reference": br.Reference, + } + } + + // Batch decrypt. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "batch-decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "batch-key", "items": decryptItems}, + }) + if err != nil { + t.Fatalf("batch-decrypt: %v", err) + } + + results, _ = resp.Data["results"].([]interface{}) + expected := []string{"item1", "item2"} + for i, r := range results { + br, ok := r.(batchResult) + if !ok { + t.Fatalf("expected batchResult, got %T", r) + } + if br.Error != "" { + t.Fatalf("batch decrypt item %d error: %s", i, br.Error) + } + decoded, _ := base64.StdEncoding.DecodeString(br.Plaintext) + if string(decoded) != expected[i] { + t.Fatalf("item %d: expected %q, got %q", i, expected[i], string(decoded)) + } + } +} + +func TestBatchPartialErrors(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "batch-err-key", "aes256-gcm") + ctx := context.Background() + + items := []interface{}{ + map[string]interface{}{ + "ciphertext": "metacrypt:v1:invalidbase64!!!", + "reference": "bad", + }, + map[string]interface{}{ + "ciphertext": "metacrypt:v1:" + base64.StdEncoding.EncodeToString([]byte("not-valid-ciphertext")), + "reference": "also-bad", + }, + } + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "batch-decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "batch-err-key", "items": items}, + }) + if err != nil { + t.Fatalf("batch-decrypt: %v", err) + } + + results, _ := resp.Data["results"].([]interface{}) + if len(results) != 2 { + t.Fatalf("expected 2 results, got %d", len(results)) + } + for i, r := range results { + br, ok := r.(batchResult) + if !ok { + t.Fatalf("expected batchResult, got %T", r) + } + if br.Error == "" { + t.Fatalf("item %d: expected error", i) + } + } +} + +func TestRewrap(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "rewrap-key", "aes256-gcm") + ctx := context.Background() + + plaintext := base64.StdEncoding.EncodeToString([]byte("rewrap me")) + + // Encrypt with v1. + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rewrap-key", "plaintext": plaintext}, + }) + if err != nil { + t.Fatalf("encrypt: %v", err) + } + ctV1, _ := resp.Data["ciphertext"].(string) + + // Rotate. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "rotate-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "rewrap-key"}, + }) + if err != nil { + t.Fatalf("rotate: %v", err) + } + + // Rewrap. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "rewrap", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rewrap-key", "ciphertext": ctV1}, + }) + if err != nil { + t.Fatalf("rewrap: %v", err) + } + ctV2, _ := resp.Data["ciphertext"].(string) + if !strings.HasPrefix(ctV2, "metacrypt:v2:") { + t.Fatalf("expected v2 ciphertext after rewrap, got %s", ctV2) + } + + // Decrypt rewrapped ciphertext. + resp, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "decrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "rewrap-key", "ciphertext": ctV2}, + }) + if err != nil { + t.Fatalf("decrypt rewrapped: %v", err) + } + ptB64, _ := resp.Data["plaintext"].(string) + decoded, _ := base64.StdEncoding.DecodeString(ptB64) + if string(decoded) != "rewrap me" { + t.Fatalf("expected 'rewrap me', got %q", string(decoded)) + } +} + +func TestAuthEnforcement(t *testing.T) { + te, _ := setupEngine(t) + ctx := context.Background() + + // Admin-only operations should fail for users. + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: userCaller(), + Data: map[string]interface{}{"name": "test", "type": "aes256-gcm"}, + }) + if err == nil { + t.Fatal("expected create-key to fail for user") + } + + // Admin-only operations should fail for guests. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: guestCaller(), + Data: map[string]interface{}{"name": "test", "type": "aes256-gcm"}, + }) + if err == nil { + t.Fatal("expected create-key to fail for guest") + } + + // User operations should fail for guests. + createKey(t, te, "auth-key", "aes256-gcm") + + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "list-keys", + CallerInfo: guestCaller(), + }) + if err == nil { + t.Fatal("expected list-keys to fail for guest") + } + + // Encrypt should fail for guest. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: guestCaller(), + Data: map[string]interface{}{"key": "auth-key", "plaintext": base64.StdEncoding.EncodeToString([]byte("test"))}, + }) + if err == nil { + t.Fatal("expected encrypt to fail for guest") + } + + // Unauthenticated should fail. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: nil, + Data: map[string]interface{}{"name": "test", "type": "aes256-gcm"}, + }) + if err == nil { + t.Fatal("expected create-key to fail without auth") + } +} + +func TestDeleteKeyWithAndWithoutAllowDeletion(t *testing.T) { + te, _ := setupEngine(t) + ctx := context.Background() + + createKey(t, te, "nodelete-key", "aes256-gcm") + + // Should fail: allow_deletion is false. + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "delete-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "nodelete-key"}, + }) + if err == nil { + t.Fatal("expected delete to fail without allow_deletion") + } + + // Enable deletion. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "update-key-config", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "nodelete-key", "allow_deletion": true}, + }) + if err != nil { + t.Fatalf("update-key-config: %v", err) + } + + // Should succeed now. + _, err = te.HandleRequest(ctx, &engine.Request{ + Operation: "delete-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "nodelete-key"}, + }) + if err != nil { + t.Fatalf("delete-key: %v", err) + } + + if _, ok := te.keys["nodelete-key"]; ok { + t.Fatal("key should have been deleted") + } +} + +func TestGetPublicKey(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "pubkey", "ed25519") + ctx := context.Background() + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "get-public-key", + CallerInfo: userCaller(), + Data: map[string]interface{}{"name": "pubkey"}, + }) + if err != nil { + t.Fatalf("get-public-key: %v", err) + } + pk, _ := resp.Data["public_key"].(string) + if pk == "" { + t.Fatal("expected non-empty public key") + } + ver, _ := resp.Data["version"].(int) + if ver != 1 { + t.Fatalf("expected version 1, got %d", ver) + } +} + +func TestGetPublicKeyRejectsSymmetric(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "sym", "aes256-gcm") + ctx := context.Background() + + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "get-public-key", + CallerInfo: userCaller(), + Data: map[string]interface{}{"name": "sym"}, + }) + if err == nil { + t.Fatal("expected error getting public key for symmetric key") + } +} + +func TestBatchRewrap(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "brwrap-key", "aes256-gcm") + ctx := context.Background() + + pt1 := base64.StdEncoding.EncodeToString([]byte("item1")) + pt2 := base64.StdEncoding.EncodeToString([]byte("item2")) + + // Encrypt two items. + var cts []string + for _, pt := range []string{pt1, pt2} { + resp, _ := te.HandleRequest(ctx, &engine.Request{ + Operation: "encrypt", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "brwrap-key", "plaintext": pt}, + }) + ct, _ := resp.Data["ciphertext"].(string) + cts = append(cts, ct) + } + + // Rotate. + te.HandleRequest(ctx, &engine.Request{ + Operation: "rotate-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "brwrap-key"}, + }) + + // Batch rewrap. + items := []interface{}{ + map[string]interface{}{"ciphertext": cts[0], "reference": "r1"}, + map[string]interface{}{"ciphertext": cts[1], "reference": "r2"}, + } + + resp, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "batch-rewrap", + CallerInfo: userCaller(), + Data: map[string]interface{}{"key": "brwrap-key", "items": items}, + }) + if err != nil { + t.Fatalf("batch-rewrap: %v", err) + } + + results, _ := resp.Data["results"].([]interface{}) + for i, r := range results { + br, ok := r.(batchResult) + if !ok { + t.Fatalf("expected batchResult, got %T", r) + } + if br.Error != "" { + t.Fatalf("item %d error: %s", i, br.Error) + } + if !strings.HasPrefix(br.Ciphertext, "metacrypt:v2:") { + t.Fatalf("item %d: expected v2 ciphertext, got %s", i, br.Ciphertext) + } + } +} + +func TestDuplicateKeyCreation(t *testing.T) { + te, _ := setupEngine(t) + createKey(t, te, "dup-key", "aes256-gcm") + ctx := context.Background() + + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "dup-key", "type": "aes256-gcm"}, + }) + if err == nil { + t.Fatal("expected error creating duplicate key") + } +} + +func TestInvalidKeyType(t *testing.T) { + te, _ := setupEngine(t) + ctx := context.Background() + + _, err := te.HandleRequest(ctx, &engine.Request{ + Operation: "create-key", + CallerInfo: adminCaller(), + Data: map[string]interface{}{"name": "bad", "type": "invalid-type"}, + }) + if err == nil { + t.Fatal("expected error for invalid key type") + } +} diff --git a/internal/engine/transit/types.go b/internal/engine/transit/types.go new file mode 100644 index 0000000..f1c7a2b --- /dev/null +++ b/internal/engine/transit/types.go @@ -0,0 +1,15 @@ +package transit + +// TransitConfig is the transit engine configuration stored in the barrier. +type TransitConfig struct { + MaxKeyVersions int `json:"max_key_versions"` +} + +// KeyConfig is per-key configuration stored in the barrier. +type KeyConfig struct { + Name string `json:"name"` + Type string `json:"type"` // aes256-gcm, chacha20-poly, ed25519, ecdsa-p256, ecdsa-p384, hmac-sha256, hmac-sha512 + CurrentVersion int `json:"current_version"` + MinDecryptionVersion int `json:"min_decryption_version"` + AllowDeletion bool `json:"allow_deletion"` +} diff --git a/internal/grpcserver/server.go b/internal/grpcserver/server.go index 5cc93f9..bb6e39f 100644 --- a/internal/grpcserver/server.go +++ b/internal/grpcserver/server.go @@ -82,6 +82,7 @@ func (s *GRPCServer) Start() error { pb.RegisterCAServiceServer(s.srv, &caServer{s: s}) pb.RegisterPolicyServiceServer(s.srv, &policyServer{s: s}) pb.RegisterACMEServiceServer(s.srv, &acmeServer{s: s}) + pb.RegisterTransitServiceServer(s.srv, &transitServer{s: s}) lis, err := net.Listen("tcp", s.cfg.Server.GRPCAddr) if err != nil { @@ -136,7 +137,24 @@ func sealRequiredMethods() map[string]bool { "/metacrypt.v2.ACMEService/CreateEAB": true, "/metacrypt.v2.ACMEService/SetConfig": true, "/metacrypt.v2.ACMEService/ListAccounts": true, - "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.TransitService/CreateKey": true, + "/metacrypt.v2.TransitService/DeleteKey": true, + "/metacrypt.v2.TransitService/GetKey": true, + "/metacrypt.v2.TransitService/ListKeys": true, + "/metacrypt.v2.TransitService/RotateKey": true, + "/metacrypt.v2.TransitService/UpdateKeyConfig": true, + "/metacrypt.v2.TransitService/TrimKey": true, + "/metacrypt.v2.TransitService/Encrypt": true, + "/metacrypt.v2.TransitService/Decrypt": true, + "/metacrypt.v2.TransitService/Rewrap": true, + "/metacrypt.v2.TransitService/BatchEncrypt": true, + "/metacrypt.v2.TransitService/BatchDecrypt": true, + "/metacrypt.v2.TransitService/BatchRewrap": true, + "/metacrypt.v2.TransitService/Sign": true, + "/metacrypt.v2.TransitService/Verify": true, + "/metacrypt.v2.TransitService/Hmac": true, + "/metacrypt.v2.TransitService/GetPublicKey": true, } } @@ -166,7 +184,24 @@ func authRequiredMethods() map[string]bool { "/metacrypt.v2.ACMEService/CreateEAB": true, "/metacrypt.v2.ACMEService/SetConfig": true, "/metacrypt.v2.ACMEService/ListAccounts": true, - "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.TransitService/CreateKey": true, + "/metacrypt.v2.TransitService/DeleteKey": true, + "/metacrypt.v2.TransitService/GetKey": true, + "/metacrypt.v2.TransitService/ListKeys": true, + "/metacrypt.v2.TransitService/RotateKey": true, + "/metacrypt.v2.TransitService/UpdateKeyConfig": true, + "/metacrypt.v2.TransitService/TrimKey": true, + "/metacrypt.v2.TransitService/Encrypt": true, + "/metacrypt.v2.TransitService/Decrypt": true, + "/metacrypt.v2.TransitService/Rewrap": true, + "/metacrypt.v2.TransitService/BatchEncrypt": true, + "/metacrypt.v2.TransitService/BatchDecrypt": true, + "/metacrypt.v2.TransitService/BatchRewrap": true, + "/metacrypt.v2.TransitService/Sign": true, + "/metacrypt.v2.TransitService/Verify": true, + "/metacrypt.v2.TransitService/Hmac": true, + "/metacrypt.v2.TransitService/GetPublicKey": true, } } @@ -185,6 +220,11 @@ func adminRequiredMethods() map[string]bool { "/metacrypt.v2.PolicyService/DeletePolicy": true, "/metacrypt.v2.ACMEService/SetConfig": true, "/metacrypt.v2.ACMEService/ListAccounts": true, - "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.ACMEService/ListOrders": true, + "/metacrypt.v2.TransitService/CreateKey": true, + "/metacrypt.v2.TransitService/DeleteKey": true, + "/metacrypt.v2.TransitService/RotateKey": true, + "/metacrypt.v2.TransitService/UpdateKeyConfig": true, + "/metacrypt.v2.TransitService/TrimKey": true, } } diff --git a/internal/grpcserver/transit.go b/internal/grpcserver/transit.go new file mode 100644 index 0000000..f5fc2f3 --- /dev/null +++ b/internal/grpcserver/transit.go @@ -0,0 +1,486 @@ +package grpcserver + +import ( + "context" + "errors" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2" + "git.wntrmute.dev/kyle/metacrypt/internal/engine" + "git.wntrmute.dev/kyle/metacrypt/internal/engine/transit" + "git.wntrmute.dev/kyle/metacrypt/internal/policy" +) + +type transitServer struct { + pb.UnimplementedTransitServiceServer + s *GRPCServer +} + +func (ts *transitServer) transitHandleRequest(ctx context.Context, mount, operation string, req *engine.Request) (*engine.Response, error) { + resp, err := ts.s.engines.HandleRequest(ctx, mount, req) + if err != nil { + st := codes.Internal + switch { + case errors.Is(err, engine.ErrMountNotFound): + st = codes.NotFound + case errors.Is(err, transit.ErrKeyNotFound): + st = codes.NotFound + case errors.Is(err, transit.ErrKeyExists): + st = codes.AlreadyExists + case errors.Is(err, transit.ErrUnauthorized): + st = codes.Unauthenticated + case errors.Is(err, transit.ErrForbidden): + st = codes.PermissionDenied + case errors.Is(err, transit.ErrDeletionDenied): + st = codes.FailedPrecondition + case errors.Is(err, transit.ErrUnsupportedOp): + st = codes.InvalidArgument + case errors.Is(err, transit.ErrDecryptVersion): + st = codes.FailedPrecondition + case errors.Is(err, transit.ErrInvalidFormat): + st = codes.InvalidArgument + case errors.Is(err, transit.ErrBatchTooLarge): + st = codes.InvalidArgument + case errors.Is(err, transit.ErrInvalidMinVer): + st = codes.InvalidArgument + case strings.Contains(err.Error(), "not found"): + st = codes.NotFound + case strings.Contains(err.Error(), "forbidden"): + st = codes.PermissionDenied + } + ts.s.logger.Error("grpc: transit "+operation, "mount", mount, "error", err) + return nil, status.Error(st, err.Error()) + } + return resp, nil +} + +func (ts *transitServer) callerInfo(ctx context.Context) *engine.CallerInfo { + ti := tokenInfoFromContext(ctx) + if ti == nil { + return nil + } + return &engine.CallerInfo{ + Username: ti.Username, + Roles: ti.Roles, + IsAdmin: ti.IsAdmin, + } +} + +func (ts *transitServer) policyChecker(ctx context.Context) engine.PolicyChecker { + caller := ts.callerInfo(ctx) + if caller == nil { + return nil + } + return func(resource, action string) (string, bool) { + pReq := &policy.Request{ + Username: caller.Username, + Roles: caller.Roles, + Resource: resource, + Action: action, + } + effect, matched, err := ts.s.policy.Match(ctx, pReq) + if err != nil { + return string(policy.EffectDeny), false + } + return string(effect), matched + } +} + +func (ts *transitServer) CreateKey(ctx context.Context, req *pb.CreateTransitKeyRequest) (*pb.CreateTransitKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "create-key", &engine.Request{ + Operation: "create-key", + CallerInfo: ts.callerInfo(ctx), + Data: map[string]interface{}{ + "name": req.Name, + "type": req.Type, + }, + }) + if err != nil { + return nil, err + } + name, _ := resp.Data["name"].(string) + keyType, _ := resp.Data["type"].(string) + version, _ := resp.Data["version"].(int) + ts.s.logger.Info("audit: transit key created", "mount", req.Mount, "key", name, "type", keyType, "username", callerUsername(ctx)) + return &pb.CreateTransitKeyResponse{Name: name, Type: keyType, Version: int32(version)}, nil +} + +func (ts *transitServer) DeleteKey(ctx context.Context, req *pb.DeleteTransitKeyRequest) (*pb.DeleteTransitKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + _, err := ts.transitHandleRequest(ctx, req.Mount, "delete-key", &engine.Request{ + Operation: "delete-key", + CallerInfo: ts.callerInfo(ctx), + Data: map[string]interface{}{"name": req.Name}, + }) + if err != nil { + return nil, err + } + ts.s.logger.Info("audit: transit key deleted", "mount", req.Mount, "key", req.Name, "username", callerUsername(ctx)) + return &pb.DeleteTransitKeyResponse{}, nil +} + +func (ts *transitServer) GetKey(ctx context.Context, req *pb.GetTransitKeyRequest) (*pb.GetTransitKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "get-key", &engine.Request{ + Operation: "get-key", + CallerInfo: ts.callerInfo(ctx), + Data: map[string]interface{}{"name": req.Name}, + }) + if err != nil { + return nil, err + } + name, _ := resp.Data["name"].(string) + keyType, _ := resp.Data["type"].(string) + currentVersion, _ := resp.Data["current_version"].(int) + minDecryptionVersion, _ := resp.Data["min_decryption_version"].(int) + allowDeletion, _ := resp.Data["allow_deletion"].(bool) + rawVersions, _ := resp.Data["versions"].([]int) + versions := make([]int32, len(rawVersions)) + for i, v := range rawVersions { + versions[i] = int32(v) + } + return &pb.GetTransitKeyResponse{ + Name: name, + Type: keyType, + CurrentVersion: int32(currentVersion), + MinDecryptionVersion: int32(minDecryptionVersion), + AllowDeletion: allowDeletion, + Versions: versions, + }, nil +} + +func (ts *transitServer) ListKeys(ctx context.Context, req *pb.ListTransitKeysRequest) (*pb.ListTransitKeysResponse, error) { + if req.Mount == "" { + return nil, status.Error(codes.InvalidArgument, "mount is required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "list-keys", &engine.Request{ + Operation: "list-keys", + CallerInfo: ts.callerInfo(ctx), + }) + if err != nil { + return nil, err + } + keys := toStringSliceFromInterface(resp.Data["keys"]) + return &pb.ListTransitKeysResponse{Keys: keys}, nil +} + +func (ts *transitServer) RotateKey(ctx context.Context, req *pb.RotateTransitKeyRequest) (*pb.RotateTransitKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "rotate-key", &engine.Request{ + Operation: "rotate-key", + CallerInfo: ts.callerInfo(ctx), + Data: map[string]interface{}{"name": req.Name}, + }) + if err != nil { + return nil, err + } + name, _ := resp.Data["name"].(string) + version, _ := resp.Data["version"].(int) + ts.s.logger.Info("audit: transit key rotated", "mount", req.Mount, "key", name, "version", version, "username", callerUsername(ctx)) + return &pb.RotateTransitKeyResponse{Name: name, Version: int32(version)}, nil +} + +func (ts *transitServer) UpdateKeyConfig(ctx context.Context, req *pb.UpdateTransitKeyConfigRequest) (*pb.UpdateTransitKeyConfigResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + data := map[string]interface{}{"name": req.Name} + if req.MinDecryptionVersion != 0 { + data["min_decryption_version"] = float64(req.MinDecryptionVersion) + } + data["allow_deletion"] = req.AllowDeletion + + _, err := ts.transitHandleRequest(ctx, req.Mount, "update-key-config", &engine.Request{ + Operation: "update-key-config", + CallerInfo: ts.callerInfo(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + return &pb.UpdateTransitKeyConfigResponse{}, nil +} + +func (ts *transitServer) TrimKey(ctx context.Context, req *pb.TrimTransitKeyRequest) (*pb.TrimTransitKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "trim-key", &engine.Request{ + Operation: "trim-key", + CallerInfo: ts.callerInfo(ctx), + Data: map[string]interface{}{"name": req.Name}, + }) + if err != nil { + return nil, err + } + trimmed, _ := resp.Data["trimmed"].(int) + return &pb.TrimTransitKeyResponse{Trimmed: int32(trimmed)}, nil +} + +func (ts *transitServer) Encrypt(ctx context.Context, req *pb.TransitEncryptRequest) (*pb.TransitEncryptResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + data := map[string]interface{}{ + "key": req.Key, + "plaintext": req.Plaintext, + } + if req.Context != "" { + data["context"] = req.Context + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "encrypt", &engine.Request{ + Operation: "encrypt", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + ct, _ := resp.Data["ciphertext"].(string) + return &pb.TransitEncryptResponse{Ciphertext: ct}, nil +} + +func (ts *transitServer) Decrypt(ctx context.Context, req *pb.TransitDecryptRequest) (*pb.TransitDecryptResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + data := map[string]interface{}{ + "key": req.Key, + "ciphertext": req.Ciphertext, + } + if req.Context != "" { + data["context"] = req.Context + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "decrypt", &engine.Request{ + Operation: "decrypt", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + pt, _ := resp.Data["plaintext"].(string) + return &pb.TransitDecryptResponse{Plaintext: pt}, nil +} + +func (ts *transitServer) Rewrap(ctx context.Context, req *pb.TransitRewrapRequest) (*pb.TransitRewrapResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + data := map[string]interface{}{ + "key": req.Key, + "ciphertext": req.Ciphertext, + } + if req.Context != "" { + data["context"] = req.Context + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "rewrap", &engine.Request{ + Operation: "rewrap", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + ct, _ := resp.Data["ciphertext"].(string) + return &pb.TransitRewrapResponse{Ciphertext: ct}, nil +} + +func (ts *transitServer) BatchEncrypt(ctx context.Context, req *pb.TransitBatchEncryptRequest) (*pb.TransitBatchResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + items := protoItemsToInterface(req.Items) + resp, err := ts.transitHandleRequest(ctx, req.Mount, "batch-encrypt", &engine.Request{ + Operation: "batch-encrypt", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: map[string]interface{}{"key": req.Key, "items": items}, + }) + if err != nil { + return nil, err + } + return toBatchResponse(resp), nil +} + +func (ts *transitServer) BatchDecrypt(ctx context.Context, req *pb.TransitBatchDecryptRequest) (*pb.TransitBatchResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + items := protoItemsToInterface(req.Items) + resp, err := ts.transitHandleRequest(ctx, req.Mount, "batch-decrypt", &engine.Request{ + Operation: "batch-decrypt", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: map[string]interface{}{"key": req.Key, "items": items}, + }) + if err != nil { + return nil, err + } + return toBatchResponse(resp), nil +} + +func (ts *transitServer) BatchRewrap(ctx context.Context, req *pb.TransitBatchRewrapRequest) (*pb.TransitBatchResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + items := protoItemsToInterface(req.Items) + resp, err := ts.transitHandleRequest(ctx, req.Mount, "batch-rewrap", &engine.Request{ + Operation: "batch-rewrap", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: map[string]interface{}{"key": req.Key, "items": items}, + }) + if err != nil { + return nil, err + } + return toBatchResponse(resp), nil +} + +func (ts *transitServer) Sign(ctx context.Context, req *pb.TransitSignRequest) (*pb.TransitSignResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "sign", &engine.Request{ + Operation: "sign", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: map[string]interface{}{"key": req.Key, "input": req.Input}, + }) + if err != nil { + return nil, err + } + sig, _ := resp.Data["signature"].(string) + return &pb.TransitSignResponse{Signature: sig}, nil +} + +func (ts *transitServer) Verify(ctx context.Context, req *pb.TransitVerifyRequest) (*pb.TransitVerifyResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "verify", &engine.Request{ + Operation: "verify", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: map[string]interface{}{ + "key": req.Key, + "input": req.Input, + "signature": req.Signature, + }, + }) + if err != nil { + return nil, err + } + valid, _ := resp.Data["valid"].(bool) + return &pb.TransitVerifyResponse{Valid: valid}, nil +} + +func (ts *transitServer) Hmac(ctx context.Context, req *pb.TransitHmacRequest) (*pb.TransitHmacResponse, error) { + if req.Mount == "" || req.Key == "" { + return nil, status.Error(codes.InvalidArgument, "mount and key are required") + } + data := map[string]interface{}{ + "key": req.Key, + "input": req.Input, + } + if req.Hmac != "" { + data["hmac"] = req.Hmac + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "hmac", &engine.Request{ + Operation: "hmac", + CallerInfo: ts.callerInfo(ctx), + CheckPolicy: ts.policyChecker(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + hmacStr, _ := resp.Data["hmac"].(string) + valid, _ := resp.Data["valid"].(bool) + return &pb.TransitHmacResponse{Hmac: hmacStr, Valid: valid}, nil +} + +func (ts *transitServer) GetPublicKey(ctx context.Context, req *pb.GetTransitPublicKeyRequest) (*pb.GetTransitPublicKeyResponse, error) { + if req.Mount == "" || req.Name == "" { + return nil, status.Error(codes.InvalidArgument, "mount and name are required") + } + data := map[string]interface{}{"name": req.Name} + if req.Version != 0 { + data["version"] = float64(req.Version) + } + resp, err := ts.transitHandleRequest(ctx, req.Mount, "get-public-key", &engine.Request{ + Operation: "get-public-key", + CallerInfo: ts.callerInfo(ctx), + Data: data, + }) + if err != nil { + return nil, err + } + pk, _ := resp.Data["public_key"].(string) + version, _ := resp.Data["version"].(int) + keyType, _ := resp.Data["type"].(string) + return &pb.GetTransitPublicKeyResponse{ + PublicKey: pk, + Version: int32(version), + Type: keyType, + }, nil +} + +// --- helpers --- + +func protoItemsToInterface(items []*pb.TransitBatchItem) []interface{} { + out := make([]interface{}, len(items)) + for i, item := range items { + m := map[string]interface{}{} + if item.Plaintext != "" { + m["plaintext"] = item.Plaintext + } + if item.Ciphertext != "" { + m["ciphertext"] = item.Ciphertext + } + if item.Context != "" { + m["context"] = item.Context + } + if item.Reference != "" { + m["reference"] = item.Reference + } + out[i] = m + } + return out +} + +func toBatchResponse(resp *engine.Response) *pb.TransitBatchResponse { + raw, _ := resp.Data["results"].([]interface{}) + results := make([]*pb.TransitBatchResultItem, 0, len(raw)) + for _, item := range raw { + switch r := item.(type) { + case map[string]interface{}: + pt, _ := r["plaintext"].(string) + ct, _ := r["ciphertext"].(string) + ref, _ := r["reference"].(string) + errStr, _ := r["error"].(string) + results = append(results, &pb.TransitBatchResultItem{ + Plaintext: pt, + Ciphertext: ct, + Reference: ref, + Error: errStr, + }) + } + } + return &pb.TransitBatchResponse{Results: results} +} diff --git a/internal/server/routes.go b/internal/server/routes.go index 5822d54..e818362 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -47,6 +47,26 @@ func (s *Server) registerRoutes(r chi.Router) { r.HandleFunc("/v1/policy/rules", s.requireAuth(s.handlePolicyRules)) r.HandleFunc("/v1/policy/rule", s.requireAuth(s.handlePolicyRule)) + + // Transit engine routes. + r.Post("/v1/transit/{mount}/keys", s.requireAdmin(s.handleTransitCreateKey)) + r.Get("/v1/transit/{mount}/keys", s.requireAuth(s.handleTransitListKeys)) + r.Get("/v1/transit/{mount}/keys/{name}", s.requireAuth(s.handleTransitGetKey)) + r.Delete("/v1/transit/{mount}/keys/{name}", s.requireAdmin(s.handleTransitDeleteKey)) + r.Post("/v1/transit/{mount}/keys/{name}/rotate", s.requireAdmin(s.handleTransitRotateKey)) + r.Post("/v1/transit/{mount}/keys/{name}/config", s.requireAdmin(s.handleTransitUpdateKeyConfig)) + r.Post("/v1/transit/{mount}/keys/{name}/trim", s.requireAdmin(s.handleTransitTrimKey)) + r.Post("/v1/transit/{mount}/encrypt/{key}", s.requireAuth(s.handleTransitEncrypt)) + r.Post("/v1/transit/{mount}/decrypt/{key}", s.requireAuth(s.handleTransitDecrypt)) + r.Post("/v1/transit/{mount}/rewrap/{key}", s.requireAuth(s.handleTransitRewrap)) + r.Post("/v1/transit/{mount}/batch/encrypt/{key}", s.requireAuth(s.handleTransitBatchEncrypt)) + r.Post("/v1/transit/{mount}/batch/decrypt/{key}", s.requireAuth(s.handleTransitBatchDecrypt)) + r.Post("/v1/transit/{mount}/batch/rewrap/{key}", s.requireAuth(s.handleTransitBatchRewrap)) + r.Post("/v1/transit/{mount}/sign/{key}", s.requireAuth(s.handleTransitSign)) + r.Post("/v1/transit/{mount}/verify/{key}", s.requireAuth(s.handleTransitVerify)) + r.Post("/v1/transit/{mount}/hmac/{key}", s.requireAuth(s.handleTransitHmac)) + r.Get("/v1/transit/{mount}/keys/{name}/public-key", s.requireAuth(s.handleTransitGetPublicKey)) + s.registerACMERoutes(r) } @@ -608,11 +628,270 @@ func (s *Server) getCAEngine(mountName string) (*ca.CAEngine, error) { return caEng, nil } +// --- Transit Engine Handlers --- + +func (s *Server) transitRequest(w http.ResponseWriter, r *http.Request, mount, operation string, data map[string]interface{}) { + info := TokenInfoFromContext(r.Context()) + + policyChecker := func(resource, action string) (string, bool) { + pReq := &policy.Request{ + Username: info.Username, + Roles: info.Roles, + Resource: resource, + Action: action, + } + eff, matched, pErr := s.policy.Match(r.Context(), pReq) + if pErr != nil { + return string(policy.EffectDeny), false + } + return string(eff), matched + } + + resp, err := s.engines.HandleRequest(r.Context(), mount, &engine.Request{ + Operation: operation, + CallerInfo: &engine.CallerInfo{Username: info.Username, Roles: info.Roles, IsAdmin: info.IsAdmin}, + CheckPolicy: policyChecker, + Data: data, + }) + if err != nil { + status := http.StatusInternalServerError + switch { + case errors.Is(err, engine.ErrMountNotFound): + status = http.StatusNotFound + case strings.Contains(err.Error(), "forbidden"): + status = http.StatusForbidden + case strings.Contains(err.Error(), "authentication required"): + status = http.StatusUnauthorized + case strings.Contains(err.Error(), "not found"): + status = http.StatusNotFound + case strings.Contains(err.Error(), "not allowed"): + status = http.StatusForbidden + case strings.Contains(err.Error(), "unsupported"): + status = http.StatusBadRequest + case strings.Contains(err.Error(), "invalid"): + status = http.StatusBadRequest + } + http.Error(w, `{"error":"`+err.Error()+`"}`, status) + return + } + writeJSON(w, http.StatusOK, resp.Data) +} + +func (s *Server) handleTransitCreateKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + var req struct { + Name string `json:"name"` + Type string `json:"type"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "create-key", map[string]interface{}{"name": req.Name, "type": req.Type}) +} + +func (s *Server) handleTransitDeleteKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + s.transitRequest(w, r, mount, "delete-key", map[string]interface{}{"name": name}) +} + +func (s *Server) handleTransitGetKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + s.transitRequest(w, r, mount, "get-key", map[string]interface{}{"name": name}) +} + +func (s *Server) handleTransitListKeys(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + s.transitRequest(w, r, mount, "list-keys", nil) +} + +func (s *Server) handleTransitRotateKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + s.transitRequest(w, r, mount, "rotate-key", map[string]interface{}{"name": name}) +} + +func (s *Server) handleTransitUpdateKeyConfig(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + var req struct { + MinDecryptionVersion *float64 `json:"min_decryption_version"` + AllowDeletion *bool `json:"allow_deletion"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + data := map[string]interface{}{"name": name} + if req.MinDecryptionVersion != nil { + data["min_decryption_version"] = *req.MinDecryptionVersion + } + if req.AllowDeletion != nil { + data["allow_deletion"] = *req.AllowDeletion + } + s.transitRequest(w, r, mount, "update-key-config", data) +} + +func (s *Server) handleTransitTrimKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + s.transitRequest(w, r, mount, "trim-key", map[string]interface{}{"name": name}) +} + +func (s *Server) handleTransitEncrypt(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Plaintext string `json:"plaintext"` + Context string `json:"context"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + data := map[string]interface{}{"key": key, "plaintext": req.Plaintext} + if req.Context != "" { + data["context"] = req.Context + } + s.transitRequest(w, r, mount, "encrypt", data) +} + +func (s *Server) handleTransitDecrypt(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Ciphertext string `json:"ciphertext"` + Context string `json:"context"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + data := map[string]interface{}{"key": key, "ciphertext": req.Ciphertext} + if req.Context != "" { + data["context"] = req.Context + } + s.transitRequest(w, r, mount, "decrypt", data) +} + +func (s *Server) handleTransitRewrap(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Ciphertext string `json:"ciphertext"` + Context string `json:"context"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + data := map[string]interface{}{"key": key, "ciphertext": req.Ciphertext} + if req.Context != "" { + data["context"] = req.Context + } + s.transitRequest(w, r, mount, "rewrap", data) +} + +func (s *Server) handleTransitBatchEncrypt(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Items []interface{} `json:"items"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "batch-encrypt", map[string]interface{}{"key": key, "items": req.Items}) +} + +func (s *Server) handleTransitBatchDecrypt(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Items []interface{} `json:"items"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "batch-decrypt", map[string]interface{}{"key": key, "items": req.Items}) +} + +func (s *Server) handleTransitBatchRewrap(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Items []interface{} `json:"items"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "batch-rewrap", map[string]interface{}{"key": key, "items": req.Items}) +} + +func (s *Server) handleTransitSign(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Input string `json:"input"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "sign", map[string]interface{}{"key": key, "input": req.Input}) +} + +func (s *Server) handleTransitVerify(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Input string `json:"input"` + Signature string `json:"signature"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + s.transitRequest(w, r, mount, "verify", map[string]interface{}{"key": key, "input": req.Input, "signature": req.Signature}) +} + +func (s *Server) handleTransitHmac(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + key := chi.URLParam(r, "key") + var req struct { + Input string `json:"input"` + HMAC string `json:"hmac"` + } + if err := readJSON(r, &req); err != nil { + http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) + return + } + data := map[string]interface{}{"key": key, "input": req.Input} + if req.HMAC != "" { + data["hmac"] = req.HMAC + } + s.transitRequest(w, r, mount, "hmac", data) +} + +func (s *Server) handleTransitGetPublicKey(w http.ResponseWriter, r *http.Request) { + mount := chi.URLParam(r, "mount") + name := chi.URLParam(r, "name") + s.transitRequest(w, r, mount, "get-public-key", map[string]interface{}{"name": name}) +} + // operationAction maps an engine operation name to a policy action ("read" or "write"). func operationAction(op string) string { switch op { - case "list-issuers", "list-certs", "get-cert", "get-root", "get-chain", "get-issuer": + case "list-issuers", "list-certs", "get-cert", "get-root", "get-chain", "get-issuer", + "list-keys", "get-key", "get-public-key": return "read" + case "decrypt", "rewrap", "batch-decrypt", "batch-rewrap": + return "decrypt" default: return "write" } diff --git a/proto/metacrypt/v2/transit.proto b/proto/metacrypt/v2/transit.proto new file mode 100644 index 0000000..4e2ae32 --- /dev/null +++ b/proto/metacrypt/v2/transit.proto @@ -0,0 +1,291 @@ +syntax = "proto3"; + +package metacrypt.v2; + +option go_package = "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2;metacryptv2"; + +// TransitService provides typed, authenticated access to transit engine +// operations: symmetric encryption, signing, HMAC, and versioned key +// management. All RPCs require the service to be unsealed. +service TransitService { + // CreateKey creates a new named encryption key. Admin only. + rpc CreateKey(CreateTransitKeyRequest) returns (CreateTransitKeyResponse); + + // DeleteKey permanently removes a named key. Admin only. + // Only succeeds if allow_deletion is true on the key config. + rpc DeleteKey(DeleteTransitKeyRequest) returns (DeleteTransitKeyResponse); + + // GetKey returns metadata for a named key (no raw material). Auth required. + rpc GetKey(GetTransitKeyRequest) returns (GetTransitKeyResponse); + + // ListKeys returns the names of all configured keys. Auth required. + rpc ListKeys(ListTransitKeysRequest) returns (ListTransitKeysResponse); + + // RotateKey creates a new version of the named key. Admin only. + rpc RotateKey(RotateTransitKeyRequest) returns (RotateTransitKeyResponse); + + // UpdateKeyConfig updates key configuration (e.g. min_decryption_version). + // Admin only. + rpc UpdateKeyConfig(UpdateTransitKeyConfigRequest) returns (UpdateTransitKeyConfigResponse); + + // TrimKey deletes versions below min_decryption_version. Admin only. + rpc TrimKey(TrimTransitKeyRequest) returns (TrimTransitKeyResponse); + + // Encrypt encrypts plaintext with the latest key version. Auth required. + rpc Encrypt(TransitEncryptRequest) returns (TransitEncryptResponse); + + // Decrypt decrypts ciphertext. Auth required. + rpc Decrypt(TransitDecryptRequest) returns (TransitDecryptResponse); + + // Rewrap re-encrypts ciphertext with the latest key version without + // exposing plaintext. Auth required. + rpc Rewrap(TransitRewrapRequest) returns (TransitRewrapResponse); + + // BatchEncrypt encrypts multiple items in a single request. Auth required. + rpc BatchEncrypt(TransitBatchEncryptRequest) returns (TransitBatchResponse); + + // BatchDecrypt decrypts multiple items in a single request. Auth required. + rpc BatchDecrypt(TransitBatchDecryptRequest) returns (TransitBatchResponse); + + // BatchRewrap re-encrypts multiple items in a single request. Auth required. + rpc BatchRewrap(TransitBatchRewrapRequest) returns (TransitBatchResponse); + + // Sign signs input data with an asymmetric key. Auth required. + rpc Sign(TransitSignRequest) returns (TransitSignResponse); + + // Verify verifies a signature against input data. Auth required. + rpc Verify(TransitVerifyRequest) returns (TransitVerifyResponse); + + // Hmac computes or verifies an HMAC. Auth required. + rpc Hmac(TransitHmacRequest) returns (TransitHmacResponse); + + // GetPublicKey returns the public key for an asymmetric key. Auth required. + rpc GetPublicKey(GetTransitPublicKeyRequest) returns (GetTransitPublicKeyResponse); +} + +// --- CreateKey --- + +message CreateTransitKeyRequest { + string mount = 1; + string name = 2; + // type is the key algorithm: aes256-gcm, chacha20-poly, ed25519, + // ecdsa-p256, ecdsa-p384, hmac-sha256, hmac-sha512. + // Defaults to aes256-gcm if empty. + string type = 3; +} + +message CreateTransitKeyResponse { + string name = 1; + string type = 2; + int32 version = 3; +} + +// --- DeleteKey --- + +message DeleteTransitKeyRequest { + string mount = 1; + string name = 2; +} + +message DeleteTransitKeyResponse {} + +// --- GetKey --- + +message GetTransitKeyRequest { + string mount = 1; + string name = 2; +} + +message GetTransitKeyResponse { + string name = 1; + string type = 2; + int32 current_version = 3; + int32 min_decryption_version = 4; + bool allow_deletion = 5; + repeated int32 versions = 6; +} + +// --- ListKeys --- + +message ListTransitKeysRequest { + string mount = 1; +} + +message ListTransitKeysResponse { + repeated string keys = 1; +} + +// --- RotateKey --- + +message RotateTransitKeyRequest { + string mount = 1; + string name = 2; +} + +message RotateTransitKeyResponse { + string name = 1; + int32 version = 2; +} + +// --- UpdateKeyConfig --- + +message UpdateTransitKeyConfigRequest { + string mount = 1; + string name = 2; + int32 min_decryption_version = 3; + bool allow_deletion = 4; +} + +message UpdateTransitKeyConfigResponse {} + +// --- TrimKey --- + +message TrimTransitKeyRequest { + string mount = 1; + string name = 2; +} + +message TrimTransitKeyResponse { + int32 trimmed = 1; +} + +// --- Encrypt --- + +message TransitEncryptRequest { + string mount = 1; + string key = 2; + // plaintext is base64-encoded data to encrypt. + string plaintext = 3; + // context is optional base64-encoded additional authenticated data (AAD). + string context = 4; +} + +message TransitEncryptResponse { + // ciphertext in format "metacrypt:v{version}:{base64(nonce+ciphertext+tag)}" + string ciphertext = 1; +} + +// --- Decrypt --- + +message TransitDecryptRequest { + string mount = 1; + string key = 2; + string ciphertext = 3; + string context = 4; +} + +message TransitDecryptResponse { + // plaintext is base64-encoded decrypted data. + string plaintext = 1; +} + +// --- Rewrap --- + +message TransitRewrapRequest { + string mount = 1; + string key = 2; + string ciphertext = 3; + string context = 4; +} + +message TransitRewrapResponse { + string ciphertext = 1; +} + +// --- Batch --- + +message TransitBatchItem { + string plaintext = 1; + string ciphertext = 2; + string context = 3; + string reference = 4; +} + +message TransitBatchResultItem { + string plaintext = 1; + string ciphertext = 2; + string reference = 3; + string error = 4; +} + +message TransitBatchEncryptRequest { + string mount = 1; + string key = 2; + repeated TransitBatchItem items = 3; +} + +message TransitBatchDecryptRequest { + string mount = 1; + string key = 2; + repeated TransitBatchItem items = 3; +} + +message TransitBatchRewrapRequest { + string mount = 1; + string key = 2; + repeated TransitBatchItem items = 3; +} + +message TransitBatchResponse { + repeated TransitBatchResultItem results = 1; +} + +// --- Sign --- + +message TransitSignRequest { + string mount = 1; + string key = 2; + // input is base64-encoded data to sign. + string input = 3; +} + +message TransitSignResponse { + // signature in format "metacrypt:v{version}:{base64(signature_bytes)}" + string signature = 1; +} + +// --- Verify --- + +message TransitVerifyRequest { + string mount = 1; + string key = 2; + string input = 3; + string signature = 4; +} + +message TransitVerifyResponse { + bool valid = 1; +} + +// --- HMAC --- + +message TransitHmacRequest { + string mount = 1; + string key = 2; + // input is base64-encoded data to HMAC. + string input = 3; + // hmac, if set, switches to verify mode. + string hmac = 4; +} + +message TransitHmacResponse { + // hmac is set in compute mode. + string hmac = 1; + // valid is set in verify mode. + bool valid = 2; +} + +// --- GetPublicKey --- + +message GetTransitPublicKeyRequest { + string mount = 1; + string name = 2; + int32 version = 3; +} + +message GetTransitPublicKeyResponse { + // public_key is base64-encoded PKIX DER public key. + string public_key = 1; + int32 version = 2; + string type = 3; +}