Work on making SecretKeys type-safe
This commit is contained in:
62
I2PKeyTypes.go
Normal file
62
I2PKeyTypes.go
Normal file
@ -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)
|
||||||
|
}
|
@ -1,52 +1,73 @@
|
|||||||
|
// i2p_secret_key.go
|
||||||
package i2pkeys
|
package i2pkeys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecretKey is a private key interface
|
// SecretKey returns a type-safe secret key implementation
|
||||||
type SecretKey interface {
|
func (k I2PKeys) SecretKey() (SecretKeyProvider, error) {
|
||||||
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err 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 {
|
// PrivateKey returns the crypto.PrivateKey interface implementation
|
||||||
var pk ed25519.PrivateKey = k.Private()
|
func (k I2PKeys) PrivateKey() (crypto.PrivateKey, error) {
|
||||||
return pk
|
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 {
|
// Ed25519PrivateKey safely converts to ed25519.PrivateKey
|
||||||
var pk ed25519.PrivateKey = k.Private()
|
func (k I2PKeys) Ed25519PrivateKey() (ed25519.PrivateKey, error) {
|
||||||
_, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0))
|
sk, err := k.SecretKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Warn("Error in private key signature")
|
return nil, err
|
||||||
// TODO: Elgamal, P256, P384, P512, GOST? keys?
|
}
|
||||||
}
|
|
||||||
return pk
|
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 {
|
// Sign implements crypto.Signer
|
||||||
return k.SecretKey().(*ed25519.PrivateKey)
|
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)
|
||||||
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)
|
|
||||||
}
|
sig, err := sk.Sign(rand, digest, opts)
|
||||||
*/
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrSigningFailed, err)
|
||||||
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)
|
|
||||||
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HostnameEntry creates a signed hostname entry
|
||||||
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
|
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
|
||||||
sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
|
if hostname == "" {
|
||||||
if err != nil {
|
return "", errors.New("empty hostname")
|
||||||
log.WithError(err).Error("Error signing hostname")
|
}
|
||||||
return "", fmt.Errorf("error signing hostname: %w", err)
|
|
||||||
}
|
sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
|
||||||
return string(sig), nil
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("signing hostname: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(sig), nil
|
||||||
}
|
}
|
10
go.mod
10
go.mod
@ -2,9 +2,9 @@ module github.com/go-i2p/i2pkeys
|
|||||||
|
|
||||||
go 1.23.3
|
go 1.23.3
|
||||||
|
|
||||||
require (
|
require github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c
|
||||||
github.com/go-i2p/logger v0.0.0-20241121222244-5ae974ee455c
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
|
4
go.sum
4
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI=
|
||||||
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/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
56
test_I2PSecretKeys.go
Normal file
56
test_I2PSecretKeys.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user