mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-06-07 10:01:41 -04:00
127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
package ntcp
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/go-i2p/go-i2p/lib/crypto/curve25519"
|
|
"github.com/go-i2p/go-i2p/lib/transport/ntcp/handshake"
|
|
"github.com/samber/oops"
|
|
)
|
|
|
|
// DecryptOptionsBlock decrypts the options block from a SessionRequest message
|
|
func (c *NTCP2Session) DecryptOptionsBlock(encryptedOptions []byte, obfuscatedX []byte, deobfuscatedX []byte) ([]byte, error) {
|
|
return c.PerformAEADOperation(
|
|
deobfuscatedX, // Key material
|
|
encryptedOptions, // Data to decrypt
|
|
obfuscatedX, // Associated data
|
|
0, // Nonce counter (0 for first message)
|
|
false, // Decrypt operation
|
|
)
|
|
}
|
|
|
|
// addDelayForSecurity adds a small random delay to resist probing
|
|
func (c *NTCP2Session) AddDelayForSecurity() {
|
|
// Sleep between 50-250ms to make timing attacks harder
|
|
// delay := time.Duration(50+mrand.Intn(200)) * time.Millisecond
|
|
delay := time.Duration(0)
|
|
time.Sleep(delay)
|
|
}
|
|
|
|
// readEphemeralKey reads the ephemeral key (X) from the connection
|
|
func (c *NTCP2Session) ReadEphemeralKey(conn net.Conn) ([]byte, error) {
|
|
ephemeralKey := make([]byte, 32)
|
|
if _, err := io.ReadFull(conn, ephemeralKey); err != nil {
|
|
if err == io.ErrUnexpectedEOF {
|
|
return nil, oops.Errorf("incomplete ephemeral key: connection closed prematurely")
|
|
}
|
|
return nil, oops.Errorf("failed to read ephemeral key: %w", err)
|
|
}
|
|
return ephemeralKey, nil
|
|
}
|
|
|
|
// processEphemeralKey deobfuscates and validates the ephemeral key
|
|
func (c *NTCP2Session) ProcessEphemeralKey(obfuscatedX []byte, hs *handshake.HandshakeState) ([]byte, error) {
|
|
deobfuscatedX, err := c.DeobfuscateEphemeral(obfuscatedX)
|
|
if err != nil {
|
|
c.AddDelayForSecurity()
|
|
return nil, oops.Errorf("failed to deobfuscate ephemeral key: %w", err)
|
|
}
|
|
|
|
// Validate key for curve25519 (MSB must be cleared)
|
|
if deobfuscatedX[31]&0x80 != 0 {
|
|
log.Warnf("NTCP2: Rejecting SessionRequest - invalid ephemeral key format")
|
|
c.AddDelayForSecurity()
|
|
return nil, oops.Errorf("invalid ephemeral key format")
|
|
}
|
|
|
|
// Store in handshake state
|
|
pubKey := curve25519.Curve25519PublicKey(deobfuscatedX)
|
|
hs.RemoteEphemeral = pubKey
|
|
|
|
return deobfuscatedX, nil
|
|
}
|
|
|
|
// readAndValidatePadding reads the padding from the connection
|
|
func (c *NTCP2Session) ReadAndValidatePadding(conn net.Conn, paddingLen int) error {
|
|
// Check reasonable padding size to prevent DoS
|
|
if paddingLen > 64 {
|
|
return oops.Errorf("excessive padding size: %d bytes", paddingLen)
|
|
}
|
|
|
|
padding := make([]byte, paddingLen)
|
|
n, err := io.ReadFull(conn, padding)
|
|
if err != nil {
|
|
if err == io.ErrUnexpectedEOF {
|
|
return oops.Errorf("incomplete padding: got %d bytes, expected %d", n, paddingLen)
|
|
}
|
|
return oops.Errorf("failed to read padding: %w", err)
|
|
}
|
|
|
|
// No need to validate padding content - it's random data
|
|
return nil
|
|
}
|
|
|
|
// Intn generates a random integer in the range [0, n)
|
|
// This is a secure alternative to math/rand.Intn
|
|
// It uses crypto/rand to generate a cryptographically secure random number
|
|
// Which might be dumb and or pointless for padding.
|
|
func Intn(n int) int {
|
|
// implementation of Intn function using crypto/rand
|
|
cryptoRand, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return int(cryptoRand.Int64())
|
|
}
|
|
|
|
// Helper to write message parts to connection
|
|
func (s *NTCP2Session) WriteMessageToConn(conn net.Conn, obfuscatedKey, encryptedPayload, padding []byte) error {
|
|
// Calculate total size
|
|
totalSize := len(obfuscatedKey) + len(encryptedPayload)
|
|
if padding != nil {
|
|
totalSize += len(padding)
|
|
}
|
|
|
|
// Create buffer and copy data
|
|
message := make([]byte, totalSize)
|
|
offset := 0
|
|
|
|
copy(message[offset:], obfuscatedKey)
|
|
offset += len(obfuscatedKey)
|
|
|
|
copy(message[offset:], encryptedPayload)
|
|
offset += len(encryptedPayload)
|
|
|
|
if padding != nil {
|
|
copy(message[offset:], padding)
|
|
}
|
|
|
|
// Write to connection
|
|
_, err := conn.Write(message)
|
|
return err
|
|
}
|