From 8587d33d3af2e828b62d58be0bc54176c14bd631 Mon Sep 17 00:00:00 2001 From: eyedeekay Date: Fri, 29 Nov 2024 18:21:01 -0500 Subject: [PATCH] Work on making SecretKeys type-safe --- I2PKeyTypes.go | 62 ++++++++++++++++++++++++++++++ I2PSecretKey.go | 87 +++++++++++++++++++++++++++---------------- go.mod | 10 ++--- go.sum | 4 +- test_I2PSecretKeys.go | 56 ++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 I2PKeyTypes.go create mode 100644 test_I2PSecretKeys.go diff --git a/I2PKeyTypes.go b/I2PKeyTypes.go new file mode 100644 index 0000000..3d4aeb4 --- /dev/null +++ b/I2PKeyTypes.go @@ -0,0 +1,62 @@ +// i2p_keys.go +package i2pkeys + +import ( + "crypto" + "crypto/ed25519" + "errors" + "fmt" + "io" +) + +var ( + ErrInvalidKeyType = errors.New("invalid key type") + ErrSigningFailed = errors.New("signing operation failed") +) + +// KeyType represents supported key algorithms +type KeyType int + +const ( + KeyTypeEd25519 KeyType = iota + KeyTypeElgamal + // Add other key types as needed +) + +// SecretKeyProvider extends the basic crypto interfaces +type SecretKeyProvider interface { + crypto.Signer + Type() KeyType + Raw() []byte +} + +// Ed25519SecretKey provides a type-safe wrapper +type Ed25519SecretKey struct { + key ed25519.PrivateKey +} + +func NewEd25519SecretKey(key ed25519.PrivateKey) (*Ed25519SecretKey, error) { + if len(key) != ed25519.PrivateKeySize { + return nil, fmt.Errorf("%w: invalid Ed25519 key size", ErrInvalidKeyType) + } + return &Ed25519SecretKey{key: key}, nil +} + +func (k *Ed25519SecretKey) Type() KeyType { + return KeyTypeEd25519 +} + +func (k *Ed25519SecretKey) Raw() []byte { + return k.key +} + +func (k *Ed25519SecretKey) Public() crypto.PublicKey { + return k.key.Public() +} + +func (k *Ed25519SecretKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + if k == nil || len(k.key) != ed25519.PrivateKeySize { + return nil, fmt.Errorf("%w: invalid key state", ErrInvalidKeyType) + } + return k.key.Sign(rand, digest, opts) +} \ No newline at end of file diff --git a/I2PSecretKey.go b/I2PSecretKey.go index 22e874a..906a754 100644 --- a/I2PSecretKey.go +++ b/I2PSecretKey.go @@ -1,52 +1,73 @@ +// i2p_secret_key.go package i2pkeys import ( "crypto" "crypto/ed25519" "crypto/rand" + "errors" "fmt" "io" ) -// SecretKey is a private key interface -type SecretKey interface { - Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) +// SecretKey returns a type-safe secret key implementation +func (k I2PKeys) SecretKey() (SecretKeyProvider, error) { + rawKey := k.Private() + if len(rawKey) != ed25519.PrivateKeySize { + return nil, fmt.Errorf("%w: expected Ed25519 key", ErrInvalidKeyType) + } + + return NewEd25519SecretKey(ed25519.PrivateKey(rawKey)) } -func (k I2PKeys) SecretKey() SecretKey { - var pk ed25519.PrivateKey = k.Private() - return pk +// PrivateKey returns the crypto.PrivateKey interface implementation +func (k I2PKeys) PrivateKey() (crypto.PrivateKey, error) { + sk, err := k.SecretKey() + if err != nil { + return nil, fmt.Errorf("getting secret key: %w", err) + } + return sk, nil } -func (k I2PKeys) PrivateKey() crypto.PrivateKey { - var pk ed25519.PrivateKey = k.Private() - _, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0)) - if err != nil { - log.WithError(err).Warn("Error in private key signature") - // TODO: Elgamal, P256, P384, P512, GOST? keys? - } - return pk +// Ed25519PrivateKey safely converts to ed25519.PrivateKey +func (k I2PKeys) Ed25519PrivateKey() (ed25519.PrivateKey, error) { + sk, err := k.SecretKey() + if err != nil { + return nil, err + } + + if sk.Type() != KeyTypeEd25519 { + return nil, fmt.Errorf("%w: not an Ed25519 key", ErrInvalidKeyType) + } + + return ed25519.PrivateKey(sk.Raw()), nil } -func (k I2PKeys) Ed25519PrivateKey() *ed25519.PrivateKey { - return k.SecretKey().(*ed25519.PrivateKey) -} - -/* -func (k I2PKeys) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) { - return k.SecretKey().(*ed25519.PrivateKey).Decrypt(rand, msg, opts) -} -*/ - -func (k I2PKeys) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { - return k.SecretKey().(*ed25519.PrivateKey).Sign(rand, digest, opts) +// Sign implements crypto.Signer +func (k I2PKeys) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + sk, err := k.SecretKey() + if err != nil { + return nil, fmt.Errorf("getting secret key: %w", err) + } + + sig, err := sk.Sign(rand, digest, opts) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrSigningFailed, err) + } + + return sig, nil } +// HostnameEntry creates a signed hostname entry func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) { - sig, err := k.Sign(rand.Reader, []byte(hostname), opts) - if err != nil { - log.WithError(err).Error("Error signing hostname") - return "", fmt.Errorf("error signing hostname: %w", err) - } - return string(sig), nil -} + if hostname == "" { + return "", errors.New("empty hostname") + } + + sig, err := k.Sign(rand.Reader, []byte(hostname), opts) + if err != nil { + return "", fmt.Errorf("signing hostname: %w", err) + } + + return string(sig), nil +} \ No newline at end of file diff --git a/go.mod b/go.mod index 7377fe4..f5ed652 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,9 @@ module github.com/go-i2p/i2pkeys go 1.23.3 -require ( - github.com/go-i2p/logger v0.0.0-20241121222244-5ae974ee455c - github.com/sirupsen/logrus v1.9.3 -) +require github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c -require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +require ( + github.com/sirupsen/logrus v1.9.3 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum index 8776b7f..4f5412c 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-i2p/logger v0.0.0-20241121222244-5ae974ee455c h1:7qg6AGtzDZDF2fquNn/oHcJwRF/27tKU6A165+bWvVo= -github.com/go-i2p/logger v0.0.0-20241121222244-5ae974ee455c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk= +github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI= +github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/test_I2PSecretKeys.go b/test_I2PSecretKeys.go new file mode 100644 index 0000000..706daea --- /dev/null +++ b/test_I2PSecretKeys.go @@ -0,0 +1,56 @@ +package i2pkeys + +import ( + "crypto" + "crypto/ed25519" + "crypto/rand" + "testing" +) + +func TestSecretKeyOperations(t *testing.T) { + // Generate test keys + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("Failed to generate test keys: %v", err) + } + + keys := I2PKeys{ + Address: I2PAddr(pub), + Both: string(priv), + } + + t.Run("SecretKey", func(t *testing.T) { + sk, err := keys.SecretKey() + if err != nil { + t.Fatalf("SecretKey() error = %v", err) + } + + if sk.Type() != KeyTypeEd25519 { + t.Errorf("Wrong key type, got %v, want %v", sk.Type(), KeyTypeEd25519) + } + }) + + t.Run("Sign", func(t *testing.T) { + message := []byte("test message") + sig, err := keys.Sign(rand.Reader, message, crypto.Hash(0)) + if err != nil { + t.Fatalf("Sign() error = %v", err) + } + + if !ed25519.Verify(pub, message, sig) { + t.Error("Signature verification failed") + } + }) + + t.Run("HostnameEntry", func(t *testing.T) { + hostname := "test.i2p" + entry, err := keys.HostnameEntry(hostname, crypto.Hash(0)) + if err != nil { + t.Fatalf("HostnameEntry() error = %v", err) + } + + if entry == "" { + t.Error("Empty hostname entry") + } + }) +} \ No newline at end of file