mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-06-16 22:10:48 -04:00
More duplicated code reduction, interface improvement
This commit is contained in:
@ -7,6 +7,23 @@ import (
|
|||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AEADOperator defines the interface for AEAD operations in the NTCP2 protocol
|
||||||
|
type AEADOperator interface {
|
||||||
|
// EncryptWithAssociatedData encrypts data using the provided key and associated data
|
||||||
|
EncryptWithAssociatedData(key, data, associatedData []byte, nonceCounter uint64) ([]byte, error)
|
||||||
|
|
||||||
|
// DecryptWithAssociatedData decrypts data using the provided key and associated data
|
||||||
|
DecryptWithAssociatedData(key, data, associatedData []byte, nonceCounter uint64) ([]byte, error)
|
||||||
|
|
||||||
|
// EncryptWithDerivedKey encrypts data, deriving the key from raw key material first
|
||||||
|
EncryptWithDerivedKey(keyMaterial, data, associatedData []byte, nonceCounter uint64) ([]byte, error)
|
||||||
|
|
||||||
|
// DecryptWithDerivedKey decrypts data, deriving the key from raw key material first
|
||||||
|
DecryptWithDerivedKey(keyMaterial, data, associatedData []byte, nonceCounter uint64) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ AEADOperator = (*NTCP2Session)(nil)
|
||||||
|
|
||||||
// PerformAEADOperation handles both encryption and decryption using ChaCha20-Poly1305
|
// PerformAEADOperation handles both encryption and decryption using ChaCha20-Poly1305
|
||||||
func (c *NTCP2Session) PerformAEADOperation(
|
func (c *NTCP2Session) PerformAEADOperation(
|
||||||
keyMaterial []byte, // Raw key material to derive key from
|
keyMaterial []byte, // Raw key material to derive key from
|
||||||
@ -48,3 +65,61 @@ func (c *NTCP2Session) PerformAEADOperation(
|
|||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncryptWithAssociatedData encrypts data using ChaCha20-Poly1305 with the provided key and associated data
|
||||||
|
func (c *NTCP2Session) EncryptWithAssociatedData(
|
||||||
|
key []byte,
|
||||||
|
data []byte,
|
||||||
|
associatedData []byte,
|
||||||
|
nonceCounter uint64,
|
||||||
|
) ([]byte, error) {
|
||||||
|
return c.PerformAEADOperation(key, data, associatedData, nonceCounter, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptWithAssociatedData decrypts data using ChaCha20-Poly1305 with the provided key and associated data
|
||||||
|
func (c *NTCP2Session) DecryptWithAssociatedData(
|
||||||
|
key []byte,
|
||||||
|
data []byte,
|
||||||
|
associatedData []byte,
|
||||||
|
nonceCounter uint64,
|
||||||
|
) ([]byte, error) {
|
||||||
|
return c.PerformAEADOperation(key, data, associatedData, nonceCounter, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformAEADWithDerivedKey performs AEAD operation, deriving the key from raw key material first
|
||||||
|
func (c *NTCP2Session) PerformAEADWithDerivedKey(
|
||||||
|
keyMaterial []byte, // Raw key material to derive key from
|
||||||
|
data []byte,
|
||||||
|
associatedData []byte,
|
||||||
|
nonceCounter uint64,
|
||||||
|
encrypt bool,
|
||||||
|
) ([]byte, error) {
|
||||||
|
// 1. Derive key using KDF
|
||||||
|
key, err := c.deriveChacha20Key(keyMaterial)
|
||||||
|
if err != nil {
|
||||||
|
return nil, oops.Errorf("failed to derive ChaCha20 key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Perform the AEAD operation
|
||||||
|
return c.PerformAEADOperation(key, data, associatedData, nonceCounter, encrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptWithDerivedKey encrypts data, deriving the key from raw key material first
|
||||||
|
func (c *NTCP2Session) EncryptWithDerivedKey(
|
||||||
|
keyMaterial []byte,
|
||||||
|
data []byte,
|
||||||
|
associatedData []byte,
|
||||||
|
nonceCounter uint64,
|
||||||
|
) ([]byte, error) {
|
||||||
|
return c.PerformAEADWithDerivedKey(keyMaterial, data, associatedData, nonceCounter, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptWithDerivedKey decrypts data, deriving the key from raw key material first
|
||||||
|
func (c *NTCP2Session) DecryptWithDerivedKey(
|
||||||
|
keyMaterial []byte,
|
||||||
|
data []byte,
|
||||||
|
associatedData []byte,
|
||||||
|
nonceCounter uint64,
|
||||||
|
) ([]byte, error) {
|
||||||
|
return c.PerformAEADWithDerivedKey(keyMaterial, data, associatedData, nonceCounter, false)
|
||||||
|
}
|
||||||
|
@ -30,6 +30,8 @@ type HandshakeState struct {
|
|||||||
RemotePaddingLen int
|
RemotePaddingLen int
|
||||||
// chachaKey is the derived ChaCha20 symmetric key for the session
|
// chachaKey is the derived ChaCha20 symmetric key for the session
|
||||||
ChachaKey []byte
|
ChachaKey []byte
|
||||||
|
// HandshakeHash is the cumulative hash of the handshake
|
||||||
|
HandshakeHash []byte
|
||||||
// sharedSecret is the Diffie-Hellman shared secret computed during handshake
|
// sharedSecret is the Diffie-Hellman shared secret computed during handshake
|
||||||
SharedSecret []byte
|
SharedSecret []byte
|
||||||
// timestamp is the Unix timestamp when handshake was initiated
|
// timestamp is the Unix timestamp when handshake was initiated
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/go-i2p/go-i2p/lib/crypto/aes"
|
"github.com/go-i2p/go-i2p/lib/crypto/aes"
|
||||||
"github.com/go-i2p/go-i2p/lib/transport/noise"
|
"github.com/go-i2p/go-i2p/lib/transport/noise"
|
||||||
"github.com/go-i2p/go-i2p/lib/transport/ntcp/handshake"
|
"github.com/go-i2p/go-i2p/lib/transport/ntcp/handshake"
|
||||||
|
"github.com/go-i2p/go-i2p/lib/transport/ntcp/kdf"
|
||||||
"github.com/go-i2p/go-i2p/lib/transport/obfs"
|
"github.com/go-i2p/go-i2p/lib/transport/obfs"
|
||||||
"github.com/go-i2p/go-i2p/lib/transport/padding"
|
"github.com/go-i2p/go-i2p/lib/transport/padding"
|
||||||
"github.com/go-i2p/go-i2p/lib/util/time/sntp"
|
"github.com/go-i2p/go-i2p/lib/util/time/sntp"
|
||||||
@ -37,6 +38,14 @@ import (
|
|||||||
type NTCP2Session struct {
|
type NTCP2Session struct {
|
||||||
*noise.NoiseSession
|
*noise.NoiseSession
|
||||||
*NTCP2Transport
|
*NTCP2Transport
|
||||||
|
// Session keys for encrypted communication
|
||||||
|
inboundKey []byte // Key for decrypting incoming messages
|
||||||
|
outboundKey []byte // Key for encrypting outgoing messages
|
||||||
|
// Keys for length obfuscation using SipHash
|
||||||
|
lengthEncryptKey1 []byte // First SipHash key (k1)
|
||||||
|
lengthEncryptKey2 []byte // Second SipHash key (k2)
|
||||||
|
// Key for frame obfuscation in data phase
|
||||||
|
framingKey []byte
|
||||||
paddingStrategy padding.PaddingStrategy
|
paddingStrategy padding.PaddingStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +182,72 @@ func (c *NTCP2Session) computeSharedSecret(ephemeralKey, param []byte) ([]byte,
|
|||||||
|
|
||||||
// deriveSessionKeys computes the session keys from the completed handshake
|
// deriveSessionKeys computes the session keys from the completed handshake
|
||||||
func (c *NTCP2Session) deriveSessionKeys(hs *handshake.HandshakeState) error {
|
func (c *NTCP2Session) deriveSessionKeys(hs *handshake.HandshakeState) error {
|
||||||
// Use shared secrets to derive session keys
|
// Create KDF context if not already present
|
||||||
// TODO: Implement key derivation according to NTCP2 spec
|
kdfContext := kdf.NewNTCP2KDF()
|
||||||
|
|
||||||
|
// If we have a handshake hash from the handshake state, use it
|
||||||
|
if len(hs.HandshakeHash) > 0 {
|
||||||
|
kdfContext.HandshakeHash = hs.HandshakeHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a chaining key from the handshake state, use it
|
||||||
|
if len(hs.ChachaKey) > 0 {
|
||||||
|
kdfContext.ChainingKey = hs.ChachaKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the final session keys for bidirectional communication
|
||||||
|
keyAB, keyBA, err := kdfContext.DeriveKeys()
|
||||||
|
if err != nil {
|
||||||
|
return oops.Errorf("failed to derive session keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the session keys based on whether we're the initiator or responder
|
||||||
|
if hs.IsInitiator {
|
||||||
|
// For initiator (Alice), outbound = Alice->Bob, inbound = Bob->Alice
|
||||||
|
c.outboundKey = keyAB
|
||||||
|
c.inboundKey = keyBA
|
||||||
|
} else {
|
||||||
|
// For responder (Bob), outbound = Bob->Alice, inbound = Alice->Bob
|
||||||
|
c.outboundKey = keyBA
|
||||||
|
c.inboundKey = keyAB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive SipHash keys for length obfuscation
|
||||||
|
sipHashKey, err := kdfContext.DeriveSipHashKey()
|
||||||
|
if err != nil {
|
||||||
|
return oops.Errorf("failed to derive SipHash keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SipHash requires two 8-byte keys (k1, k2) and an 8-byte IV
|
||||||
|
// The sipHashKey is 16 bytes - first 8 bytes are k1, next 8 bytes are k2
|
||||||
|
if len(sipHashKey) < 16 {
|
||||||
|
return oops.Errorf("derived SipHash key too short: %d bytes", len(sipHashKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up length obfuscation
|
||||||
|
c.lengthEncryptKey1 = sipHashKey[:8]
|
||||||
|
c.lengthEncryptKey2 = sipHashKey[8:16]
|
||||||
|
|
||||||
|
// Derive framing key for data phase
|
||||||
|
framingKey, err := kdfContext.DeriveFramingKey()
|
||||||
|
if err != nil {
|
||||||
|
return oops.Errorf("failed to derive framing key: %w", err)
|
||||||
|
}
|
||||||
|
c.framingKey = framingKey
|
||||||
|
|
||||||
|
// Clear sensitive key material from the KDF context
|
||||||
|
// to prevent leaking it in memory
|
||||||
|
for i := range kdfContext.ChainingKey {
|
||||||
|
kdfContext.ChainingKey[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// For additional security, also clear the handshake state keys
|
||||||
|
// that are no longer needed
|
||||||
|
for i := range hs.ChachaKey {
|
||||||
|
hs.ChachaKey[i] = 0
|
||||||
|
}
|
||||||
|
hs.ChachaKey = nil
|
||||||
|
|
||||||
|
log.Debugf("NTCP2: Session keys derived successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/go-i2p/go-i2p/lib/transport/ntcp/kdf"
|
"github.com/go-i2p/go-i2p/lib/transport/ntcp/kdf"
|
||||||
"github.com/go-i2p/go-i2p/lib/transport/ntcp/messages"
|
"github.com/go-i2p/go-i2p/lib/transport/ntcp/messages"
|
||||||
"github.com/samber/oops"
|
"github.com/samber/oops"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *NTCP2Session) CreateSessionRequest() (*messages.SessionRequest, error) {
|
func (s *NTCP2Session) CreateSessionRequest() (*messages.SessionRequest, error) {
|
||||||
@ -271,17 +270,8 @@ func (c *NTCP2Session) encryptSessionRequestOptions(
|
|||||||
kdfContext.MixHash(obfuscatedX)
|
kdfContext.MixHash(obfuscatedX)
|
||||||
|
|
||||||
// Create AEAD cipher
|
// Create AEAD cipher
|
||||||
aead, err := chacha20poly1305.New(chacha20Key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, oops.Errorf("failed to create ChaCha20-Poly1305 cipher: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the nonce (all zeros for first message)
|
|
||||||
nonce := make([]byte, chacha20poly1305.NonceSize)
|
|
||||||
|
|
||||||
// Encrypt options block using associated data
|
|
||||||
optionsData := sessionRequestMessage.Options.Data()
|
optionsData := sessionRequestMessage.Options.Data()
|
||||||
ciphertext := aead.Seal(nil, nonce, optionsData, obfuscatedX)
|
ciphertext, err := c.EncryptWithAssociatedData(chacha20Key, optionsData, obfuscatedX, 0)
|
||||||
|
|
||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,17 @@ func (p *SessionRequestProcessor) EncryptPayload(
|
|||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
req, ok := message.(*messages.SessionRequest)
|
req, ok := message.(*messages.SessionRequest)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, oops.Errorf("expected SessionRequest message")
|
return nil, oops.Errorf("expected SessionRequest message, got %T", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.NTCP2Session.encryptSessionRequestOptions(req, obfuscatedKey)
|
// Use the central AEAD operation instead of custom encryption
|
||||||
|
// The key material would be derived in this case
|
||||||
|
return p.NTCP2Session.EncryptWithDerivedKey(
|
||||||
|
hs.LocalEphemeral.Bytes(),
|
||||||
|
req.Options.Data(),
|
||||||
|
obfuscatedKey, // Using obfuscated key as associated data
|
||||||
|
0, // First message uses nonce counter 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageType implements handshake.HandshakeMessageProcessor.
|
// MessageType implements handshake.HandshakeMessageProcessor.
|
||||||
|
Reference in New Issue
Block a user