Migrate to go-i2p/crypto package for cryptographic operations

This commit is contained in:
eyedeekay
2025-11-24 11:02:32 -05:00
parent a2d229e3e2
commit ed91172d4f
8 changed files with 477 additions and 114 deletions

View File

@@ -1,26 +1,37 @@
// Migration Notes (November 24, 2025):
// Migrated to use github.com/go-i2p/crypto/chacha20poly1305 package which provides
// standardized ChaCha20-Poly1305 AEAD (Authenticated Encryption with Associated Data).
//
// Key Changes:
// - ChaCha20Poly1305Cipher now wraps crypto/chacha20poly1305.AEAD
// - NewChaCha20Poly1305Cipher() delegates to chacha20poly1305.GenerateKey() and NewAEAD()
// - Encrypt/Decrypt maintain [nonce][ciphertext+tag] format for backward compatibility
// - Crypto package separates tag from ciphertext; wrapper combines them for I2CP compatibility
//
// The crypto package uses explicit tag separation (AEAD standard), while I2CP traditionally
// combines nonce+ciphertext+tag. The wrapper maintains I2CP format compatibility.
package go_i2cp
import (
"crypto/cipher"
"crypto/rand"
"fmt"
"golang.org/x/crypto/chacha20poly1305"
cryptoaead "github.com/go-i2p/crypto/chacha20poly1305"
)
// ChaCha20Poly1305Cipher provides authenticated encryption using ChaCha20-Poly1305
// Wraps github.com/go-i2p/crypto/chacha20poly1305.AEAD
type ChaCha20Poly1305Cipher struct {
algorithmType uint32
aead cipher.AEAD
aead *cryptoaead.AEAD
key [32]byte
}
// NewChaCha20Poly1305Cipher creates a new ChaCha20-Poly1305 cipher with a random key
// Delegates to github.com/go-i2p/crypto/chacha20poly1305.GenerateKey() and NewAEAD()
func NewChaCha20Poly1305Cipher() (*ChaCha20Poly1305Cipher, error) {
var key [32]byte
// Generate random 256-bit key
_, err := rand.Read(key[:])
// Generate random 256-bit key using crypto package
key, err := cryptoaead.GenerateKey()
if err != nil {
return nil, fmt.Errorf("failed to generate ChaCha20-Poly1305 key: %w", err)
}
@@ -29,8 +40,9 @@ func NewChaCha20Poly1305Cipher() (*ChaCha20Poly1305Cipher, error) {
}
// NewChaCha20Poly1305CipherWithKey creates a new ChaCha20-Poly1305 cipher with the provided key
// Delegates to github.com/go-i2p/crypto/chacha20poly1305.NewAEAD()
func NewChaCha20Poly1305CipherWithKey(key [32]byte) (*ChaCha20Poly1305Cipher, error) {
aead, err := chacha20poly1305.New(key[:])
aead, err := cryptoaead.NewAEAD(key)
if err != nil {
return nil, fmt.Errorf("failed to create ChaCha20-Poly1305 AEAD: %w", err)
}
@@ -66,25 +78,31 @@ func ChaCha20Poly1305CipherFromStream(stream *Stream) (*ChaCha20Poly1305Cipher,
}
// Encrypt encrypts plaintext with optional associated data using ChaCha20-Poly1305
// Returns [nonce][ciphertext+tag] format for I2CP compatibility
// Uses github.com/go-i2p/crypto/chacha20poly1305.AEAD.Encrypt()
func (c *ChaCha20Poly1305Cipher) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
if c.aead == nil {
return nil, fmt.Errorf("cipher not initialized")
}
// Generate random nonce
nonce := make([]byte, c.aead.NonceSize())
_, err := rand.Read(nonce)
// Generate random nonce using crypto package
nonce, err := cryptoaead.GenerateNonce()
if err != nil {
return nil, fmt.Errorf("failed to generate nonce: %w", err)
}
// Encrypt plaintext
ciphertext := c.aead.Seal(nil, nonce, plaintext, additionalData)
// Encrypt plaintext - crypto package returns separate ciphertext and tag
ciphertext, tag, err := c.aead.Encrypt(plaintext, additionalData, nonce[:])
if err != nil {
return nil, fmt.Errorf("failed to encrypt: %w", err)
}
// Prepend nonce to ciphertext for transmission
result := make([]byte, len(nonce)+len(ciphertext))
copy(result, nonce)
// Combine nonce, ciphertext, and tag for I2CP compatibility
// Format: [nonce][ciphertext][tag]
result := make([]byte, len(nonce)+len(ciphertext)+len(tag))
copy(result, nonce[:])
copy(result[len(nonce):], ciphertext)
copy(result[len(nonce)+len(ciphertext):], tag[:])
return result, nil
}
@@ -110,22 +128,32 @@ func (c *ChaCha20Poly1305Cipher) EncryptStream(src, dst *Stream, additionalData
}
// Decrypt decrypts ciphertext with optional associated data using ChaCha20-Poly1305
// Expects [nonce][ciphertext+tag] format for I2CP compatibility
// Uses github.com/go-i2p/crypto/chacha20poly1305.AEAD.Decrypt()
func (c *ChaCha20Poly1305Cipher) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
if c.aead == nil {
return nil, fmt.Errorf("cipher not initialized")
}
nonceSize := c.aead.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short: need at least %d bytes for nonce", nonceSize)
nonceSize := cryptoaead.NonceSize
tagSize := cryptoaead.TagSize
minSize := nonceSize + tagSize
if len(ciphertext) < minSize {
return nil, fmt.Errorf("ciphertext too short: need at least %d bytes (nonce + tag)", minSize)
}
// Extract nonce and actual ciphertext
// Extract nonce, ciphertext, and tag from combined format
nonce := ciphertext[:nonceSize]
actualCiphertext := ciphertext[nonceSize:]
actualCiphertextWithTag := ciphertext[nonceSize:]
// Decrypt ciphertext
plaintext, err := c.aead.Open(nil, nonce, actualCiphertext, additionalData)
// Split ciphertext and tag
actualCiphertext := actualCiphertextWithTag[:len(actualCiphertextWithTag)-tagSize]
var tag [cryptoaead.TagSize]byte
copy(tag[:], actualCiphertextWithTag[len(actualCiphertext):])
// Decrypt using crypto package - pass tag separately
plaintext, err := c.aead.Decrypt(actualCiphertext, tag[:], additionalData, nonce)
if err != nil {
return nil, fmt.Errorf("failed to decrypt: %w", err)
}
@@ -186,16 +214,10 @@ func (c *ChaCha20Poly1305Cipher) AlgorithmType() uint32 {
// NonceSize returns the nonce size used by the cipher
func (c *ChaCha20Poly1305Cipher) NonceSize() int {
if c.aead == nil {
return chacha20poly1305.NonceSize // Return standard size even if not initialized
}
return c.aead.NonceSize()
return cryptoaead.NonceSize
}
// Overhead returns the authentication tag overhead
func (c *ChaCha20Poly1305Cipher) Overhead() int {
if c.aead == nil {
return chacha20poly1305.Overhead // Return standard overhead even if not initialized
}
return c.aead.Overhead()
return cryptoaead.TagSize
}

View File

@@ -28,6 +28,17 @@ func NewCrypto() *Crypto {
// Sign a stream using the specified algorithm
func (c *Crypto) SignStream(sgk *SignatureKeyPair, stream *Stream) (err error) {
// Use new DSA wrapper if available
if sgk.dsaKeyPair != nil {
signature, err := sgk.dsaKeyPair.Sign(stream.Bytes())
if err != nil {
return fmt.Errorf("failed to sign stream with DSA: %w", err)
}
stream.Write(signature)
return nil
}
// Fallback to legacy implementation for backward compatibility
var r, s *big.Int
out := NewStream(make([]byte, 40))
c.sh1.Reset()
@@ -76,12 +87,21 @@ func (c *Crypto) VerifyStream(sgk *SignatureKeyPair, stream *Stream) (verified b
Fatal(tAG|FATAL, "Stream length < 40 bytes (signature length)")
return false, fmt.Errorf("stream too short for signature verification")
}
var r, s big.Int
message := stream.Bytes()[:stream.Len()-40]
digest := stream.Bytes()[stream.Len()-40:]
signature := stream.Bytes()[stream.Len()-40:]
// Use new DSA wrapper if available
if sgk.dsaKeyPair != nil {
verified = sgk.dsaKeyPair.Verify(message, signature)
return verified, nil
}
// Fallback to legacy implementation for backward compatibility
var r, s big.Int
// TODO not sure about this part...
r.SetBytes(digest[:20])
s.SetBytes(digest[20:])
r.SetBytes(signature[:20])
s.SetBytes(signature[20:])
verified = dsa.Verify(&sgk.pub, message, &r, &s)
return
}
@@ -190,24 +210,43 @@ func (c *Crypto) PublicKeyFromStream(keyType uint32, stream *Stream) (key *big.I
func (c *Crypto) SignatureKeygen(algorithmTyp uint32) (sgk SignatureKeyPair, err error) {
switch algorithmTyp {
case DSA_SHA1:
var pkey dsa.PrivateKey
pkey.G = c.params.G
pkey.Q = c.params.Q
pkey.P = c.params.P
err = dsa.GenerateKey(&pkey, c.rng)
sgk.priv = pkey
sgk.pub.G = pkey.G
sgk.pub.P = pkey.P
sgk.pub.Q = pkey.Q
sgk.pub.Y = pkey.Y
// Use new DSA wrapper from crypto package
dsaKp, err := c.DSASignatureKeygen()
if err != nil {
return sgk, fmt.Errorf("failed to generate DSA key pair: %w", err)
}
// Convert new DSAKeyPair to legacy SignatureKeyPair for backward compatibility
// This allows existing code to continue working while we migrate to the new types
sgk.algorithmType = DSA_SHA1
sgk.dsaKeyPair = dsaKp
// Also populate legacy fields for backward compatibility
// Extract raw bytes and reconstruct old big.Int format
privKeyBytes := dsaKp.PrivateKey()
pubKeyBytes := dsaKp.PublicKey()
// Initialize DSA parameters from crypto struct
sgk.priv.G = c.params.G
sgk.priv.Q = c.params.Q
sgk.priv.P = c.params.P
sgk.pub.G = c.params.G
sgk.pub.Q = c.params.Q
sgk.pub.P = c.params.P
// Set private key X from bytes
sgk.priv.X = new(big.Int)
sgk.priv.X.SetBytes(privKeyBytes)
// Set public key Y from bytes
sgk.pub.Y = new(big.Int)
sgk.pub.Y.SetBytes(pubKeyBytes)
sgk.priv.Y = sgk.pub.Y // Private key also stores public key Y
default:
err = fmt.Errorf("unsupported signature algorithm type: %d", algorithmTyp)
}
return
}
// Ed25519SignatureKeygen generates a new Ed25519 signature key pair
} // Ed25519SignatureKeygen generates a new Ed25519 signature key pair
func (c *Crypto) Ed25519SignatureKeygen() (*Ed25519KeyPair, error) {
return NewEd25519KeyPair()
}
@@ -222,7 +261,12 @@ func (c *Crypto) ChaCha20Poly1305CipherKeygen() (*ChaCha20Poly1305Cipher, error)
return NewChaCha20Poly1305Cipher()
}
// Random32 generates a cryptographically secure random uint32 value
// DSASignatureKeygen generates a new DSA signature key pair
func (c *Crypto) DSASignatureKeygen() (*DSAKeyPair, error) {
return NewDSAKeyPair()
}
// Random32 generates a cryptographically secure random uint32.
// Used for I2CP message nonces and request IDs per protocol specification
func (c *Crypto) Random32() uint32 {
var bytes [4]byte

141
dsa.go Normal file
View File

@@ -0,0 +1,141 @@
package go_i2cp
import (
"fmt"
cryptodsa "github.com/go-i2p/crypto/dsa"
)
// DSAKeyPair represents a DSA signature key pair using the crypto package
type DSAKeyPair struct {
privKey cryptodsa.DSAPrivateKey
pubKey cryptodsa.DSAPublicKey
}
// NewDSAKeyPair generates a new DSA key pair using the crypto package
func NewDSAKeyPair() (*DSAKeyPair, error) {
var privKey cryptodsa.DSAPrivateKey
generatedKey, err := privKey.Generate()
if err != nil {
return nil, fmt.Errorf("failed to generate DSA key pair: %w", err)
}
privKey = generatedKey.(cryptodsa.DSAPrivateKey)
pubKeyInterface, err := privKey.Public()
if err != nil {
return nil, fmt.Errorf("failed to derive public key: %w", err)
}
pubKey := pubKeyInterface.(cryptodsa.DSAPublicKey)
return &DSAKeyPair{
privKey: privKey,
pubKey: pubKey,
}, nil
}
// AlgorithmType returns DSA_SHA1 algorithm type
func (kp *DSAKeyPair) AlgorithmType() uint32 {
return DSA_SHA1
}
// Sign creates a DSA signature for the given data
func (kp *DSAKeyPair) Sign(data []byte) ([]byte, error) {
signer, err := kp.privKey.NewSigner()
if err != nil {
return nil, fmt.Errorf("failed to create signer: %w", err)
}
signature, err := signer.Sign(data)
if err != nil {
return nil, fmt.Errorf("signing failed: %w", err)
}
return signature, nil
}
// Verify verifies a DSA signature against the given data
func (kp *DSAKeyPair) Verify(data, signature []byte) bool {
verifier, err := kp.pubKey.NewVerifier()
if err != nil {
return false
}
err = verifier.Verify(data, signature)
if err != nil {
return false
}
return true
}
// PrivateKey returns the private key bytes
func (kp *DSAKeyPair) PrivateKey() []byte {
return kp.privKey.Bytes()
}
// PublicKey returns the public key bytes
func (kp *DSAKeyPair) PublicKey() []byte {
return kp.pubKey.Bytes()
}
// WriteToStream writes the DSA key pair to a stream in I2CP format
func (kp *DSAKeyPair) WriteToStream(stream *Stream) error {
// Write algorithm type
err := stream.WriteUint32(DSA_SHA1)
if err != nil {
return fmt.Errorf("failed to write algorithm type: %w", err)
}
// Write private key (20 bytes)
privKeyBytes := kp.privKey.Bytes()
if len(privKeyBytes) != 20 {
return fmt.Errorf("invalid DSA private key length: %d (expected 20)", len(privKeyBytes))
}
_, err = stream.Write(privKeyBytes)
if err != nil {
return fmt.Errorf("failed to write private key: %w", err)
}
// Write public key (128 bytes)
pubKeyBytes := kp.pubKey.Bytes()
if len(pubKeyBytes) != 128 {
return fmt.Errorf("invalid DSA public key length: %d (expected 128)", len(pubKeyBytes))
}
_, err = stream.Write(pubKeyBytes)
if err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
return nil
}
// DSAKeyPairFromStream reads a DSA key pair from a stream
func DSAKeyPairFromStream(stream *Stream) (*DSAKeyPair, error) {
// Read private key (20 bytes)
privKeyBytes := make([]byte, 20)
_, err := stream.Read(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to read private key: %w", err)
}
// Read public key (128 bytes)
pubKeyBytes := make([]byte, 128)
_, err = stream.Read(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to read public key: %w", err)
}
// Convert to crypto package types
var privKey cryptodsa.DSAPrivateKey
copy(privKey[:], privKeyBytes)
var pubKey cryptodsa.DSAPublicKey
copy(pubKey[:], pubKeyBytes)
return &DSAKeyPair{
privKey: privKey,
pubKey: pubKey,
}, nil
}

View File

@@ -2,33 +2,60 @@ package go_i2cp
import (
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"fmt"
cryptoed25519 "github.com/go-i2p/crypto/ed25519"
)
// Ed25519KeyPair represents an Ed25519 signature key pair for modern I2P cryptography
// Ed25519 Implementation Migration Notes:
//
// This file has been migrated to use github.com/go-i2p/crypto/ed25519 (v0.0.5)
// for all cryptographic operations, following Phase 2.1 of the migration plan.
//
// Migration completed: November 24, 2025
//
// Key changes:
// - Ed25519KeyPair now wraps crypto package types (Ed25519PrivateKey, Ed25519PublicKey)
// - Sign() delegates to crypto.Signer interface (NewSigner().SignHash())
// - Verify() delegates to crypto.Verifier interface (NewVerifier().VerifyHash())
// - NewEd25519KeyPair() uses crypto.GenerateEd25519KeyPair()
// - Maintains I2CP-specific SHA-256 pre-hashing for protocol compatibility
// - Backward compatible API - all existing tests pass without modification
//
// The crypto package provides:
// - Interface-based signing/verification (types.Signer, types.Verifier)
// - Secure key generation and management
// - Memory zeroing for private keys (privKey.Zero())
// - Consistent cryptographic operations across go-i2p ecosystem
// Ed25519KeyPair represents an Ed25519 signature key pair for modern I2P cryptography.
// This type wraps github.com/go-i2p/crypto/ed25519 keys to provide I2CP-specific
// functionality while delegating cryptographic operations to the specialized crypto package.
type Ed25519KeyPair struct {
algorithmType uint32
privateKey ed25519.PrivateKey
publicKey ed25519.PublicKey
privateKey cryptoed25519.Ed25519PrivateKey
publicKey cryptoed25519.Ed25519PublicKey
}
// NewEd25519KeyPair generates a new Ed25519 key pair
// NewEd25519KeyPair generates a new Ed25519 key pair using github.com/go-i2p/crypto.
// This function delegates to the crypto package for secure key generation.
func NewEd25519KeyPair() (*Ed25519KeyPair, error) {
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
publicKey, privateKey, err := cryptoed25519.GenerateEd25519KeyPair()
if err != nil {
return nil, fmt.Errorf("failed to generate Ed25519 key pair: %w", err)
}
// Dereference pointers - crypto package keys are slices
return &Ed25519KeyPair{
algorithmType: ED25519_SHA256,
privateKey: privateKey,
publicKey: publicKey,
privateKey: *privateKey,
publicKey: *publicKey,
}, nil
}
// Ed25519KeyPairFromStream reads an Ed25519 key pair from a stream
// Ed25519KeyPairFromStream reads an Ed25519 key pair from a stream.
// Uses github.com/go-i2p/crypto/ed25519 for key reconstruction.
func Ed25519KeyPairFromStream(stream *Stream) (*Ed25519KeyPair, error) {
var algorithmType uint32
var err error
@@ -56,14 +83,27 @@ func Ed25519KeyPairFromStream(stream *Stream) (*Ed25519KeyPair, error) {
return nil, fmt.Errorf("failed to read public key: %w", err)
}
// Construct crypto package keys from raw bytes
privKey, err := cryptoed25519.CreateEd25519PrivateKeyFromBytes(privateKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to create private key: %w", err)
}
pubKey, err := cryptoed25519.CreateEd25519PublicKeyFromBytes(publicKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to create public key: %w", err)
}
// CreateFromBytes returns values, not pointers
return &Ed25519KeyPair{
algorithmType: algorithmType,
privateKey: ed25519.PrivateKey(privateKeyBytes),
publicKey: ed25519.PublicKey(publicKeyBytes),
privateKey: privKey,
publicKey: pubKey,
}, nil
}
// Ed25519PublicKeyFromStream reads only the public key from a stream
// Ed25519PublicKeyFromStream reads only the public key from a stream.
// Returns stdlib ed25519.PublicKey for backward compatibility.
func Ed25519PublicKeyFromStream(stream *Stream) (ed25519.PublicKey, error) {
publicKeyBytes := make([]byte, ed25519.PublicKeySize)
_, err := stream.Read(publicKeyBytes)
@@ -73,18 +113,30 @@ func Ed25519PublicKeyFromStream(stream *Stream) (ed25519.PublicKey, error) {
return ed25519.PublicKey(publicKeyBytes), nil
}
// Sign creates a signature for the given message using Ed25519
// Sign creates a signature for the given message using Ed25519.
// Uses github.com/go-i2p/crypto/ed25519 Signer interface for cryptographic operations.
func (kp *Ed25519KeyPair) Sign(message []byte) ([]byte, error) {
if kp.privateKey == nil {
return nil, fmt.Errorf("private key is nil")
}
// Ed25519 signs the message directly, but for I2CP compatibility we hash it first
// Create signer from private key
signer, err := kp.privateKey.NewSigner()
if err != nil {
return nil, fmt.Errorf("failed to create signer: %w", err)
}
// For I2CP compatibility, hash the message first using SHA-256
hasher := sha256.New()
hasher.Write(message)
messageHash := hasher.Sum(nil)
signature := ed25519.Sign(kp.privateKey, messageHash)
// Sign the hash using the crypto package's interface-based signing
signature, err := signer.SignHash(messageHash)
if err != nil {
return nil, fmt.Errorf("failed to sign message: %w", err)
}
return signature, nil
}
@@ -107,18 +159,27 @@ func (kp *Ed25519KeyPair) SignStream(stream *Stream) error {
return nil
}
// Verify verifies a signature against the given message using Ed25519
// Verify verifies a signature against the given message using Ed25519.
// Uses github.com/go-i2p/crypto/ed25519 Verifier interface for cryptographic operations.
func (kp *Ed25519KeyPair) Verify(message, signature []byte) bool {
if kp.publicKey == nil {
return false
}
// Create verifier from public key
verifier, err := kp.publicKey.NewVerifier()
if err != nil {
return false
}
// Hash the message for I2CP compatibility
hasher := sha256.New()
hasher.Write(message)
messageHash := hasher.Sum(nil)
return ed25519.Verify(kp.publicKey, messageHash, signature)
// Verify using the crypto package's interface-based verification
err = verifier.VerifyHash(messageHash, signature)
return err == nil
}
// VerifyStream verifies a signature embedded in the stream data
@@ -176,13 +237,14 @@ func (kp *Ed25519KeyPair) WriteToStream(stream *Stream) error {
return nil
}
// WritePublicKeyToStream writes only the public key to a stream
// WritePublicKeyToStream writes only the public key to a stream.
// Uses crypto package's Bytes() method for key serialization.
func (kp *Ed25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
if stream == nil {
return fmt.Errorf("stream cannot be nil")
}
_, err := stream.Write(kp.publicKey)
_, err := stream.Write(kp.publicKey.Bytes())
if err != nil {
return fmt.Errorf("failed to write Ed25519 public key: %w", err)
}
@@ -190,14 +252,14 @@ func (kp *Ed25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
return nil
}
// PublicKey returns the public key
// PublicKey returns the public key as stdlib ed25519.PublicKey for backward compatibility.
func (kp *Ed25519KeyPair) PublicKey() ed25519.PublicKey {
return kp.publicKey
return ed25519.PublicKey(kp.publicKey.Bytes())
}
// PrivateKey returns the private key
// PrivateKey returns the private key as stdlib ed25519.PrivateKey for backward compatibility.
func (kp *Ed25519KeyPair) PrivateKey() ed25519.PrivateKey {
return kp.privateKey
return ed25519.PrivateKey(kp.privateKey.Bytes())
}
// AlgorithmType returns the algorithm type constant

12
go.mod
View File

@@ -3,14 +3,20 @@ module github.com/go-i2p/go-i2cp
go 1.24.4
require (
github.com/go-i2p/crypto v0.0.5
github.com/go-i2p/logger v0.0.1
go.step.sm/crypto v0.67.0
golang.org/x/crypto v0.45.0
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/samber/oops v1.19.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stretchr/testify v1.10.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
)

21
go.sum
View File

@@ -1,23 +1,44 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-i2p/crypto v0.0.5 h1:i6EiQ4rKnNDDcACFfCkizgfMTwxFcMRj187w/VLiD9o=
github.com/go-i2p/crypto v0.0.5/go.mod h1:uerqW6jReQkeNgXXMM3n55wZbKbnvuZrbsDaNL9oLjo=
github.com/go-i2p/logger v0.0.1 h1:OFDZMjqiNXbPIm+SDxiwYtI6ocC3mb9V/t5kvZ+6XQ0=
github.com/go-i2p/logger v0.0.1/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/oops v1.19.0 h1:sfZAwC8MmTXBRRyNc4Z1utuTPBx+hFKF5fJ9DEQRZfw=
github.com/samber/oops v1.19.0/go.mod h1:+f+61dbiMxEMQ8gw/zTxW2pk+YGobaDM4glEHQtPOww=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.step.sm/crypto v0.67.0 h1:1km9LmxMKG/p+mKa1R4luPN04vlJYnRLlLQrWv7egGU=
go.step.sm/crypto v0.67.0/go.mod h1:+AoDpB0mZxbW/PmOXuwkPSpXRgaUaoIK+/Wx/HGgtAU=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -7,8 +7,11 @@ import (
)
// SignatureKeyPair represents a DSA signature key pair
// This is a legacy struct maintained for backward compatibility.
// New code should use DSAKeyPair directly from dsa.go
type SignatureKeyPair struct {
algorithmType uint32
pub dsa.PublicKey
priv dsa.PrivateKey
dsaKeyPair *DSAKeyPair // New wrapper from crypto package
}

140
x25519.go
View File

@@ -1,40 +1,52 @@
// Migration Notes (November 24, 2025):
// Migrated to use github.com/go-i2p/crypto/curve25519 package which provides
// standardized X25519 key generation and ECDH operations via go.step.sm/crypto/x25519.
//
// Key Changes:
// - X25519KeyPair now wraps curve25519.Curve25519PrivateKey and Curve25519PublicKey (slice types)
// - NewX25519KeyPair() delegates to curve25519.GenerateX25519KeyPair()
// - GenerateSharedSecret() uses PrivateKey.SharedKey() for ECDH operations
// - Maintains [32]byte return types for backward compatibility
//
// The crypto package uses []byte (slice) types for keys, while I2CP uses
// [32]byte arrays. The wrapper maintains I2CP compatibility.
package go_i2cp
import (
"crypto/rand"
"fmt"
"golang.org/x/crypto/curve25519"
cryptox25519 "github.com/go-i2p/crypto/curve25519"
"go.step.sm/crypto/x25519"
)
// X25519KeyPair represents an X25519 key exchange key pair for modern I2P ECDH
// Wraps github.com/go-i2p/crypto/curve25519 types
type X25519KeyPair struct {
algorithmType uint32
privateKey [32]byte
publicKey [32]byte
privateKey cryptox25519.Curve25519PrivateKey
publicKey cryptox25519.Curve25519PublicKey
}
// NewX25519KeyPair generates a new X25519 key pair for ECDH operations
// Delegates to github.com/go-i2p/crypto/curve25519.GenerateX25519KeyPair()
func NewX25519KeyPair() (*X25519KeyPair, error) {
var privateKey, publicKey [32]byte
// Generate random private key
_, err := rand.Read(privateKey[:])
// Generate key pair using crypto package
pubKey, privKey, err := cryptox25519.GenerateX25519KeyPair()
if err != nil {
return nil, fmt.Errorf("failed to generate X25519 private key: %w", err)
return nil, fmt.Errorf("failed to generate X25519 key pair: %w", err)
}
// Derive public key from private key
curve25519.ScalarBaseMult(&publicKey, &privateKey)
// Dereference pointers to get slice values
return &X25519KeyPair{
algorithmType: X25519,
privateKey: privateKey,
publicKey: publicKey,
privateKey: *privKey,
publicKey: *pubKey,
}, nil
}
// X25519KeyPairFromStream reads an X25519 key pair from a stream
// Uses go.step.sm/crypto/x25519 for key reconstruction and validation
func X25519KeyPairFromStream(stream *Stream) (*X25519KeyPair, error) {
var algorithmType uint32
var err error
@@ -48,22 +60,53 @@ func X25519KeyPairFromStream(stream *Stream) (*X25519KeyPair, error) {
return nil, fmt.Errorf("unsupported algorithm type: %d", algorithmType)
}
var kp X25519KeyPair
kp.algorithmType = algorithmType
// Read private key (32 bytes)
_, err = stream.Read(kp.privateKey[:])
privKeyBytes := make([]byte, 32)
_, err = stream.Read(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to read X25519 private key: %w", err)
}
// Read public key (32 bytes)
_, err = stream.Read(kp.publicKey[:])
pubKeyBytes := make([]byte, 32)
_, err = stream.Read(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to read X25519 public key: %w", err)
}
return &kp, nil
// Validate that the public key corresponds to the private key
privKey := make(x25519.PrivateKey, 32)
copy(privKey, privKeyBytes)
expectedPubKeyInterface := privKey.Public()
expectedPubKey, ok := expectedPubKeyInterface.(x25519.PublicKey)
if !ok {
return nil, fmt.Errorf("failed to convert public key to x25519.PublicKey")
}
// Compare public keys
if !bytesEqual(expectedPubKey, pubKeyBytes) {
return nil, fmt.Errorf("public key does not match private key")
}
return &X25519KeyPair{
algorithmType: algorithmType,
privateKey: cryptox25519.Curve25519PrivateKey(privKeyBytes),
publicKey: cryptox25519.Curve25519PublicKey(pubKeyBytes),
}, nil
}
// bytesEqual compares two byte slices for equality
func bytesEqual(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// X25519PublicKeyFromStream reads only the public key from a stream
@@ -77,19 +120,36 @@ func X25519PublicKeyFromStream(stream *Stream) ([32]byte, error) {
}
// GenerateSharedSecret performs ECDH to generate a shared secret with the peer's public key
// Uses go.step.sm/crypto/x25519 PrivateKey.SharedKey() for ECDH
func (kp *X25519KeyPair) GenerateSharedSecret(peerPublicKey [32]byte) ([32]byte, error) {
var sharedSecret [32]byte
var result [32]byte
// Perform scalar multiplication: shared_secret = our_private * peer_public
curve25519.ScalarMult(&sharedSecret, &kp.privateKey, &peerPublicKey)
// Check for weak shared secrets (all zeros)
var zero [32]byte
if sharedSecret == zero {
return sharedSecret, fmt.Errorf("generated weak shared secret (all zeros)")
// Validate the peer's public key first
if !ValidateX25519PublicKey(peerPublicKey) {
return result, fmt.Errorf("invalid peer public key")
}
return sharedSecret, nil
// Convert slice to x25519 types for SharedKey operation
privKey := make(x25519.PrivateKey, 32)
copy(privKey, kp.privateKey)
pubKey := make(x25519.PublicKey, 32)
copy(pubKey, peerPublicKey[:])
// Perform X25519 key exchange (ECDH)
sharedSecret, err := privKey.SharedKey(pubKey)
if err != nil {
return result, fmt.Errorf("X25519 key exchange failed: %w", err)
}
// Additional security check: reject all-zero shared secrets
var zero [32]byte
copy(result[:], sharedSecret)
if result == zero {
return result, fmt.Errorf("generated weak shared secret (all zeros)")
}
return result, nil
}
// WriteToStream writes the complete X25519 key pair to a stream
@@ -104,14 +164,14 @@ func (kp *X25519KeyPair) WriteToStream(stream *Stream) error {
return fmt.Errorf("failed to write algorithm type: %w", err)
}
// Write private key
_, err = stream.Write(kp.privateKey[:])
// Write private key (convert slice to bytes)
_, err = stream.Write(kp.privateKey)
if err != nil {
return fmt.Errorf("failed to write X25519 private key: %w", err)
}
// Write public key
_, err = stream.Write(kp.publicKey[:])
// Write public key (convert slice to bytes)
_, err = stream.Write(kp.publicKey)
if err != nil {
return fmt.Errorf("failed to write X25519 public key: %w", err)
}
@@ -125,7 +185,7 @@ func (kp *X25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
return fmt.Errorf("stream cannot be nil")
}
_, err := stream.Write(kp.publicKey[:])
_, err := stream.Write(kp.publicKey)
if err != nil {
return fmt.Errorf("failed to write X25519 public key: %w", err)
}
@@ -133,14 +193,18 @@ func (kp *X25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
return nil
}
// PublicKey returns a copy of the public key
// PublicKey returns a copy of the public key as [32]byte for backward compatibility
func (kp *X25519KeyPair) PublicKey() [32]byte {
return kp.publicKey
var result [32]byte
copy(result[:], kp.publicKey)
return result
}
// PrivateKey returns a copy of the private key
// PrivateKey returns a copy of the private key as [32]byte for backward compatibility
func (kp *X25519KeyPair) PrivateKey() [32]byte {
return kp.privateKey
var result [32]byte
copy(result[:], kp.privateKey)
return result
}
// AlgorithmType returns the algorithm type constant