mirror of
https://github.com/go-i2p/go-i2cp.git
synced 2025-12-20 15:15:53 -05:00
Migrate to go-i2p/crypto package for cryptographic operations
This commit is contained in:
@@ -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
|
package go_i2cp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
cryptoaead "github.com/go-i2p/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChaCha20Poly1305Cipher provides authenticated encryption using ChaCha20-Poly1305
|
// ChaCha20Poly1305Cipher provides authenticated encryption using ChaCha20-Poly1305
|
||||||
|
// Wraps github.com/go-i2p/crypto/chacha20poly1305.AEAD
|
||||||
type ChaCha20Poly1305Cipher struct {
|
type ChaCha20Poly1305Cipher struct {
|
||||||
algorithmType uint32
|
algorithmType uint32
|
||||||
aead cipher.AEAD
|
aead *cryptoaead.AEAD
|
||||||
key [32]byte
|
key [32]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChaCha20Poly1305Cipher creates a new ChaCha20-Poly1305 cipher with a random key
|
// 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) {
|
func NewChaCha20Poly1305Cipher() (*ChaCha20Poly1305Cipher, error) {
|
||||||
var key [32]byte
|
// Generate random 256-bit key using crypto package
|
||||||
|
key, err := cryptoaead.GenerateKey()
|
||||||
// Generate random 256-bit key
|
|
||||||
_, err := rand.Read(key[:])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate ChaCha20-Poly1305 key: %w", err)
|
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
|
// 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) {
|
func NewChaCha20Poly1305CipherWithKey(key [32]byte) (*ChaCha20Poly1305Cipher, error) {
|
||||||
aead, err := chacha20poly1305.New(key[:])
|
aead, err := cryptoaead.NewAEAD(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create ChaCha20-Poly1305 AEAD: %w", err)
|
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
|
// 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) {
|
func (c *ChaCha20Poly1305Cipher) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
|
||||||
if c.aead == nil {
|
if c.aead == nil {
|
||||||
return nil, fmt.Errorf("cipher not initialized")
|
return nil, fmt.Errorf("cipher not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate random nonce
|
// Generate random nonce using crypto package
|
||||||
nonce := make([]byte, c.aead.NonceSize())
|
nonce, err := cryptoaead.GenerateNonce()
|
||||||
_, err := rand.Read(nonce)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate nonce: %w", err)
|
return nil, fmt.Errorf("failed to generate nonce: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt plaintext
|
// Encrypt plaintext - crypto package returns separate ciphertext and tag
|
||||||
ciphertext := c.aead.Seal(nil, nonce, plaintext, additionalData)
|
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
|
// Combine nonce, ciphertext, and tag for I2CP compatibility
|
||||||
result := make([]byte, len(nonce)+len(ciphertext))
|
// Format: [nonce][ciphertext][tag]
|
||||||
copy(result, nonce)
|
result := make([]byte, len(nonce)+len(ciphertext)+len(tag))
|
||||||
|
copy(result, nonce[:])
|
||||||
copy(result[len(nonce):], ciphertext)
|
copy(result[len(nonce):], ciphertext)
|
||||||
|
copy(result[len(nonce)+len(ciphertext):], tag[:])
|
||||||
|
|
||||||
return result, nil
|
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
|
// 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) {
|
func (c *ChaCha20Poly1305Cipher) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
|
||||||
if c.aead == nil {
|
if c.aead == nil {
|
||||||
return nil, fmt.Errorf("cipher not initialized")
|
return nil, fmt.Errorf("cipher not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceSize := c.aead.NonceSize()
|
nonceSize := cryptoaead.NonceSize
|
||||||
if len(ciphertext) < nonceSize {
|
tagSize := cryptoaead.TagSize
|
||||||
return nil, fmt.Errorf("ciphertext too short: need at least %d bytes for nonce", nonceSize)
|
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]
|
nonce := ciphertext[:nonceSize]
|
||||||
actualCiphertext := ciphertext[nonceSize:]
|
actualCiphertextWithTag := ciphertext[nonceSize:]
|
||||||
|
|
||||||
// Decrypt ciphertext
|
// Split ciphertext and tag
|
||||||
plaintext, err := c.aead.Open(nil, nonce, actualCiphertext, additionalData)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decrypt: %w", err)
|
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
|
// NonceSize returns the nonce size used by the cipher
|
||||||
func (c *ChaCha20Poly1305Cipher) NonceSize() int {
|
func (c *ChaCha20Poly1305Cipher) NonceSize() int {
|
||||||
if c.aead == nil {
|
return cryptoaead.NonceSize
|
||||||
return chacha20poly1305.NonceSize // Return standard size even if not initialized
|
|
||||||
}
|
|
||||||
return c.aead.NonceSize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overhead returns the authentication tag overhead
|
// Overhead returns the authentication tag overhead
|
||||||
func (c *ChaCha20Poly1305Cipher) Overhead() int {
|
func (c *ChaCha20Poly1305Cipher) Overhead() int {
|
||||||
if c.aead == nil {
|
return cryptoaead.TagSize
|
||||||
return chacha20poly1305.Overhead // Return standard overhead even if not initialized
|
|
||||||
}
|
|
||||||
return c.aead.Overhead()
|
|
||||||
}
|
}
|
||||||
|
|||||||
80
crypto.go
80
crypto.go
@@ -28,6 +28,17 @@ func NewCrypto() *Crypto {
|
|||||||
|
|
||||||
// Sign a stream using the specified algorithm
|
// Sign a stream using the specified algorithm
|
||||||
func (c *Crypto) SignStream(sgk *SignatureKeyPair, stream *Stream) (err error) {
|
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
|
var r, s *big.Int
|
||||||
out := NewStream(make([]byte, 40))
|
out := NewStream(make([]byte, 40))
|
||||||
c.sh1.Reset()
|
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)")
|
Fatal(tAG|FATAL, "Stream length < 40 bytes (signature length)")
|
||||||
return false, fmt.Errorf("stream too short for signature verification")
|
return false, fmt.Errorf("stream too short for signature verification")
|
||||||
}
|
}
|
||||||
var r, s big.Int
|
|
||||||
message := stream.Bytes()[:stream.Len()-40]
|
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...
|
// TODO not sure about this part...
|
||||||
r.SetBytes(digest[:20])
|
r.SetBytes(signature[:20])
|
||||||
s.SetBytes(digest[20:])
|
s.SetBytes(signature[20:])
|
||||||
verified = dsa.Verify(&sgk.pub, message, &r, &s)
|
verified = dsa.Verify(&sgk.pub, message, &r, &s)
|
||||||
return
|
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) {
|
func (c *Crypto) SignatureKeygen(algorithmTyp uint32) (sgk SignatureKeyPair, err error) {
|
||||||
switch algorithmTyp {
|
switch algorithmTyp {
|
||||||
case DSA_SHA1:
|
case DSA_SHA1:
|
||||||
var pkey dsa.PrivateKey
|
// Use new DSA wrapper from crypto package
|
||||||
pkey.G = c.params.G
|
dsaKp, err := c.DSASignatureKeygen()
|
||||||
pkey.Q = c.params.Q
|
if err != nil {
|
||||||
pkey.P = c.params.P
|
return sgk, fmt.Errorf("failed to generate DSA key pair: %w", err)
|
||||||
err = dsa.GenerateKey(&pkey, c.rng)
|
}
|
||||||
sgk.priv = pkey
|
|
||||||
sgk.pub.G = pkey.G
|
// Convert new DSAKeyPair to legacy SignatureKeyPair for backward compatibility
|
||||||
sgk.pub.P = pkey.P
|
// This allows existing code to continue working while we migrate to the new types
|
||||||
sgk.pub.Q = pkey.Q
|
|
||||||
sgk.pub.Y = pkey.Y
|
|
||||||
sgk.algorithmType = DSA_SHA1
|
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:
|
default:
|
||||||
err = fmt.Errorf("unsupported signature algorithm type: %d", algorithmTyp)
|
err = fmt.Errorf("unsupported signature algorithm type: %d", algorithmTyp)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
} // Ed25519SignatureKeygen generates a new Ed25519 signature key pair
|
||||||
|
|
||||||
// Ed25519SignatureKeygen generates a new Ed25519 signature key pair
|
|
||||||
func (c *Crypto) Ed25519SignatureKeygen() (*Ed25519KeyPair, error) {
|
func (c *Crypto) Ed25519SignatureKeygen() (*Ed25519KeyPair, error) {
|
||||||
return NewEd25519KeyPair()
|
return NewEd25519KeyPair()
|
||||||
}
|
}
|
||||||
@@ -222,7 +261,12 @@ func (c *Crypto) ChaCha20Poly1305CipherKeygen() (*ChaCha20Poly1305Cipher, error)
|
|||||||
return NewChaCha20Poly1305Cipher()
|
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
|
// Used for I2CP message nonces and request IDs per protocol specification
|
||||||
func (c *Crypto) Random32() uint32 {
|
func (c *Crypto) Random32() uint32 {
|
||||||
var bytes [4]byte
|
var bytes [4]byte
|
||||||
|
|||||||
141
dsa.go
Normal file
141
dsa.go
Normal 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
|
||||||
|
}
|
||||||
108
ed25519.go
108
ed25519.go
@@ -2,33 +2,60 @@ package go_i2cp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"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 {
|
type Ed25519KeyPair struct {
|
||||||
algorithmType uint32
|
algorithmType uint32
|
||||||
privateKey ed25519.PrivateKey
|
privateKey cryptoed25519.Ed25519PrivateKey
|
||||||
publicKey ed25519.PublicKey
|
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) {
|
func NewEd25519KeyPair() (*Ed25519KeyPair, error) {
|
||||||
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
publicKey, privateKey, err := cryptoed25519.GenerateEd25519KeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate Ed25519 key pair: %w", err)
|
return nil, fmt.Errorf("failed to generate Ed25519 key pair: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dereference pointers - crypto package keys are slices
|
||||||
return &Ed25519KeyPair{
|
return &Ed25519KeyPair{
|
||||||
algorithmType: ED25519_SHA256,
|
algorithmType: ED25519_SHA256,
|
||||||
privateKey: privateKey,
|
privateKey: *privateKey,
|
||||||
publicKey: publicKey,
|
publicKey: *publicKey,
|
||||||
}, nil
|
}, 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) {
|
func Ed25519KeyPairFromStream(stream *Stream) (*Ed25519KeyPair, error) {
|
||||||
var algorithmType uint32
|
var algorithmType uint32
|
||||||
var err error
|
var err error
|
||||||
@@ -56,14 +83,27 @@ func Ed25519KeyPairFromStream(stream *Stream) (*Ed25519KeyPair, error) {
|
|||||||
return nil, fmt.Errorf("failed to read public key: %w", err)
|
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{
|
return &Ed25519KeyPair{
|
||||||
algorithmType: algorithmType,
|
algorithmType: algorithmType,
|
||||||
privateKey: ed25519.PrivateKey(privateKeyBytes),
|
privateKey: privKey,
|
||||||
publicKey: ed25519.PublicKey(publicKeyBytes),
|
publicKey: pubKey,
|
||||||
}, nil
|
}, 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) {
|
func Ed25519PublicKeyFromStream(stream *Stream) (ed25519.PublicKey, error) {
|
||||||
publicKeyBytes := make([]byte, ed25519.PublicKeySize)
|
publicKeyBytes := make([]byte, ed25519.PublicKeySize)
|
||||||
_, err := stream.Read(publicKeyBytes)
|
_, err := stream.Read(publicKeyBytes)
|
||||||
@@ -73,18 +113,30 @@ func Ed25519PublicKeyFromStream(stream *Stream) (ed25519.PublicKey, error) {
|
|||||||
return ed25519.PublicKey(publicKeyBytes), nil
|
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) {
|
func (kp *Ed25519KeyPair) Sign(message []byte) ([]byte, error) {
|
||||||
if kp.privateKey == nil {
|
if kp.privateKey == nil {
|
||||||
return nil, fmt.Errorf("private key is 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 := sha256.New()
|
||||||
hasher.Write(message)
|
hasher.Write(message)
|
||||||
messageHash := hasher.Sum(nil)
|
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
|
return signature, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,18 +159,27 @@ func (kp *Ed25519KeyPair) SignStream(stream *Stream) error {
|
|||||||
return nil
|
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 {
|
func (kp *Ed25519KeyPair) Verify(message, signature []byte) bool {
|
||||||
if kp.publicKey == nil {
|
if kp.publicKey == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create verifier from public key
|
||||||
|
verifier, err := kp.publicKey.NewVerifier()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Hash the message for I2CP compatibility
|
// Hash the message for I2CP compatibility
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
hasher.Write(message)
|
hasher.Write(message)
|
||||||
messageHash := hasher.Sum(nil)
|
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
|
// VerifyStream verifies a signature embedded in the stream data
|
||||||
@@ -176,13 +237,14 @@ func (kp *Ed25519KeyPair) WriteToStream(stream *Stream) error {
|
|||||||
return nil
|
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 {
|
func (kp *Ed25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
return fmt.Errorf("stream cannot be nil")
|
return fmt.Errorf("stream cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := stream.Write(kp.publicKey)
|
_, err := stream.Write(kp.publicKey.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write Ed25519 public key: %w", err)
|
return fmt.Errorf("failed to write Ed25519 public key: %w", err)
|
||||||
}
|
}
|
||||||
@@ -190,14 +252,14 @@ func (kp *Ed25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
|
|||||||
return nil
|
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 {
|
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 {
|
func (kp *Ed25519KeyPair) PrivateKey() ed25519.PrivateKey {
|
||||||
return kp.privateKey
|
return ed25519.PrivateKey(kp.privateKey.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlgorithmType returns the algorithm type constant
|
// AlgorithmType returns the algorithm type constant
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -3,14 +3,20 @@ module github.com/go-i2p/go-i2cp
|
|||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-i2p/crypto v0.0.5
|
||||||
github.com/go-i2p/logger v0.0.1
|
github.com/go-i2p/logger v0.0.1
|
||||||
|
go.step.sm/crypto v0.67.0
|
||||||
golang.org/x/crypto v0.45.0
|
golang.org/x/crypto v0.45.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/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/sys v0.38.0 // indirect
|
||||||
|
golang.org/x/text v0.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
21
go.sum
21
go.sum
@@ -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.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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:OFDZMjqiNXbPIm+SDxiwYtI6ocC3mb9V/t5kvZ+6XQ0=
|
||||||
github.com/go-i2p/logger v0.0.1/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
|
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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
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/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.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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
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.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 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SignatureKeyPair represents a DSA signature key pair
|
// 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 {
|
type SignatureKeyPair struct {
|
||||||
algorithmType uint32
|
algorithmType uint32
|
||||||
pub dsa.PublicKey
|
pub dsa.PublicKey
|
||||||
priv dsa.PrivateKey
|
priv dsa.PrivateKey
|
||||||
|
dsaKeyPair *DSAKeyPair // New wrapper from crypto package
|
||||||
}
|
}
|
||||||
|
|||||||
140
x25519.go
140
x25519.go
@@ -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
|
package go_i2cp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"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
|
// X25519KeyPair represents an X25519 key exchange key pair for modern I2P ECDH
|
||||||
|
// Wraps github.com/go-i2p/crypto/curve25519 types
|
||||||
type X25519KeyPair struct {
|
type X25519KeyPair struct {
|
||||||
algorithmType uint32
|
algorithmType uint32
|
||||||
privateKey [32]byte
|
privateKey cryptox25519.Curve25519PrivateKey
|
||||||
publicKey [32]byte
|
publicKey cryptox25519.Curve25519PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewX25519KeyPair generates a new X25519 key pair for ECDH operations
|
// NewX25519KeyPair generates a new X25519 key pair for ECDH operations
|
||||||
|
// Delegates to github.com/go-i2p/crypto/curve25519.GenerateX25519KeyPair()
|
||||||
func NewX25519KeyPair() (*X25519KeyPair, error) {
|
func NewX25519KeyPair() (*X25519KeyPair, error) {
|
||||||
var privateKey, publicKey [32]byte
|
// Generate key pair using crypto package
|
||||||
|
pubKey, privKey, err := cryptox25519.GenerateX25519KeyPair()
|
||||||
// Generate random private key
|
|
||||||
_, err := rand.Read(privateKey[:])
|
|
||||||
if err != nil {
|
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
|
// Dereference pointers to get slice values
|
||||||
curve25519.ScalarBaseMult(&publicKey, &privateKey)
|
|
||||||
|
|
||||||
return &X25519KeyPair{
|
return &X25519KeyPair{
|
||||||
algorithmType: X25519,
|
algorithmType: X25519,
|
||||||
privateKey: privateKey,
|
privateKey: *privKey,
|
||||||
publicKey: publicKey,
|
publicKey: *pubKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// X25519KeyPairFromStream reads an X25519 key pair from a stream
|
// 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) {
|
func X25519KeyPairFromStream(stream *Stream) (*X25519KeyPair, error) {
|
||||||
var algorithmType uint32
|
var algorithmType uint32
|
||||||
var err error
|
var err error
|
||||||
@@ -48,22 +60,53 @@ func X25519KeyPairFromStream(stream *Stream) (*X25519KeyPair, error) {
|
|||||||
return nil, fmt.Errorf("unsupported algorithm type: %d", algorithmType)
|
return nil, fmt.Errorf("unsupported algorithm type: %d", algorithmType)
|
||||||
}
|
}
|
||||||
|
|
||||||
var kp X25519KeyPair
|
|
||||||
kp.algorithmType = algorithmType
|
|
||||||
|
|
||||||
// Read private key (32 bytes)
|
// Read private key (32 bytes)
|
||||||
_, err = stream.Read(kp.privateKey[:])
|
privKeyBytes := make([]byte, 32)
|
||||||
|
_, err = stream.Read(privKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read X25519 private key: %w", err)
|
return nil, fmt.Errorf("failed to read X25519 private key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read public key (32 bytes)
|
// Read public key (32 bytes)
|
||||||
_, err = stream.Read(kp.publicKey[:])
|
pubKeyBytes := make([]byte, 32)
|
||||||
|
_, err = stream.Read(pubKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read X25519 public key: %w", err)
|
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
|
// 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
|
// 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) {
|
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
|
// Validate the peer's public key first
|
||||||
curve25519.ScalarMult(&sharedSecret, &kp.privateKey, &peerPublicKey)
|
if !ValidateX25519PublicKey(peerPublicKey) {
|
||||||
|
return result, fmt.Errorf("invalid peer public key")
|
||||||
// Check for weak shared secrets (all zeros)
|
|
||||||
var zero [32]byte
|
|
||||||
if sharedSecret == zero {
|
|
||||||
return sharedSecret, fmt.Errorf("generated weak shared secret (all zeros)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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)
|
return fmt.Errorf("failed to write algorithm type: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write private key
|
// Write private key (convert slice to bytes)
|
||||||
_, err = stream.Write(kp.privateKey[:])
|
_, err = stream.Write(kp.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write X25519 private key: %w", err)
|
return fmt.Errorf("failed to write X25519 private key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write public key
|
// Write public key (convert slice to bytes)
|
||||||
_, err = stream.Write(kp.publicKey[:])
|
_, err = stream.Write(kp.publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write X25519 public key: %w", err)
|
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")
|
return fmt.Errorf("stream cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := stream.Write(kp.publicKey[:])
|
_, err := stream.Write(kp.publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write X25519 public key: %w", err)
|
return fmt.Errorf("failed to write X25519 public key: %w", err)
|
||||||
}
|
}
|
||||||
@@ -133,14 +193,18 @@ func (kp *X25519KeyPair) WritePublicKeyToStream(stream *Stream) error {
|
|||||||
return nil
|
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 {
|
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 {
|
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
|
// AlgorithmType returns the algorithm type constant
|
||||||
|
|||||||
Reference in New Issue
Block a user