diff --git a/lib/transport/ntcp/session_request.go b/lib/transport/ntcp/session_request.go index e474246..09dbd9a 100644 --- a/lib/transport/ntcp/session_request.go +++ b/lib/transport/ntcp/session_request.go @@ -1,73 +1,16 @@ package ntcp import ( - "crypto/rand" "io" - "math/big" - mrand "math/rand" "net" "time" "github.com/go-i2p/go-i2p/lib/common/data" "github.com/go-i2p/go-i2p/lib/crypto/curve25519" "github.com/go-i2p/go-i2p/lib/transport/ntcp/handshake" - "github.com/go-i2p/go-i2p/lib/transport/ntcp/messages" "github.com/samber/oops" ) -func (s *NTCP2Session) CreateSessionRequest() (*messages.SessionRequest, error) { - // Get our ephemeral key pair - ephemeralKey := make([]byte, 32) - if _, err := rand.Read(ephemeralKey); err != nil { - return nil, err - } - - // Add random padding (implementation specific) - randomInt, err := rand.Int(rand.Reader, big.NewInt(16)) - if err != nil { - return nil, err - } - - padding := make([]byte, randomInt.Int64()) // Up to 16 bytes of padding - if err != nil { - return nil, err - } - - netId, err := data.NewIntegerFromInt(2, 1) - if err != nil { - return nil, err - } - version, err := data.NewIntegerFromInt(2, 1) - if err != nil { - return nil, err - } - paddingLen, _, err := data.NewInteger([]byte{byte(len(padding))}, 1) - if err != nil { - return nil, err - } - //message3Part2Len, err := data.NewInteger() - //if err != nil { - // return nil, err - //} - timestamp, err := data.DateFromTime(s.GetCurrentTime()) - if err != nil { - return nil, err - } - requestOptions := &messages.RequestOptions{ - NetworkID: netId, - ProtocolVersion: version, - PaddingLength: paddingLen, - // Message3Part2Length: , - Timestamp: timestamp, - } - - return &messages.SessionRequest{ - XContent: [32]byte(ephemeralKey), - Options: *requestOptions, - Padding: padding, - }, nil -} - // DecryptOptionsBlock decrypts the options block from a SessionRequest message func (c *NTCP2Session) DecryptOptionsBlock(encryptedOptions []byte, obfuscatedX []byte, deobfuscatedX []byte) ([]byte, error) { return c.PerformAEADOperation( @@ -79,6 +22,30 @@ func (c *NTCP2Session) DecryptOptionsBlock(encryptedOptions []byte, obfuscatedX ) } +// 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) +} + +// validateTimestamp checks if the timestamp is within acceptable range +func (c *NTCP2Session) validateTimestamp(timestamp data.Date) error { + timestampTime := timestamp.Time() + + now := c.GetCurrentTime() + diff := now.Sub(timestampTime) + + // Allow ±60 seconds clock skew + if diff < -60*time.Second || diff > 60*time.Second { + log.Warnf("NTCP2: Rejecting SessionRequest - clock skew too large: %v", diff) + return oops.Errorf("clock skew too large: %v", diff) + } + + return nil +} + // readEphemeralKey reads the ephemeral key (X) from the connection func (c *NTCP2Session) readEphemeralKey(conn net.Conn) ([]byte, error) { ephemeralKey := make([]byte, 32) @@ -113,113 +80,6 @@ func (c *NTCP2Session) processEphemeralKey(obfuscatedX []byte, hs *handshake.Han return deobfuscatedX, nil } -// readOptionsBlock reads the encrypted options block from the connection -func (c *NTCP2Session) readOptionsBlock(conn net.Conn) ([]byte, error) { - // Options block with auth tag is 16 bytes minimum - optionsBlock := make([]byte, 16) - if _, err := io.ReadFull(conn, optionsBlock); err != nil { - if err == io.ErrUnexpectedEOF { - return nil, oops.Errorf("incomplete options block: connection closed prematurely") - } - return nil, oops.Errorf("failed to read options block: %w", err) - } - return optionsBlock, nil -} - -// processOptionsBlock decrypts and processes the options block -func (c *NTCP2Session) processOptionsBlock( - encryptedOptions []byte, - obfuscatedX []byte, - deobfuscatedX []byte, - hs *handshake.HandshakeState, -) (*messages.RequestOptions, error) { - // Decrypt options block - decryptedOptions, err := c.DecryptOptionsBlock(encryptedOptions, obfuscatedX, deobfuscatedX) - if err != nil { - c.addDelayForSecurity() - return nil, oops.Errorf("failed to decrypt options block: %w", err) - } - - // Minimum size for valid options - if len(decryptedOptions) < 9 { - return nil, oops.Errorf("options block too small: %d bytes", len(decryptedOptions)) - } - - // Parse network ID - networkID, _, err := data.NewInteger([]byte{decryptedOptions[0]}, 1) - if err != nil { - return nil, oops.Errorf("failed to parse network ID: %w", err) - } - - if networkID.Int() != 2 { - return nil, oops.Errorf("invalid network ID: %d", networkID.Int()) - } - - // Parse protocol version - protocolVersion, _, err := data.NewInteger([]byte{decryptedOptions[1]}, 1) - if err != nil { - return nil, oops.Errorf("failed to parse protocol version: %w", err) - } - - if protocolVersion.Int() != 2 { - return nil, oops.Errorf("unsupported protocol version: %d", protocolVersion.Int()) - } - - // Parse padding length - paddingLength, _, err := data.NewInteger([]byte{decryptedOptions[2]}, 1) - if err != nil { - return nil, oops.Errorf("failed to parse padding length: %w", err) - } - - // Parse message 3 part 2 length (2 bytes) - msg3p2Len, _, err := data.NewInteger(decryptedOptions[3:5], 2) - if err != nil { - return nil, oops.Errorf("failed to parse message 3 part 2 length: %w", err) - } - - // Parse timestamp (4 bytes) - timestamp, _, err := data.NewDate(decryptedOptions[5:9]) - if err != nil { - return nil, oops.Errorf("failed to parse timestamp: %w", err) - } - - // Validate timestamp - if err := c.validateTimestamp(*timestamp); err != nil { - return nil, err - } - - // Update handshake state - timestampVal := timestamp.Time() - hs.Timestamp = uint32(timestampVal.Unix()) - - // Construct the RequestOptions object - requestOptions := &messages.RequestOptions{ - NetworkID: networkID, - ProtocolVersion: protocolVersion, - PaddingLength: paddingLength, - Message3Part2Length: msg3p2Len, - Timestamp: timestamp, - } - - return requestOptions, nil -} - -// validateTimestamp checks if the timestamp is within acceptable range -func (c *NTCP2Session) validateTimestamp(timestamp data.Date) error { - timestampTime := timestamp.Time() - - now := c.GetCurrentTime() - diff := now.Sub(timestampTime) - - // Allow ±60 seconds clock skew - if diff < -60*time.Second || diff > 60*time.Second { - log.Warnf("NTCP2: Rejecting SessionRequest - clock skew too large: %v", diff) - return oops.Errorf("clock skew too large: %v", diff) - } - - return 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 @@ -239,10 +99,3 @@ func (c *NTCP2Session) readAndValidatePadding(conn net.Conn, paddingLen int) err // No need to validate padding content - it's random data return nil } - -// 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 - time.Sleep(delay) -} diff --git a/lib/transport/ntcp/session_request_new.go b/lib/transport/ntcp/session_request_new.go index d22f2bf..a52a336 100644 --- a/lib/transport/ntcp/session_request_new.go +++ b/lib/transport/ntcp/session_request_new.go @@ -2,6 +2,7 @@ package ntcp import ( "crypto/rand" + "io" "math/big" "net" @@ -64,7 +65,42 @@ func (s *SessionRequestProcessor) MessageType() messages.MessageType { // ProcessMessage implements handshake.HandshakeMessageProcessor. func (s *SessionRequestProcessor) ProcessMessage(message messages.Message, hs *handshake.HandshakeState) error { - panic("unimplemented") + req, ok := message.(*messages.SessionRequest) + if !ok { + return oops.Errorf("expected SessionRequest message, got %T", message) + } + + // Validate timestamp using existing method + if err := s.validateTimestamp(*req.Options.Timestamp); err != nil { + return err + } + + // Store padding length for message 3 if provided + if req.Options.PaddingLength != nil { + paddingLen := req.Options.PaddingLength.Int() + hs.RemotePaddingLen = paddingLen + } + + // Store message 3 part 2 length if provided + if req.Options.Message3Part2Length != nil { + hs.Message3Length = req.Options.Message3Part2Length.Int() + } + + log.Debugf("NTCP2: Session request processed successfully") + return nil +} + +// readOptionsBlock reads the encrypted options block from the connection +func (c *SessionRequestProcessor) readOptionsBlock(conn net.Conn) ([]byte, error) { + // Options block with auth tag is 16 bytes minimum + optionsBlock := make([]byte, 16) + if _, err := io.ReadFull(conn, optionsBlock); err != nil { + if err == io.ErrUnexpectedEOF { + return nil, oops.Errorf("incomplete options block: connection closed prematurely") + } + return nil, oops.Errorf("failed to read options block: %w", err) + } + return optionsBlock, nil } // ReadMessage reads a SessionRequest message from the connection @@ -76,19 +112,19 @@ func (p *SessionRequestProcessor) ReadMessage(conn net.Conn, hs *handshake.Hands } // 2. Process ephemeral key - deobfuscatedX, err := p.NTCP2Session.processEphemeralKey(obfuscatedX, hs) + deobfuscatedX, err := p.processEphemeralKey(obfuscatedX, hs) if err != nil { return nil, err } // 3. Read options block - encryptedOptions, err := p.NTCP2Session.readOptionsBlock(conn) + encryptedOptions, err := p.readOptionsBlock(conn) if err != nil { return nil, err } // 4. Process options block - options, err := p.NTCP2Session.processOptionsBlock(encryptedOptions, obfuscatedX, deobfuscatedX, hs) + options, err := p.processOptionsBlock(encryptedOptions, obfuscatedX, deobfuscatedX, hs) if err != nil { return nil, err } @@ -186,4 +222,82 @@ func (p *SessionRequestProcessor) ObfuscateKey(message messages.Message, hs *han return p.NTCP2Session.ObfuscateEphemeral(req.XContent[:]) } +// processOptionsBlock decrypts and processes the options block from the session request +func (p *SessionRequestProcessor) processOptionsBlock( + encryptedOptions []byte, + obfuscatedX []byte, + deobfuscatedX []byte, + hs *handshake.HandshakeState, +) (*messages.RequestOptions, error) { + // Decrypt options block + decryptedOptions, err := p.NTCP2Session.DecryptOptionsBlock(encryptedOptions, obfuscatedX, deobfuscatedX) + if err != nil { + p.addDelayForSecurity() + return nil, oops.Errorf("failed to decrypt options block: %w", err) + } + + // Minimum size for valid options + if len(decryptedOptions) < 9 { + return nil, oops.Errorf("options block too small: %d bytes", len(decryptedOptions)) + } + + // Parse network ID + networkID, _, err := data.NewInteger([]byte{decryptedOptions[0]}, 1) + if err != nil { + return nil, oops.Errorf("failed to parse network ID: %w", err) + } + + if networkID.Int() != 2 { + return nil, oops.Errorf("invalid network ID: %d", networkID.Int()) + } + + // Parse protocol version + protocolVersion, _, err := data.NewInteger([]byte{decryptedOptions[1]}, 1) + if err != nil { + return nil, oops.Errorf("failed to parse protocol version: %w", err) + } + + if protocolVersion.Int() != 2 { + return nil, oops.Errorf("unsupported protocol version: %d", protocolVersion.Int()) + } + + // Parse padding length + paddingLength, _, err := data.NewInteger([]byte{decryptedOptions[2]}, 1) + if err != nil { + return nil, oops.Errorf("failed to parse padding length: %w", err) + } + + // Parse message 3 part 2 length (2 bytes) + msg3p2Len, _, err := data.NewInteger(decryptedOptions[3:5], 2) + if err != nil { + return nil, oops.Errorf("failed to parse message 3 part 2 length: %w", err) + } + + // Parse timestamp (4 bytes) + timestamp, _, err := data.NewDate(decryptedOptions[5:9]) + if err != nil { + return nil, oops.Errorf("failed to parse timestamp: %w", err) + } + + // Validate timestamp + if err := p.validateTimestamp(*timestamp); err != nil { + return nil, err + } + + // Update handshake state + timestampVal := timestamp.Time() + hs.Timestamp = uint32(timestampVal.Unix()) + + // Construct the RequestOptions object + requestOptions := &messages.RequestOptions{ + NetworkID: networkID, + ProtocolVersion: protocolVersion, + PaddingLength: paddingLength, + Message3Part2Length: msg3p2Len, + Timestamp: timestamp, + } + + return requestOptions, nil +} + var _ handshake.HandshakeMessageProcessor = (*SessionRequestProcessor)(nil)