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 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 19:45:56 -07:00
parent ac4577f778
commit cbd77c58e8
10 changed files with 6718 additions and 4 deletions

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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",
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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"`
}

View File

@@ -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 {
@@ -137,6 +138,23 @@ func sealRequiredMethods() map[string]bool {
"/metacrypt.v2.ACMEService/SetConfig": true,
"/metacrypt.v2.ACMEService/ListAccounts": 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,
}
}
@@ -167,6 +185,23 @@ func authRequiredMethods() map[string]bool {
"/metacrypt.v2.ACMEService/SetConfig": true,
"/metacrypt.v2.ACMEService/ListAccounts": 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,
}
}
@@ -186,5 +221,10 @@ func adminRequiredMethods() map[string]bool {
"/metacrypt.v2.ACMEService/SetConfig": true,
"/metacrypt.v2.ACMEService/ListAccounts": 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,
}
}

View File

@@ -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}
}

View File

@@ -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"
}

View File

@@ -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;
}