All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
740 lines
18 KiB
Go
740 lines
18 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.wntrmute.dev/mc/metacrypt/internal/barrier"
|
|
"git.wntrmute.dev/mc/metacrypt/internal/engine"
|
|
)
|
|
|
|
// memBarrier is an in-memory barrier for testing.
|
|
type memBarrier struct {
|
|
data map[string][]byte
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func newMemBarrier() *memBarrier {
|
|
return &memBarrier{data: make(map[string][]byte)}
|
|
}
|
|
|
|
func (m *memBarrier) Unseal(_ []byte) error { return nil }
|
|
func (m *memBarrier) Seal() error { return nil }
|
|
func (m *memBarrier) IsSealed() bool { return false }
|
|
|
|
func (m *memBarrier) Get(_ context.Context, path string) ([]byte, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
v, ok := m.data[path]
|
|
if !ok {
|
|
return nil, barrier.ErrNotFound
|
|
}
|
|
cp := make([]byte, len(v))
|
|
copy(cp, v)
|
|
return cp, nil
|
|
}
|
|
|
|
func (m *memBarrier) Put(_ context.Context, path string, value []byte) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := make([]byte, len(value))
|
|
copy(cp, value)
|
|
m.data[path] = cp
|
|
return nil
|
|
}
|
|
|
|
func (m *memBarrier) Delete(_ context.Context, path string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.data, path)
|
|
return nil
|
|
}
|
|
|
|
func (m *memBarrier) List(_ context.Context, prefix string) ([]string, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
var paths []string
|
|
for k := range m.data {
|
|
if strings.HasPrefix(k, prefix) {
|
|
paths = append(paths, strings.TrimPrefix(k, prefix))
|
|
}
|
|
}
|
|
return paths, nil
|
|
}
|
|
|
|
func adminCaller() *engine.CallerInfo {
|
|
return &engine.CallerInfo{Username: "admin", Roles: []string{"admin"}, IsAdmin: true}
|
|
}
|
|
|
|
func userCaller(name string) *engine.CallerInfo {
|
|
return &engine.CallerInfo{Username: name, Roles: []string{"user"}, IsAdmin: false}
|
|
}
|
|
|
|
func guestCaller() *engine.CallerInfo {
|
|
return &engine.CallerInfo{Username: "guest", Roles: []string{"guest"}, IsAdmin: false}
|
|
}
|
|
|
|
func setupEngine(t *testing.T) (*UserEngine, *memBarrier) {
|
|
t.Helper()
|
|
b := newMemBarrier()
|
|
eng := NewUserEngine().(*UserEngine) //nolint:errcheck
|
|
ctx := context.Background()
|
|
|
|
config := map[string]interface{}{
|
|
"key_algorithm": "x25519",
|
|
"sym_algorithm": "aes256-gcm",
|
|
}
|
|
|
|
if err := eng.Initialize(ctx, b, "engine/user/test/", config); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
return eng, b
|
|
}
|
|
|
|
func TestInitializeAndUnseal(t *testing.T) {
|
|
eng, b := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
// Register a user.
|
|
resp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
if resp.Data["username"] != "alice" {
|
|
t.Fatalf("expected username alice, got %v", resp.Data["username"])
|
|
}
|
|
|
|
// Seal and unseal.
|
|
if err := eng.Seal(); err != nil {
|
|
t.Fatalf("seal: %v", err)
|
|
}
|
|
|
|
eng2 := NewUserEngine().(*UserEngine) //nolint:errcheck
|
|
if err := eng2.Unseal(ctx, b, "engine/user/test/"); err != nil {
|
|
t.Fatalf("unseal: %v", err)
|
|
}
|
|
|
|
// Verify alice's key is loaded.
|
|
resp, err = eng2.HandleRequest(ctx, &engine.Request{
|
|
Operation: "get-public-key",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"username": "alice"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("get-public-key after unseal: %v", err)
|
|
}
|
|
if resp.Data["username"] != "alice" {
|
|
t.Fatalf("expected alice, got %v", resp.Data["username"])
|
|
}
|
|
}
|
|
|
|
func TestRegisterCreatesKeypair(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
resp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
|
|
pubKey, ok := resp.Data["public_key"].(string)
|
|
if !ok || pubKey == "" {
|
|
t.Fatal("expected non-empty public key")
|
|
}
|
|
|
|
// Decode to verify it's valid base64.
|
|
raw, err := base64.StdEncoding.DecodeString(pubKey)
|
|
if err != nil {
|
|
t.Fatalf("decode public key: %v", err)
|
|
}
|
|
if len(raw) != 32 { // X25519 public key is 32 bytes
|
|
t.Fatalf("expected 32-byte public key, got %d", len(raw))
|
|
}
|
|
}
|
|
|
|
func TestRegisterIdempotent(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
resp1, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register 1: %v", err)
|
|
}
|
|
|
|
resp2, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register 2: %v", err)
|
|
}
|
|
|
|
if resp1.Data["public_key"] != resp2.Data["public_key"] {
|
|
t.Fatal("register should be idempotent")
|
|
}
|
|
}
|
|
|
|
func TestEncryptDecryptSingleRecipient(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
// Register alice and bob.
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// Alice encrypts to bob.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "hello bob",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
envelope, ok := encResp.Data["envelope"].(string)
|
|
if !ok || envelope == "" {
|
|
t.Fatal("expected non-empty envelope")
|
|
}
|
|
|
|
// Bob decrypts.
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": envelope},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt: %v", err)
|
|
}
|
|
|
|
if decResp.Data["plaintext"] != "hello bob" {
|
|
t.Fatalf("expected 'hello bob', got %v", decResp.Data["plaintext"])
|
|
}
|
|
if decResp.Data["sender"] != "alice" {
|
|
t.Fatalf("expected sender alice, got %v", decResp.Data["sender"])
|
|
}
|
|
}
|
|
|
|
func TestEncryptDecryptWithMetadata(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "secret message",
|
|
"metadata": "important context",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt: %v", err)
|
|
}
|
|
if decResp.Data["plaintext"] != "secret message" {
|
|
t.Fatalf("plaintext mismatch: %v", decResp.Data["plaintext"])
|
|
}
|
|
if decResp.Data["metadata"] != "important context" {
|
|
t.Fatalf("metadata mismatch: %v", decResp.Data["metadata"])
|
|
}
|
|
}
|
|
|
|
func TestMultiRecipientEncryptDecrypt(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
users := []string{"alice", "bob", "charlie"}
|
|
for _, name := range users {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// Alice encrypts to bob and charlie.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "hello everyone",
|
|
"recipients": []interface{}{"bob", "charlie"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
envelope := encResp.Data["envelope"].(string)
|
|
|
|
// Both bob and charlie can decrypt.
|
|
for _, name := range []string{"bob", "charlie"} {
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller(name),
|
|
Data: map[string]interface{}{"envelope": envelope},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt by %s: %v", name, err)
|
|
}
|
|
if decResp.Data["plaintext"] != "hello everyone" {
|
|
t.Fatalf("%s: expected 'hello everyone', got %v", name, decResp.Data["plaintext"])
|
|
}
|
|
}
|
|
|
|
// Alice (not a recipient) cannot decrypt.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{"envelope": envelope},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error when non-recipient decrypts")
|
|
}
|
|
}
|
|
|
|
func TestReEncrypt(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// Alice encrypts to bob.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "secret",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
// Bob re-encrypts.
|
|
reEncResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "re-encrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("re-encrypt: %v", err)
|
|
}
|
|
|
|
// Bob can decrypt re-encrypted envelope.
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": reEncResp.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt re-encrypted: %v", err)
|
|
}
|
|
if decResp.Data["plaintext"] != "secret" {
|
|
t.Fatalf("expected 'secret', got %v", decResp.Data["plaintext"])
|
|
}
|
|
if decResp.Data["sender"] != "bob" {
|
|
t.Fatalf("expected sender bob after re-encrypt, got %v", decResp.Data["sender"])
|
|
}
|
|
}
|
|
|
|
func TestRotateKey(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// Alice encrypts to bob.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "before rotation",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
// Bob rotates key.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "rotate-key",
|
|
CallerInfo: userCaller("bob"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("rotate-key: %v", err)
|
|
}
|
|
|
|
// Old envelope should fail to decrypt (sender's pubkey is used to unwrap,
|
|
// but the DEK was wrapped with old recipient key).
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected decrypt to fail after key rotation")
|
|
}
|
|
|
|
// New encrypt/decrypt should work.
|
|
encResp2, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "after rotation",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt after rotation: %v", err)
|
|
}
|
|
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp2.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt after rotation: %v", err)
|
|
}
|
|
if decResp.Data["plaintext"] != "after rotation" {
|
|
t.Fatalf("expected 'after rotation', got %v", decResp.Data["plaintext"])
|
|
}
|
|
}
|
|
|
|
func TestAutoProvisionOnEncrypt(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
// Encrypt without pre-registering anyone.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "auto-provision test",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
// Both alice and bob should be auto-provisioned. bob can decrypt.
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt: %v", err)
|
|
}
|
|
if decResp.Data["plaintext"] != "auto-provision test" {
|
|
t.Fatalf("expected 'auto-provision test', got %v", decResp.Data["plaintext"])
|
|
}
|
|
}
|
|
|
|
func TestProvisionAdminOnly(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
// Non-admin cannot provision.
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "provision",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{"username": "bob"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for non-admin provision")
|
|
}
|
|
|
|
// Admin can provision.
|
|
resp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "provision",
|
|
CallerInfo: adminCaller(),
|
|
Data: map[string]interface{}{"username": "bob"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("admin provision: %v", err)
|
|
}
|
|
if resp.Data["username"] != "bob" {
|
|
t.Fatalf("expected bob, got %v", resp.Data["username"])
|
|
}
|
|
}
|
|
|
|
func TestDecryptSelfOnly(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
for _, name := range []string{"alice", "bob", "charlie"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// Alice encrypts to bob only.
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "for bob only",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
// Charlie cannot decrypt.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("charlie"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error when charlie tries to decrypt bob's message")
|
|
}
|
|
}
|
|
|
|
func TestGuestRejected(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "list-users",
|
|
CallerInfo: guestCaller(),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected guest to be rejected from list-users")
|
|
}
|
|
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "get-public-key",
|
|
CallerInfo: guestCaller(),
|
|
Data: map[string]interface{}{"username": "alice"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected guest to be rejected from get-public-key")
|
|
}
|
|
}
|
|
|
|
func TestDeleteUser(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
// Register bob.
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("bob"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
|
|
// Non-admin cannot delete.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "delete-user",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"username": "bob"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for non-admin delete")
|
|
}
|
|
|
|
// Admin can delete.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "delete-user",
|
|
CallerInfo: adminCaller(),
|
|
Data: map[string]interface{}{"username": "bob"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("admin delete: %v", err)
|
|
}
|
|
|
|
// bob should no longer exist.
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "get-public-key",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{"username": "bob"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected user not found after delete")
|
|
}
|
|
}
|
|
|
|
func TestMaxRecipientsLimit(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register: %v", err)
|
|
}
|
|
|
|
// Build 101 recipients.
|
|
recipients := make([]interface{}, 101)
|
|
for i := range recipients {
|
|
recipients[i] = "user" + strings.Repeat("x", 5) + string(rune('a'+i%26)) + string(rune('0'+i/26))
|
|
}
|
|
|
|
_, err = eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "test",
|
|
"recipients": recipients,
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for too many recipients")
|
|
}
|
|
if !strings.Contains(err.Error(), "too many recipients") {
|
|
t.Fatalf("expected 'too many recipients' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListUsers(t *testing.T) {
|
|
eng, _ := setupEngine(t)
|
|
ctx := context.Background()
|
|
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
resp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "list-users",
|
|
CallerInfo: userCaller("alice"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list-users: %v", err)
|
|
}
|
|
|
|
users, ok := resp.Data["users"].([]interface{})
|
|
if !ok {
|
|
t.Fatal("expected users list")
|
|
}
|
|
if len(users) != 2 {
|
|
t.Fatalf("expected 2 users, got %d", len(users))
|
|
}
|
|
}
|
|
|
|
func TestP256Algorithm(t *testing.T) {
|
|
b := newMemBarrier()
|
|
eng := NewUserEngine().(*UserEngine) //nolint:errcheck
|
|
ctx := context.Background()
|
|
|
|
config := map[string]interface{}{
|
|
"key_algorithm": "ecdh-p256",
|
|
}
|
|
if err := eng.Initialize(ctx, b, "engine/user/p256/", config); err != nil {
|
|
t.Fatalf("Initialize: %v", err)
|
|
}
|
|
|
|
for _, name := range []string{"alice", "bob"} {
|
|
_, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "register",
|
|
CallerInfo: userCaller(name),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("register %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
encResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "encrypt",
|
|
CallerInfo: userCaller("alice"),
|
|
Data: map[string]interface{}{
|
|
"plaintext": "p256 test",
|
|
"recipients": []interface{}{"bob"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("encrypt: %v", err)
|
|
}
|
|
|
|
decResp, err := eng.HandleRequest(ctx, &engine.Request{
|
|
Operation: "decrypt",
|
|
CallerInfo: userCaller("bob"),
|
|
Data: map[string]interface{}{"envelope": encResp.Data["envelope"]},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("decrypt: %v", err)
|
|
}
|
|
if decResp.Data["plaintext"] != "p256 test" {
|
|
t.Fatalf("expected 'p256 test', got %v", decResp.Data["plaintext"])
|
|
}
|
|
}
|