Merge pull request #512 from Beckn-One/503-simplekm
Feature 503 - Build a KeyManager Plugin to read keys from local
This commit is contained in:
@@ -11,6 +11,7 @@ plugins=(
|
|||||||
"decrypter"
|
"decrypter"
|
||||||
"encrypter"
|
"encrypter"
|
||||||
"keymanager"
|
"keymanager"
|
||||||
|
"simplekeymanager"
|
||||||
"publisher"
|
"publisher"
|
||||||
"reqpreprocessor"
|
"reqpreprocessor"
|
||||||
"router"
|
"router"
|
||||||
|
|||||||
143
pkg/plugin/implementation/simplekeymanager/README.md
Normal file
143
pkg/plugin/implementation/simplekeymanager/README.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# SimpleKeyManager Plugin
|
||||||
|
|
||||||
|
A simple keymanager plugin for beckn-onix that reads Ed25519 and X25519 keys from configuration instead of using external secret management systems like HashiCorp Vault.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This plugin provides a lightweight alternative to the vault keymanager by reading cryptographic keys directly from configuration. It's designed for development environments and simpler deployments that don't require the complexity of external secret management.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Ed25519 + X25519 Key Support**: Supports Ed25519 signing keys and X25519 encryption keys
|
||||||
|
- **Configuration-Based**: Reads keys from YAML configuration instead of environment variables
|
||||||
|
- **Multiple Formats**: Supports both PEM and Base64 encoded keys
|
||||||
|
- **Auto-detection**: Automatically detects key format (PEM vs Base64)
|
||||||
|
- **Zero Dependencies**: No external services required (unlike vault keymanager)
|
||||||
|
- **Memory Storage**: Stores keysets in memory for fast access
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Basic Configuration
|
||||||
|
|
||||||
|
In your beckn-onix configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
keymanager:
|
||||||
|
id: simplekeymanager
|
||||||
|
config:
|
||||||
|
networkParticipant: bap-network
|
||||||
|
keyId: bap-network-key
|
||||||
|
signingPrivateKey: uc5WYG/eke0PVGyQ9JNVLpwQL0K9JIZfHfqUHdLBTaY=
|
||||||
|
signingPublicKey: kUSiFNAD3+6oE7KffKucxZ74e6g4i9VM6ypImg4rVCM=
|
||||||
|
encrPrivateKey: uc5WYG/eke0PVGyQ9JNVLpwQL0K9JIZfHfqUHdLBTaY=
|
||||||
|
encrPublicKey: kUSiFNAD3+6oE7KffKucxZ74e6g4i9VM6ypImg4rVCM=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `networkParticipant` | string | Yes | Identifier for the keyset, represents subscriberId or networkParticipant name |
|
||||||
|
| `keyId` | string | Yes | Unique Key id for the keyset |
|
||||||
|
| `signingPrivateKey` | string | Yes* | Ed25519 private key for signing (Base64 or PEM) |
|
||||||
|
| `signingPublicKey` | string | Yes* | Ed25519 public key for signing (Base64 or PEM) |
|
||||||
|
| `encrPrivateKey` | string | Yes* | X25519 private key for encryption (Base64 or PEM) |
|
||||||
|
| `encrPublicKey` | string | Yes* | X25519 public key for encryption (Base64 or PEM) |
|
||||||
|
|
||||||
|
*Required if any key is provided. If keys are configured, all four keys must be provided.
|
||||||
|
|
||||||
|
## Key Generation
|
||||||
|
|
||||||
|
### Ed25519 Signing Keys
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate Ed25519 signing key pair
|
||||||
|
openssl genpkey -algorithm Ed25519 -out signing_private.pem
|
||||||
|
openssl pkey -in signing_private.pem -pubout -out signing_public.pem
|
||||||
|
|
||||||
|
# Convert to base64 (single line)
|
||||||
|
signing_private_b64=$(openssl pkey -in signing_private.pem -outform DER | base64 -w 0)
|
||||||
|
signing_public_b64=$(openssl pkey -in signing_public.pem -pubin -outform DER | base64 -w 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### X25519 Encryption Keys
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate X25519 encryption key pair
|
||||||
|
openssl genpkey -algorithm X25519 -out encr_private.pem
|
||||||
|
openssl pkey -in encr_private.pem -pubout -out encr_public.pem
|
||||||
|
|
||||||
|
# Convert to base64 (single line)
|
||||||
|
encr_private_b64=$(openssl pkey -in encr_private.pem -outform DER | base64 -w 0)
|
||||||
|
encr_public_b64=$(openssl pkey -in encr_public.pem -pubin -outform DER | base64 -w 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The plugin implements the same `KeyManager` interface as the vault keymanager:
|
||||||
|
|
||||||
|
- `GenerateKeyset() (*model.Keyset, error)` - Generate new key pair
|
||||||
|
- `InsertKeyset(ctx, keyID, keyset) error` - Store keyset in memory
|
||||||
|
- `Keyset(ctx, keyID) (*model.Keyset, error)` - Retrieve keyset from memory
|
||||||
|
- `DeleteKeyset(ctx, keyID) error` - Delete keyset from memory
|
||||||
|
- `LookupNPKeys(ctx, subscriberID, uniqueKeyID) (string, string, error)` - Lookup public keys from registry
|
||||||
|
|
||||||
|
### Example Usage in Code
|
||||||
|
|
||||||
|
```go
|
||||||
|
// The keyset from config is automatically loaded with the configured keyId
|
||||||
|
keyset, err := keyManager.Keyset(ctx, "bap-network")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new keys programmatically
|
||||||
|
newKeyset, err := keyManager.GenerateKeyset()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the new keyset
|
||||||
|
err = keyManager.InsertKeyset(ctx, "new-key-id", newKeyset)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison with Vault KeyManager
|
||||||
|
|
||||||
|
| Feature | SimpleKeyManager | Vault KeyManager |
|
||||||
|
|---------|------------------|------------------|
|
||||||
|
| **Setup Complexity** | Very Low (config only) | High (requires Vault) |
|
||||||
|
| **Configuration** | YAML configuration | Vault connection + secrets |
|
||||||
|
| **Dependencies** | None | HashiCorp Vault |
|
||||||
|
| **Security** | Basic (config-based) | Advanced (centralized secrets) |
|
||||||
|
| **Key Rotation** | Manual config update | Automated options |
|
||||||
|
| **Audit Logging** | Application logs only | Full audit trails |
|
||||||
|
| **Multi-tenancy** | Limited (memory-based) | Full support |
|
||||||
|
| **Best for** | Development/Testing/Simple deployments | Production/Enterprise |
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run tests with:
|
||||||
|
```bash
|
||||||
|
cd pkg/plugin/implementation/simplekeymanager
|
||||||
|
go test -v ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. The plugin is automatically built with beckn-onix
|
||||||
|
2. Configure the plugin in your beckn-onix configuration file. Change in configuration requires restart of service.
|
||||||
|
3. The plugin will be loaded automatically when beckn-onix starts
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Configuration files contain sensitive key material
|
||||||
|
- Use proper file permissions for config files
|
||||||
|
- Implement regular key rotation
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This plugin follows the same license as the main beckn-onix project.
|
||||||
45
pkg/plugin/implementation/simplekeymanager/cmd/plugin.go
Normal file
45
pkg/plugin/implementation/simplekeymanager/cmd/plugin.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/log"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/definition"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/implementation/simplekeymanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
// simpleKeyManagerProvider implements the plugin provider for the SimpleKeyManager plugin.
|
||||||
|
type simpleKeyManagerProvider struct{}
|
||||||
|
|
||||||
|
// newSimpleKeyManagerFunc is a function type that creates a new SimpleKeyManager instance.
|
||||||
|
var newSimpleKeyManagerFunc = simplekeymanager.New
|
||||||
|
|
||||||
|
// New creates and initializes a new SimpleKeyManager instance using the provided cache, registry lookup, and configuration.
|
||||||
|
func (k *simpleKeyManagerProvider) New(ctx context.Context, cache definition.Cache, registry definition.RegistryLookup, cfg map[string]string) (definition.KeyManager, func() error, error) {
|
||||||
|
config := &simplekeymanager.Config{
|
||||||
|
NetworkParticipant: cfg["networkParticipant"],
|
||||||
|
KeyID: cfg["keyId"],
|
||||||
|
SigningPrivateKey: cfg["signingPrivateKey"],
|
||||||
|
SigningPublicKey: cfg["signingPublicKey"],
|
||||||
|
EncrPrivateKey: cfg["encrPrivateKey"],
|
||||||
|
EncrPublicKey: cfg["encrPublicKey"],
|
||||||
|
}
|
||||||
|
log.Debugf(ctx, "SimpleKeyManager config mapped: np=%s, keyId=%s, has_signing_private=%v, has_signing_public=%v, has_encr_private=%v, has_encr_public=%v",
|
||||||
|
config.NetworkParticipant,
|
||||||
|
config.KeyID,
|
||||||
|
config.SigningPrivateKey != "",
|
||||||
|
config.SigningPublicKey != "",
|
||||||
|
config.EncrPrivateKey != "",
|
||||||
|
config.EncrPublicKey != "")
|
||||||
|
|
||||||
|
km, cleanup, err := newSimpleKeyManagerFunc(ctx, cache, registry, config)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(ctx, err, "Failed to initialize SimpleKeyManager")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
log.Debugf(ctx, "SimpleKeyManager instance created successfully")
|
||||||
|
return km, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider is the exported instance of simpleKeyManagerProvider used for plugin registration.
|
||||||
|
var Provider = simpleKeyManagerProvider{}
|
||||||
212
pkg/plugin/implementation/simplekeymanager/cmd/plugin_test.go
Normal file
212
pkg/plugin/implementation/simplekeymanager/cmd/plugin_test.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/model"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/implementation/simplekeymanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock implementations for testing
|
||||||
|
type mockCache struct{}
|
||||||
|
|
||||||
|
func (m *mockCache) Get(ctx context.Context, key string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Clear(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Delete(ctx context.Context, key string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockRegistry struct{}
|
||||||
|
|
||||||
|
func (m *mockRegistry) Lookup(ctx context.Context, sub *model.Subscription) ([]model.Subscription, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleKeyManagerProvider_New(t *testing.T) {
|
||||||
|
provider := &simpleKeyManagerProvider{}
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &mockCache{}
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config map[string]string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
config: map[string]string{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with keys",
|
||||||
|
config: map[string]string{
|
||||||
|
"networkParticipant": "bap-one",
|
||||||
|
"keyId": "test-key",
|
||||||
|
"signingPrivateKey": "dGVzdC1zaWduaW5nLXByaXZhdGU=",
|
||||||
|
"signingPublicKey": "dGVzdC1zaWduaW5nLXB1YmxpYw==",
|
||||||
|
"encrPrivateKey": "dGVzdC1lbmNyLXByaXZhdGU=",
|
||||||
|
"encrPublicKey": "dGVzdC1lbmNyLXB1YmxpYw==",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid config - partial keys",
|
||||||
|
config: map[string]string{
|
||||||
|
"keyId": "test-key",
|
||||||
|
"signingPrivateKey": "dGVzdC1zaWduaW5nLXByaXZhdGU=",
|
||||||
|
// Missing other required keys
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config with only keyId",
|
||||||
|
config: map[string]string{
|
||||||
|
"keyId": "test-key",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
km, cleanup, err := provider.New(ctx, cache, registry, tt.config)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("simpleKeyManagerProvider.New() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
if km == nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() returned nil keymanager")
|
||||||
|
}
|
||||||
|
if cleanup == nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() returned nil cleanup function")
|
||||||
|
}
|
||||||
|
if cleanup != nil {
|
||||||
|
// Test that cleanup doesn't panic
|
||||||
|
err := cleanup()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("cleanup() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if km != nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() should return nil keymanager on error")
|
||||||
|
}
|
||||||
|
if cleanup != nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() should return nil cleanup on error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleKeyManagerProvider_NewWithNilDependencies(t *testing.T) {
|
||||||
|
provider := &simpleKeyManagerProvider{}
|
||||||
|
ctx := context.Background()
|
||||||
|
config := map[string]string{}
|
||||||
|
|
||||||
|
// Test with nil cache
|
||||||
|
_, _, err := provider.New(ctx, nil, &mockRegistry{}, config)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() should fail with nil cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with nil registry
|
||||||
|
_, _, err = provider.New(ctx, &mockCache{}, nil, config)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() should fail with nil registry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with both nil
|
||||||
|
_, _, err = provider.New(ctx, nil, nil, config)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("simpleKeyManagerProvider.New() should fail with nil dependencies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigMapping(t *testing.T) {
|
||||||
|
provider := &simpleKeyManagerProvider{}
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &mockCache{}
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
|
||||||
|
// Test config mapping
|
||||||
|
configMap := map[string]string{
|
||||||
|
"networkParticipant": "mapped-np",
|
||||||
|
"keyId": "mapped-key-id",
|
||||||
|
"signingPrivateKey": "mapped-signing-private",
|
||||||
|
"signingPublicKey": "mapped-signing-public",
|
||||||
|
"encrPrivateKey": "mapped-encr-private",
|
||||||
|
"encrPublicKey": "mapped-encr-public",
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't directly test the config mapping without exposing internals,
|
||||||
|
// but we can test that the mapping doesn't cause errors
|
||||||
|
_, cleanup, err := provider.New(ctx, cache, registry, configMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Config mapping failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleanup != nil {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the provider implements the correct interface
|
||||||
|
// This is a compile-time check to ensure interface compliance
|
||||||
|
func TestProviderInterface(t *testing.T) {
|
||||||
|
provider := &simpleKeyManagerProvider{}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// This should compile if the interface is implemented correctly
|
||||||
|
_, _, err := provider.New(ctx, &mockCache{}, &mockRegistry{}, map[string]string{})
|
||||||
|
|
||||||
|
// We expect an error here because of missing dependencies, but the call should compile
|
||||||
|
if err == nil {
|
||||||
|
// This might succeed with mocks, which is fine
|
||||||
|
t.Log("Provider.New() succeeded with mock dependencies")
|
||||||
|
} else {
|
||||||
|
t.Logf("Provider.New() failed as expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSimpleKeyManagerFunc(t *testing.T) {
|
||||||
|
// Test that the function variable is set
|
||||||
|
if newSimpleKeyManagerFunc == nil {
|
||||||
|
t.Error("newSimpleKeyManagerFunc is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that it points to the correct function
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &mockCache{}
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
cfg := &simplekeymanager.Config{}
|
||||||
|
|
||||||
|
// This should call the actual New function
|
||||||
|
_, cleanup, err := newSimpleKeyManagerFunc(ctx, cache, registry, cfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("newSimpleKeyManagerFunc failed as expected with mocks: %v", err)
|
||||||
|
} else {
|
||||||
|
t.Log("newSimpleKeyManagerFunc succeeded with mock dependencies")
|
||||||
|
if cleanup != nil {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
377
pkg/plugin/implementation/simplekeymanager/simplekeymanager.go
Normal file
377
pkg/plugin/implementation/simplekeymanager/simplekeymanager.go
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
package simplekeymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/log"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/model"
|
||||||
|
"github.com/beckn/beckn-onix/pkg/plugin/definition"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds configuration parameters for SimpleKeyManager.
|
||||||
|
type Config struct {
|
||||||
|
NetworkParticipant string `yaml:"networkParticipant" json:"networkParticipant"`
|
||||||
|
KeyID string `yaml:"keyId" json:"keyId"`
|
||||||
|
SigningPrivateKey string `yaml:"signingPrivateKey" json:"signingPrivateKey"`
|
||||||
|
SigningPublicKey string `yaml:"signingPublicKey" json:"signingPublicKey"`
|
||||||
|
EncrPrivateKey string `yaml:"encrPrivateKey" json:"encrPrivateKey"`
|
||||||
|
EncrPublicKey string `yaml:"encrPublicKey" json:"encrPublicKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleKeyMgr provides methods for managing cryptographic keys using configuration.
|
||||||
|
type SimpleKeyMgr struct {
|
||||||
|
Registry definition.RegistryLookup
|
||||||
|
Cache definition.Cache
|
||||||
|
keysets map[string]*model.Keyset // In-memory storage for keysets
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrEmptyKeyID indicates that the provided key ID is empty.
|
||||||
|
ErrEmptyKeyID = errors.New("invalid request: keyID cannot be empty")
|
||||||
|
|
||||||
|
// ErrNilKeySet indicates that the provided keyset is nil.
|
||||||
|
ErrNilKeySet = errors.New("keyset cannot be nil")
|
||||||
|
|
||||||
|
// ErrEmptySubscriberID indicates that the provided subscriber ID is empty.
|
||||||
|
ErrEmptySubscriberID = errors.New("invalid request: subscriberID cannot be empty")
|
||||||
|
|
||||||
|
// ErrEmptyUniqueKeyID indicates that the provided unique key ID is empty.
|
||||||
|
ErrEmptyUniqueKeyID = errors.New("invalid request: uniqueKeyID cannot be empty")
|
||||||
|
|
||||||
|
// ErrSubscriberNotFound indicates that no subscriber was found with the provided credentials.
|
||||||
|
ErrSubscriberNotFound = errors.New("no subscriber found with given credentials")
|
||||||
|
|
||||||
|
// ErrNilCache indicates that the cache implementation is nil.
|
||||||
|
ErrNilCache = errors.New("cache implementation cannot be nil")
|
||||||
|
|
||||||
|
// ErrNilRegistryLookup indicates that the registry lookup implementation is nil.
|
||||||
|
ErrNilRegistryLookup = errors.New("registry lookup implementation cannot be nil")
|
||||||
|
|
||||||
|
// ErrKeysetNotFound indicates that the requested keyset was not found.
|
||||||
|
ErrKeysetNotFound = errors.New("keyset not found")
|
||||||
|
|
||||||
|
// ErrInvalidConfig indicates that the configuration is invalid.
|
||||||
|
ErrInvalidConfig = errors.New("invalid configuration")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateCfg validates the SimpleKeyManager configuration.
|
||||||
|
func ValidateCfg(cfg *Config) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return fmt.Errorf("%w: config cannot be nil", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// But if keys are provided, all must be provided
|
||||||
|
hasKeys := cfg.SigningPrivateKey != "" || cfg.SigningPublicKey != "" ||
|
||||||
|
cfg.EncrPrivateKey != "" || cfg.EncrPublicKey != "" || cfg.NetworkParticipant != "" ||
|
||||||
|
cfg.KeyID != ""
|
||||||
|
|
||||||
|
if hasKeys {
|
||||||
|
if cfg.SigningPrivateKey == "" {
|
||||||
|
return fmt.Errorf("%w: signingPrivateKey is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
if cfg.SigningPublicKey == "" {
|
||||||
|
return fmt.Errorf("%w: signingPublicKey is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
if cfg.EncrPrivateKey == "" {
|
||||||
|
return fmt.Errorf("%w: encrPrivateKey is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
if cfg.EncrPublicKey == "" {
|
||||||
|
return fmt.Errorf("%w: encrPublicKey is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
if cfg.NetworkParticipant == "" {
|
||||||
|
return fmt.Errorf("%w: networkParticipant is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
if cfg.KeyID == "" {
|
||||||
|
return fmt.Errorf("%w: keyId is required when keys are configured", ErrInvalidConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ed25519KeyGenFunc = ed25519.GenerateKey
|
||||||
|
x25519KeyGenFunc = ecdh.X25519().GenerateKey
|
||||||
|
uuidGenFunc = uuid.NewRandom
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new SimpleKeyMgr instance with the provided configuration, cache, and registry lookup.
|
||||||
|
func New(ctx context.Context, cache definition.Cache, registryLookup definition.RegistryLookup, cfg *Config) (*SimpleKeyMgr, func() error, error) {
|
||||||
|
log.Info(ctx, "Initializing SimpleKeyManager plugin")
|
||||||
|
|
||||||
|
// Validate configuration.
|
||||||
|
if err := ValidateCfg(cfg); err != nil {
|
||||||
|
log.Error(ctx, err, "Invalid configuration for SimpleKeyManager")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if cache implementation is provided.
|
||||||
|
if cache == nil {
|
||||||
|
log.Error(ctx, ErrNilCache, "Cache is nil in SimpleKeyManager initialization")
|
||||||
|
return nil, nil, ErrNilCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if registry lookup implementation is provided.
|
||||||
|
if registryLookup == nil {
|
||||||
|
log.Error(ctx, ErrNilRegistryLookup, "RegistryLookup is nil in SimpleKeyManager initialization")
|
||||||
|
return nil, nil, ErrNilRegistryLookup
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(ctx, "Creating SimpleKeyManager instance")
|
||||||
|
|
||||||
|
// Create SimpleKeyManager instance.
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
Registry: registryLookup,
|
||||||
|
Cache: cache,
|
||||||
|
keysets: make(map[string]*model.Keyset),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load keys from configuration if they exist
|
||||||
|
if err := skm.loadKeysFromConfig(ctx, cfg); err != nil {
|
||||||
|
log.Error(ctx, err, "Failed to load keys from configuration")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup function to release SimpleKeyManager resources.
|
||||||
|
cleanup := func() error {
|
||||||
|
log.Info(ctx, "Cleaning up SimpleKeyManager resources")
|
||||||
|
skm.Cache = nil
|
||||||
|
skm.Registry = nil
|
||||||
|
skm.keysets = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(ctx, "SimpleKeyManager plugin initialized successfully")
|
||||||
|
return skm, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKeyset generates a new signing (Ed25519) and encryption (X25519) key pair.
|
||||||
|
func (skm *SimpleKeyMgr) GenerateKeyset() (*model.Keyset, error) {
|
||||||
|
signingPublic, signingPrivate, err := ed25519KeyGenFunc(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate signing key pair: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encrPrivateKey, err := x25519KeyGenFunc(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate encryption key pair: %w", err)
|
||||||
|
}
|
||||||
|
encrPublicKey := encrPrivateKey.PublicKey().Bytes()
|
||||||
|
uuid, err := uuidGenFunc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate unique key id uuid: %w", err)
|
||||||
|
}
|
||||||
|
return &model.Keyset{
|
||||||
|
UniqueKeyID: uuid.String(),
|
||||||
|
SigningPrivate: encodeBase64(signingPrivate.Seed()),
|
||||||
|
SigningPublic: encodeBase64(signingPublic),
|
||||||
|
EncrPrivate: encodeBase64(encrPrivateKey.Bytes()),
|
||||||
|
EncrPublic: encodeBase64(encrPublicKey),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertKeyset stores the given keyset in memory under the specified key ID.
|
||||||
|
func (skm *SimpleKeyMgr) InsertKeyset(ctx context.Context, keyID string, keys *model.Keyset) error {
|
||||||
|
if keyID == "" {
|
||||||
|
return ErrEmptyKeyID
|
||||||
|
}
|
||||||
|
if keys == nil {
|
||||||
|
return ErrNilKeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Storing keyset for keyID: %s", keyID)
|
||||||
|
skm.keysets[keyID] = keys
|
||||||
|
log.Debugf(ctx, "Successfully stored keyset for keyID: %s", keyID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteKeyset deletes the keyset for the given key ID from memory.
|
||||||
|
func (skm *SimpleKeyMgr) DeleteKeyset(ctx context.Context, keyID string) error {
|
||||||
|
if keyID == "" {
|
||||||
|
return ErrEmptyKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Deleting keyset for keyID: %s", keyID)
|
||||||
|
if _, exists := skm.keysets[keyID]; !exists {
|
||||||
|
log.Warnf(ctx, "Keyset not found for keyID: %s", keyID)
|
||||||
|
return ErrKeysetNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(skm.keysets, keyID)
|
||||||
|
log.Debugf(ctx, "Successfully deleted keyset for keyID: %s", keyID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyset retrieves the keyset for the given key ID from memory.
|
||||||
|
func (skm *SimpleKeyMgr) Keyset(ctx context.Context, keyID string) (*model.Keyset, error) {
|
||||||
|
if keyID == "" {
|
||||||
|
return nil, ErrEmptyKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Retrieving keyset for keyID: %s", keyID)
|
||||||
|
keyset, exists := skm.keysets[keyID]
|
||||||
|
if !exists {
|
||||||
|
log.Warnf(ctx, "Keyset not found for keyID: %s", keyID)
|
||||||
|
return nil, ErrKeysetNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a copy to prevent external modifications
|
||||||
|
copyKeyset := &model.Keyset{
|
||||||
|
SubscriberID: keyset.SubscriberID,
|
||||||
|
UniqueKeyID: keyset.UniqueKeyID,
|
||||||
|
SigningPrivate: keyset.SigningPrivate,
|
||||||
|
SigningPublic: keyset.SigningPublic,
|
||||||
|
EncrPrivate: keyset.EncrPrivate,
|
||||||
|
EncrPublic: keyset.EncrPublic,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Successfully retrieved keyset for keyID: %s", keyID)
|
||||||
|
return copyKeyset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupNPKeys retrieves the signing and encryption public keys for the given subscriber ID and unique key ID.
|
||||||
|
func (skm *SimpleKeyMgr) LookupNPKeys(ctx context.Context, subscriberID, uniqueKeyID string) (string, string, error) {
|
||||||
|
if err := validateParams(subscriberID, uniqueKeyID); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheKey := fmt.Sprintf("%s_%s", subscriberID, uniqueKeyID)
|
||||||
|
cachedData, err := skm.Cache.Get(ctx, cacheKey)
|
||||||
|
if err == nil {
|
||||||
|
var keys model.Keyset
|
||||||
|
if err := json.Unmarshal([]byte(cachedData), &keys); err == nil {
|
||||||
|
log.Debugf(ctx, "Found cached keys for subscriber: %s, uniqueKeyID: %s", subscriberID, uniqueKeyID)
|
||||||
|
return keys.SigningPublic, keys.EncrPublic, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Cache miss, looking up registry for subscriber: %s, uniqueKeyID: %s", subscriberID, uniqueKeyID)
|
||||||
|
subscribers, err := skm.Registry.Lookup(ctx, &model.Subscription{
|
||||||
|
Subscriber: model.Subscriber{
|
||||||
|
SubscriberID: subscriberID,
|
||||||
|
},
|
||||||
|
KeyID: uniqueKeyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to lookup registry: %w", err)
|
||||||
|
}
|
||||||
|
if len(subscribers) == 0 {
|
||||||
|
return "", "", ErrSubscriberNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(ctx, "Successfully looked up keys for subscriber: %s, uniqueKeyID: %s", subscriberID, uniqueKeyID)
|
||||||
|
return subscribers[0].SigningPublicKey, subscribers[0].EncrPublicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadKeysFromConfig loads keys from configuration if they exist
|
||||||
|
func (skm *SimpleKeyMgr) loadKeysFromConfig(ctx context.Context, cfg *Config) error {
|
||||||
|
// Check if all keys are provided in configuration
|
||||||
|
if cfg.SigningPrivateKey != "" && cfg.SigningPublicKey != "" &&
|
||||||
|
cfg.EncrPrivateKey != "" && cfg.EncrPublicKey != "" {
|
||||||
|
|
||||||
|
log.Info(ctx, "Loading keys from configuration")
|
||||||
|
|
||||||
|
signingPrivate, err := skm.parseKey(cfg.SigningPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse signingPrivateKey: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signingPublic, err := skm.parseKey(cfg.SigningPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse signingPublicKey: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encrPrivate, err := skm.parseKey(cfg.EncrPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse encrPrivateKey: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encrPublic, err := skm.parseKey(cfg.EncrPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse encrPublicKey: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine keyID - use configured keyId or default to "default"
|
||||||
|
networkParticipant := cfg.NetworkParticipant
|
||||||
|
keyId := cfg.KeyID
|
||||||
|
|
||||||
|
// Create keyset from configuration
|
||||||
|
keyset := &model.Keyset{
|
||||||
|
SubscriberID: networkParticipant,
|
||||||
|
UniqueKeyID: keyId,
|
||||||
|
SigningPrivate: encodeBase64(signingPrivate),
|
||||||
|
SigningPublic: encodeBase64(signingPublic),
|
||||||
|
EncrPrivate: encodeBase64(encrPrivate),
|
||||||
|
EncrPublic: encodeBase64(encrPublic),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the keyset using the keyID
|
||||||
|
skm.keysets[networkParticipant] = keyset
|
||||||
|
log.Infof(ctx, "Successfully loaded keyset from configuration with keyID: %s", keyId)
|
||||||
|
} else {
|
||||||
|
log.Debug(ctx, "No keys found in configuration, keyset storage will be empty initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKey auto-detects and parses key data (PEM or base64)
|
||||||
|
func (skm *SimpleKeyMgr) parseKey(keyData string) ([]byte, error) {
|
||||||
|
keyData = strings.TrimSpace(keyData)
|
||||||
|
if keyData == "" {
|
||||||
|
return nil, fmt.Errorf("key data is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect format: if starts with "-----BEGIN", it's PEM; otherwise base64
|
||||||
|
if strings.HasPrefix(keyData, "-----BEGIN") {
|
||||||
|
return skm.parsePEMKey(keyData)
|
||||||
|
} else {
|
||||||
|
return skm.parseBase64Key(keyData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePEMKey parses PEM encoded key
|
||||||
|
func (skm *SimpleKeyMgr) parsePEMKey(keyData string) ([]byte, error) {
|
||||||
|
block, _ := pem.Decode([]byte(keyData))
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode PEM key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return block.Bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBase64Key parses base64 encoded key
|
||||||
|
func (skm *SimpleKeyMgr) parseBase64Key(keyData string) ([]byte, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode base64 key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateParams checks that subscriberID and uniqueKeyID are not empty.
|
||||||
|
func validateParams(subscriberID, uniqueKeyID string) error {
|
||||||
|
if subscriberID == "" {
|
||||||
|
return ErrEmptySubscriberID
|
||||||
|
}
|
||||||
|
if uniqueKeyID == "" {
|
||||||
|
return ErrEmptyUniqueKeyID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeBase64 returns the base64-encoded string of the given data.
|
||||||
|
func encodeBase64(data []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
package simplekeymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beckn/beckn-onix/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock implementations for testing
|
||||||
|
type mockCache struct{}
|
||||||
|
|
||||||
|
func (m *mockCache) Get(ctx context.Context, key string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Clear(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCache) Delete(ctx context.Context, key string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockRegistry struct{}
|
||||||
|
|
||||||
|
func (m *mockRegistry) Lookup(ctx context.Context, sub *model.Subscription) ([]model.Subscription, error) {
|
||||||
|
return []model.Subscription{
|
||||||
|
{
|
||||||
|
Subscriber: model.Subscriber{
|
||||||
|
SubscriberID: sub.SubscriberID,
|
||||||
|
},
|
||||||
|
KeyID: sub.KeyID,
|
||||||
|
SigningPublicKey: "test-signing-public-key",
|
||||||
|
EncrPublicKey: "test-encr-public-key",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateCfg(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
cfg: &Config{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with all keys",
|
||||||
|
cfg: &Config{
|
||||||
|
NetworkParticipant: "test-np",
|
||||||
|
KeyID: "test-key",
|
||||||
|
SigningPrivateKey: "dGVzdC1zaWduaW5nLXByaXZhdGU=",
|
||||||
|
SigningPublicKey: "dGVzdC1zaWduaW5nLXB1YmxpYw==",
|
||||||
|
EncrPrivateKey: "dGVzdC1lbmNyLXByaXZhdGU=",
|
||||||
|
EncrPublicKey: "dGVzdC1lbmNyLXB1YmxpYw==",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial keys - should fail",
|
||||||
|
cfg: &Config{
|
||||||
|
KeyID: "test-key",
|
||||||
|
SigningPrivateKey: "dGVzdC1zaWduaW5nLXByaXZhdGU=",
|
||||||
|
// Missing other keys
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := ValidateCfg(tt.cfg)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ValidateCfg() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cache := &mockCache{}
|
||||||
|
registry := &mockRegistry{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid empty config",
|
||||||
|
cfg: &Config{},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with keys",
|
||||||
|
cfg: &Config{
|
||||||
|
NetworkParticipant: "test-np",
|
||||||
|
KeyID: "test-key",
|
||||||
|
SigningPrivateKey: "dGVzdC1zaWduaW5nLXByaXZhdGU=",
|
||||||
|
SigningPublicKey: "dGVzdC1zaWduaW5nLXB1YmxpYw==",
|
||||||
|
EncrPrivateKey: "dGVzdC1lbmNyLXByaXZhdGU=",
|
||||||
|
EncrPublicKey: "dGVzdC1lbmNyLXB1YmxpYw==",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
skm, cleanup, err := New(ctx, cache, registry, tt.cfg)
|
||||||
|
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr {
|
||||||
|
if skm == nil {
|
||||||
|
t.Error("New() returned nil SimpleKeyMgr")
|
||||||
|
}
|
||||||
|
if cleanup == nil {
|
||||||
|
t.Error("New() returned nil cleanup function")
|
||||||
|
}
|
||||||
|
if cleanup != nil {
|
||||||
|
cleanup() // Test cleanup doesn't panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewWithNilDependencies(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cfg := &Config{}
|
||||||
|
|
||||||
|
// Test nil cache
|
||||||
|
_, _, err := New(ctx, nil, &mockRegistry{}, cfg)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("New() should fail with nil cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nil registry
|
||||||
|
_, _, err = New(ctx, &mockCache{}, nil, cfg)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("New() should fail with nil registry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateKeyset(t *testing.T) {
|
||||||
|
skm := &SimpleKeyMgr{}
|
||||||
|
|
||||||
|
keyset, err := skm.GenerateKeyset()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("GenerateKeyset() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyset == nil {
|
||||||
|
t.Error("GenerateKeyset() returned nil keyset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all fields are populated
|
||||||
|
if keyset.SubscriberID == "" {
|
||||||
|
t.Error("GenerateKeyset() SubscriberID is empty")
|
||||||
|
}
|
||||||
|
if keyset.UniqueKeyID == "" {
|
||||||
|
t.Error("GenerateKeyset() UniqueKeyID is empty")
|
||||||
|
}
|
||||||
|
if keyset.SigningPrivate == "" {
|
||||||
|
t.Error("GenerateKeyset() SigningPrivate is empty")
|
||||||
|
}
|
||||||
|
if keyset.SigningPublic == "" {
|
||||||
|
t.Error("GenerateKeyset() SigningPublic is empty")
|
||||||
|
}
|
||||||
|
if keyset.EncrPrivate == "" {
|
||||||
|
t.Error("GenerateKeyset() EncrPrivate is empty")
|
||||||
|
}
|
||||||
|
if keyset.EncrPublic == "" {
|
||||||
|
t.Error("GenerateKeyset() EncrPublic is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertKeyset(t *testing.T) {
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
keysets: make(map[string]*model.Keyset),
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
keyset := &model.Keyset{
|
||||||
|
SubscriberID: "test-np",
|
||||||
|
UniqueKeyID: "test-uuid",
|
||||||
|
SigningPrivate: "test-signing-private",
|
||||||
|
SigningPublic: "test-signing-public",
|
||||||
|
EncrPrivate: "test-encr-private",
|
||||||
|
EncrPublic: "test-encr-public",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test successful insertion
|
||||||
|
err := skm.InsertKeyset(ctx, "test-key", keyset)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("InsertKeyset() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify insertion
|
||||||
|
stored, exists := skm.keysets["test-key"]
|
||||||
|
if !exists {
|
||||||
|
t.Error("InsertKeyset() did not store keyset")
|
||||||
|
}
|
||||||
|
if stored != keyset {
|
||||||
|
t.Error("InsertKeyset() stored different keyset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error cases
|
||||||
|
err = skm.InsertKeyset(ctx, "", keyset)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("InsertKeyset() should fail with empty keyID")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = skm.InsertKeyset(ctx, "test-key2", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("InsertKeyset() should fail with nil keyset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyset(t *testing.T) {
|
||||||
|
originalKeyset := &model.Keyset{
|
||||||
|
SubscriberID: "test-np",
|
||||||
|
UniqueKeyID: "test-uuid",
|
||||||
|
SigningPrivate: "test-signing-private",
|
||||||
|
SigningPublic: "test-signing-public",
|
||||||
|
EncrPrivate: "test-encr-private",
|
||||||
|
EncrPublic: "test-encr-public",
|
||||||
|
}
|
||||||
|
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
keysets: map[string]*model.Keyset{
|
||||||
|
"test-key": originalKeyset,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test successful retrieval
|
||||||
|
keyset, err := skm.Keyset(ctx, "test-key")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Keyset() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyset == nil {
|
||||||
|
t.Error("Keyset() returned nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it's a copy, not the same instance
|
||||||
|
if keyset == originalKeyset {
|
||||||
|
t.Error("Keyset() should return a copy, not original")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify content is the same
|
||||||
|
if keyset.UniqueKeyID != originalKeyset.UniqueKeyID {
|
||||||
|
t.Error("Keyset() copy has different UniqueKeyID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error cases
|
||||||
|
_, err = skm.Keyset(ctx, "")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Keyset() should fail with empty keyID")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = skm.Keyset(ctx, "non-existent")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Keyset() should fail with non-existent keyID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteKeyset(t *testing.T) {
|
||||||
|
originalKeyset := &model.Keyset{
|
||||||
|
UniqueKeyID: "test-uuid",
|
||||||
|
}
|
||||||
|
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
keysets: map[string]*model.Keyset{
|
||||||
|
"test-key": originalKeyset,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test successful deletion
|
||||||
|
err := skm.DeleteKeyset(ctx, "test-key")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DeleteKeyset() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
_, exists := skm.keysets["test-key"]
|
||||||
|
if exists {
|
||||||
|
t.Error("DeleteKeyset() did not delete keyset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error cases
|
||||||
|
err = skm.DeleteKeyset(ctx, "")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("DeleteKeyset() should fail with empty keyID")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = skm.DeleteKeyset(ctx, "non-existent")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("DeleteKeyset() should fail with non-existent keyID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupNPKeys(t *testing.T) {
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
Cache: &mockCache{},
|
||||||
|
Registry: &mockRegistry{},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test successful lookup
|
||||||
|
signingKey, encrKey, err := skm.LookupNPKeys(ctx, "test-subscriber", "test-key")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("LookupNPKeys() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if signingKey == "" {
|
||||||
|
t.Error("LookupNPKeys() returned empty signing key")
|
||||||
|
}
|
||||||
|
if encrKey == "" {
|
||||||
|
t.Error("LookupNPKeys() returned empty encryption key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error cases
|
||||||
|
_, _, err = skm.LookupNPKeys(ctx, "", "test-key")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("LookupNPKeys() should fail with empty subscriberID")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = skm.LookupNPKeys(ctx, "test-subscriber", "")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("LookupNPKeys() should fail with empty uniqueKeyID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKey(t *testing.T) {
|
||||||
|
skm := &SimpleKeyMgr{}
|
||||||
|
|
||||||
|
// Test base64 key
|
||||||
|
testData := "hello world"
|
||||||
|
base64Data := base64.StdEncoding.EncodeToString([]byte(testData))
|
||||||
|
|
||||||
|
result, err := skm.parseKey(base64Data)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("parseKey() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(result) != testData {
|
||||||
|
t.Errorf("parseKey() = %s, want %s", string(result), testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test error cases
|
||||||
|
_, err = skm.parseKey("")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("parseKey() should fail with empty input")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = skm.parseKey("invalid-base64!")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("parseKey() should fail with invalid base64")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadKeysFromConfig(t *testing.T) {
|
||||||
|
skm := &SimpleKeyMgr{
|
||||||
|
keysets: make(map[string]*model.Keyset),
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test with valid config
|
||||||
|
cfg := &Config{
|
||||||
|
NetworkParticipant: "test-np",
|
||||||
|
KeyID: "test-key",
|
||||||
|
SigningPrivateKey: base64.StdEncoding.EncodeToString([]byte("signing-private")),
|
||||||
|
SigningPublicKey: base64.StdEncoding.EncodeToString([]byte("signing-public")),
|
||||||
|
EncrPrivateKey: base64.StdEncoding.EncodeToString([]byte("encr-private")),
|
||||||
|
EncrPublicKey: base64.StdEncoding.EncodeToString([]byte("encr-public")),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := skm.loadKeysFromConfig(ctx, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("loadKeysFromConfig() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify keyset was loaded
|
||||||
|
_, exists := skm.keysets["test-np"]
|
||||||
|
if !exists {
|
||||||
|
t.Error("loadKeysFromConfig() did not load keyset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with empty config (should not error)
|
||||||
|
skm2 := &SimpleKeyMgr{
|
||||||
|
keysets: make(map[string]*model.Keyset),
|
||||||
|
}
|
||||||
|
err = skm2.loadKeysFromConfig(ctx, &Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("loadKeysFromConfig() with empty config error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -339,6 +339,28 @@ func (m *Manager) KeyManager(ctx context.Context, cache definition.Cache, rClien
|
|||||||
return km, nil
|
return km, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyManager returns a KeyManager instance based on the provided configuration.
|
||||||
|
// It reuses the loaded provider.
|
||||||
|
func (m *Manager) SimpleKeyManager(ctx context.Context, cache definition.Cache, rClient definition.RegistryLookup, cfg *Config) (definition.KeyManager, error) {
|
||||||
|
|
||||||
|
kmp, err := provider[definition.KeyManagerProvider](m.plugins, cfg.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err)
|
||||||
|
}
|
||||||
|
km, closer, err := kmp.New(ctx, cache, rClient, cfg.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if closer != nil {
|
||||||
|
m.closers = append(m.closers, func() {
|
||||||
|
if err := closer(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return km, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validator implements handler.PluginManager.
|
// Validator implements handler.PluginManager.
|
||||||
func (m *Manager) Validator(ctx context.Context, cfg *Config) (definition.SchemaValidator, error) {
|
func (m *Manager) Validator(ctx context.Context, cfg *Config) (definition.SchemaValidator, error) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
|
|||||||
Reference in New Issue
Block a user