Рыба проекта. Минимальная функциональность
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package crypto
|
||||
|
||||
import "io"
|
||||
|
||||
// Decryptor defines the contract for decrypting ciphertext using private keys.
|
||||
type Decryptor interface {
|
||||
Decrypt(
|
||||
src io.Reader,
|
||||
privs []RecipientPriv,
|
||||
plaintext io.Writer,
|
||||
) error
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package crypto
|
||||
|
||||
import "io"
|
||||
|
||||
// Encryptor defines the contract for encrypting plaintext to multiple recipients.
|
||||
type Encryptor interface {
|
||||
Encrypt(
|
||||
plaintext io.Reader,
|
||||
recipients []RecipientPub,
|
||||
sink io.Writer,
|
||||
rand io.Reader,
|
||||
) error
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package crypto
|
||||
|
||||
import "io"
|
||||
|
||||
// KEM defines the contract for a Key Encapsulation Mechanism.
|
||||
type KEM interface {
|
||||
SchemeID() uint16
|
||||
GenerateKeyPair(
|
||||
rand io.Reader,
|
||||
) (
|
||||
RecipientPub,
|
||||
RecipientPriv,
|
||||
error,
|
||||
)
|
||||
Encapsulate(
|
||||
pub RecipientPub,
|
||||
rand io.Reader,
|
||||
) (
|
||||
ciphertext []byte,
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
)
|
||||
Decapsulate(
|
||||
priv RecipientPriv,
|
||||
ciphertext []byte,
|
||||
) (
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
)
|
||||
LoadPriv(
|
||||
raw []byte,
|
||||
) (
|
||||
RecipientPriv,
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
// RecipientPub represents a public recipient key.
|
||||
type RecipientPub interface {
|
||||
SchemeID() uint16
|
||||
KeyID() []byte
|
||||
Raw() []byte
|
||||
}
|
||||
|
||||
// RecipientPriv represents a private recipient key.
|
||||
type RecipientPriv interface {
|
||||
SchemeID() uint16
|
||||
KeyID() []byte
|
||||
Raw() []byte
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package crypto
|
||||
|
||||
import "io"
|
||||
|
||||
// KeyManager defines the contract for loading and generating recipient keys.
|
||||
type KeyManager interface {
|
||||
LoadPub(
|
||||
path string,
|
||||
schemeID uint16,
|
||||
) (
|
||||
RecipientPub,
|
||||
error,
|
||||
)
|
||||
LoadPriv(
|
||||
path string,
|
||||
schemeID uint16,
|
||||
) (
|
||||
RecipientPriv,
|
||||
error,
|
||||
)
|
||||
Generate(
|
||||
schemeID uint16,
|
||||
pubOut io.Writer,
|
||||
privOut io.Writer,
|
||||
rand io.Reader,
|
||||
) error
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Sentinel errors for registry operations.
|
||||
var (
|
||||
ErrUnknownScheme = errors.New("unknown KEM scheme")
|
||||
ErrDuplicateScheme = errors.New("duplicate KEM scheme registration")
|
||||
)
|
||||
|
||||
// KEMFactory is a constructor function that returns a fresh KEM instance.
|
||||
type KEMFactory func() KEM
|
||||
|
||||
// Registry is a concurrent-safe map of KEM scheme IDs to their factories.
|
||||
type Registry interface {
|
||||
Register(
|
||||
schemeID uint16,
|
||||
factory KEMFactory,
|
||||
) error
|
||||
Lookup(
|
||||
schemeID uint16,
|
||||
) (
|
||||
KEMFactory,
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
// registry is the private implementation of Registry.
|
||||
type registry struct {
|
||||
mutex sync.RWMutex
|
||||
factories map[uint16]KEMFactory
|
||||
}
|
||||
|
||||
// NewRegistry creates a new empty Registry.
|
||||
func NewRegistry() Registry {
|
||||
return ®istry{
|
||||
factories: make(map[uint16]KEMFactory),
|
||||
}
|
||||
}
|
||||
|
||||
// Register adds a KEMFactory for the given schemeID.
|
||||
// Returns ErrDuplicateScheme if the schemeID is already registered.
|
||||
func (r *registry) Register(schemeID uint16, factory KEMFactory) error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
if _, exists := r.factories[schemeID]; exists {
|
||||
return fmt.Errorf(
|
||||
"scheme 0x%04x: %w",
|
||||
schemeID,
|
||||
ErrDuplicateScheme,
|
||||
)
|
||||
}
|
||||
|
||||
r.factories[schemeID] = factory
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup retrieves the KEMFactory for the given schemeID.
|
||||
// Returns ErrUnknownScheme if the schemeID is not registered.
|
||||
func (r *registry) Lookup(
|
||||
schemeID uint16,
|
||||
) (
|
||||
KEMFactory,
|
||||
error,
|
||||
) {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
|
||||
factory, exists := r.factories[schemeID]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf(
|
||||
"scheme 0x%04x: %w",
|
||||
schemeID,
|
||||
ErrUnknownScheme,
|
||||
)
|
||||
}
|
||||
|
||||
return factory, nil
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mockKEM is a minimal KEM implementation for testing the registry.
|
||||
type mockKEM struct {
|
||||
schemeID uint16
|
||||
}
|
||||
|
||||
func (m *mockKEM) SchemeID() uint16 {
|
||||
return m.schemeID
|
||||
}
|
||||
|
||||
func (m *mockKEM) GenerateKeyPair(
|
||||
rand io.Reader,
|
||||
) (
|
||||
RecipientPub,
|
||||
RecipientPriv,
|
||||
error,
|
||||
) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (m *mockKEM) Encapsulate(
|
||||
pub RecipientPub,
|
||||
rand io.Reader,
|
||||
) (
|
||||
ciphertext []byte,
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (m *mockKEM) Decapsulate(
|
||||
priv RecipientPriv,
|
||||
ciphertext []byte,
|
||||
) (
|
||||
sharedSecret []byte,
|
||||
err error,
|
||||
) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockKEM) LoadPriv(
|
||||
raw []byte,
|
||||
) (
|
||||
RecipientPriv,
|
||||
error,
|
||||
) {
|
||||
return &mockRecipient{schemeIDValue: m.schemeID, rawBytes: raw}, nil
|
||||
}
|
||||
|
||||
// mockRecipient implements RecipientPub and RecipientPriv for tests.
|
||||
type mockRecipient struct {
|
||||
schemeIDValue uint16
|
||||
rawBytes []byte
|
||||
}
|
||||
|
||||
func (r *mockRecipient) SchemeID() uint16 {
|
||||
return r.schemeIDValue
|
||||
}
|
||||
|
||||
func (r *mockRecipient) KeyID() []byte {
|
||||
return r.rawBytes
|
||||
}
|
||||
|
||||
func (r *mockRecipient) Raw() []byte {
|
||||
return r.rawBytes
|
||||
}
|
||||
|
||||
func TestRegistry_RegisterAndLookup(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
factory := func() KEM {
|
||||
return &mockKEM{schemeID: 0x0001}
|
||||
}
|
||||
|
||||
err := registry.Register(0x0001, factory)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error registering scheme: %v", err)
|
||||
}
|
||||
|
||||
foundFactory, err := registry.Lookup(0x0001)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error looking up scheme: %v", err)
|
||||
}
|
||||
|
||||
kem := foundFactory()
|
||||
if kem.SchemeID() != 0x0001 {
|
||||
t.Errorf("expected schemeID 0x0001, got 0x%04x", kem.SchemeID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_LookupUnknownScheme(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
|
||||
_, err := registry.Lookup(0x9999)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown scheme, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, ErrUnknownScheme) {
|
||||
t.Errorf("expected ErrUnknownScheme, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_RegisterDuplicateScheme(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
factory := func() KEM {
|
||||
return &mockKEM{schemeID: 0x0001}
|
||||
}
|
||||
|
||||
err := registry.Register(0x0001, factory)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on first register: %v", err)
|
||||
}
|
||||
|
||||
err = registry.Register(0x0001, factory)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for duplicate scheme, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, ErrDuplicateScheme) {
|
||||
t.Errorf("expected ErrDuplicateScheme, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_ConcurrentAccess(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
const goroutines = 100
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
waitGroup.Add(goroutines)
|
||||
|
||||
for index := range goroutines {
|
||||
go func(schemeID uint16) {
|
||||
defer waitGroup.Done()
|
||||
|
||||
factory := func() KEM {
|
||||
return &mockKEM{schemeID: schemeID}
|
||||
}
|
||||
|
||||
_ = registry.Register(schemeID, factory)
|
||||
_, _ = registry.Lookup(schemeID)
|
||||
}(uint16(index + 1))
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
}
|
||||
|
||||
func TestRegistry_LookupReturnsIndependentInstances(t *testing.T) {
|
||||
registry := NewRegistry()
|
||||
factory := func() KEM {
|
||||
return &mockKEM{schemeID: 0x0001}
|
||||
}
|
||||
|
||||
_ = registry.Register(0x0001, factory)
|
||||
|
||||
factoryOne, _ := registry.Lookup(0x0001)
|
||||
factoryTwo, _ := registry.Lookup(0x0001)
|
||||
|
||||
kemOne := factoryOne()
|
||||
kemTwo := factoryTwo()
|
||||
|
||||
if kemOne == kemTwo {
|
||||
t.Error("expected independent KEM instances from factory")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user