mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-06-16 13:54:49 -04:00
Implement Sender for Handshake Message 1
This commit is contained in:
@ -10,20 +10,20 @@ import (
|
||||
type Curve25519PrivateKey []byte
|
||||
|
||||
// Bytes implements types.PrivateKey.
|
||||
func (k *Curve25519PrivateKey) Bytes() []byte {
|
||||
return []byte(*k) // Return the byte slice representation of the private key
|
||||
func (k Curve25519PrivateKey) Bytes() []byte {
|
||||
return k // Return the byte slice representation of the private key
|
||||
}
|
||||
|
||||
// Public implements types.PrivateKey.
|
||||
func (k *Curve25519PrivateKey) Public() (types.SigningPublicKey, error) {
|
||||
func (k Curve25519PrivateKey) Public() (types.SigningPublicKey, error) {
|
||||
// Create a proper x25519.PrivateKey from the byte slice
|
||||
if len(*k) != x25519.PrivateKeySize {
|
||||
if len(k) != x25519.PrivateKeySize {
|
||||
// Handle invalid private key length
|
||||
return nil, ErrInvalidPrivateKey
|
||||
}
|
||||
// Create a proper x25519.PrivateKey from the byte slice
|
||||
privKey := make(x25519.PrivateKey, x25519.PrivateKeySize)
|
||||
copy(privKey, *k)
|
||||
copy(privKey, k)
|
||||
// Derive the public key from the private key
|
||||
pubKey := privKey.Public() // This will return the corresponding public key
|
||||
x25519PubKey := pubKey.(curve25519.PublicKey)
|
||||
@ -32,10 +32,10 @@ func (k *Curve25519PrivateKey) Public() (types.SigningPublicKey, error) {
|
||||
}
|
||||
|
||||
// Zero implements types.PrivateKey.
|
||||
func (k *Curve25519PrivateKey) Zero() {
|
||||
func (k Curve25519PrivateKey) Zero() {
|
||||
// replace the slice with zeroes
|
||||
for i := range *k {
|
||||
(*k)[i] = 0
|
||||
for i := range k {
|
||||
(k)[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,37 @@ func (c *NTCP2Session) sendSessionRequest(conn net.Conn, hs *HandshakeState) err
|
||||
// Implement according to NTCP2 spec
|
||||
// 1. Create and send X (ephemeral key) | Padding
|
||||
// uses CreateSessionRequest from session_request.go
|
||||
sessionRequestMessage, err := c.CreateSessionRequest()
|
||||
if err != nil {
|
||||
return oops.Errorf("failed to create session request: %v", err)
|
||||
}
|
||||
// 2. Set deadline for the connection
|
||||
if err := conn.SetDeadline(time.Now().Add(NTCP2_HANDSHAKE_TIMEOUT)); err != nil {
|
||||
return oops.Errorf("failed to set deadline: %v", err)
|
||||
}
|
||||
// 3. Obfuscate the session request message
|
||||
obfuscatedX, err := c.ObfuscateEphemeral(sessionRequestMessage.XContent[:])
|
||||
if err != nil {
|
||||
return oops.Errorf("failed to obfuscate ephemeral key: %v", err)
|
||||
}
|
||||
// 4. ChaChaPoly Frame
|
||||
// Encrypt options block and authenticate both options and padding
|
||||
ciphertext, err := c.encryptSessionRequestOptions(sessionRequestMessage, obfuscatedX)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Combine all components into final message
|
||||
// 1. Obfuscated X (already in obfuscatedX)
|
||||
// 2. ChaCha20-Poly1305 encrypted options with auth tag
|
||||
// 3. Authenticated but unencrypted padding
|
||||
message := append(obfuscatedX, ciphertext...)
|
||||
message = append(message, sessionRequestMessage.Padding...)
|
||||
|
||||
// 5. Write the message to the connection
|
||||
if _, err := conn.Write(message); err != nil {
|
||||
return oops.Errorf("failed to send session request: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -77,6 +108,7 @@ func (c *NTCP2Session) sendSessionRequest(conn net.Conn, hs *HandshakeState) err
|
||||
func (c *NTCP2Session) receiveSessionRequest(conn net.Conn, hs *HandshakeState) error {
|
||||
// Implement according to NTCP2 spec
|
||||
// TODO: Implement Message 1 processing
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1,90 +1,12 @@
|
||||
package ntcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/flynn/noise"
|
||||
"github.com/samber/oops"
|
||||
)
|
||||
|
||||
// Modify ComposeInitiatorHandshakeMessage in outgoing_handshake.go
|
||||
// At the moment, remoteStatic is stored in the NTCP2Session() and doesn't need to be passed as an argument.
|
||||
// You actually get it directly out of the remote RouterInfo, which the NoiseSession also has access to.
|
||||
// So maybe, the interface should change so that we:
|
||||
// - A: get the localStatic out of the parent NTCP2Transport's routerInfo, which is the "local" routerInfo
|
||||
// - B: get the remoteStatic out of the NTCP2Session router, which is the "remote" routerInfo
|
||||
func (c *NTCP2Session) ComposeInitiatorHandshakeMessage(
|
||||
localStatic noise.DHKey,
|
||||
remoteStatic []byte,
|
||||
payload []byte,
|
||||
ephemeralPrivate []byte,
|
||||
) (
|
||||
negotiationData,
|
||||
handshakeMessage []byte,
|
||||
handshakeState *noise.HandshakeState,
|
||||
err error,
|
||||
) {
|
||||
// Create session request with obfuscated ephemeral key
|
||||
request, err := c.CreateSessionRequest()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Initialize negotiation data with NTCP2 protocol specifics
|
||||
negotiationData = initNegotiationData(nil)
|
||||
|
||||
// Buffer for the complete message
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
obfuscatedKey, err := c.ObfuscateEphemeral(request.XContent[:])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if wrote, err := buf.Write(obfuscatedKey); err != nil {
|
||||
return nil, nil, nil, err
|
||||
} else {
|
||||
log.Debugf("Wrote %d bytes of obfuscated key", wrote)
|
||||
}
|
||||
|
||||
// Write options block
|
||||
if wrote, err := buf.Write(request.Options.Data()); err != nil {
|
||||
return nil, nil, nil, err
|
||||
} else {
|
||||
log.Debugf("Wrote %d bytes of obfuscated key", wrote)
|
||||
}
|
||||
|
||||
// Initialize Noise
|
||||
config := noise.Config{
|
||||
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256),
|
||||
Pattern: noise.HandshakeXK,
|
||||
Initiator: true,
|
||||
StaticKeypair: localStatic,
|
||||
PeerStatic: remoteStatic, // Add the peer's static key
|
||||
Random: rand.Reader,
|
||||
}
|
||||
|
||||
handshakeState, err = noise.NewHandshakeState(config)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Create Noise message - this contains the encrypted payload (options block)
|
||||
// WriteMessage encrypts the payload and returns the message
|
||||
handshakeMessage, _, _, err = handshakeState.WriteMessage(nil, buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Add padding
|
||||
handshakeMessage = append(handshakeMessage, request.Padding...)
|
||||
|
||||
// Return the complete handshake message
|
||||
return negotiationData, handshakeMessage, handshakeState, nil
|
||||
}
|
||||
|
||||
// PerformOutboundHandshake initiates and completes a handshake as the initiator
|
||||
func (c *NTCP2Session) PerformOutboundHandshake(conn net.Conn, hs *HandshakeState) error {
|
||||
// Set deadline for the entire handshake process
|
||||
|
@ -1,8 +1,15 @@
|
||||
package ntcp
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
|
||||
"github.com/go-i2p/go-i2p/lib/common/router_info"
|
||||
"github.com/go-i2p/go-i2p/lib/crypto/aes"
|
||||
"github.com/go-i2p/go-i2p/lib/transport/messages"
|
||||
"github.com/go-i2p/go-i2p/lib/transport/noise"
|
||||
"github.com/go-i2p/go-i2p/lib/transport/obfs"
|
||||
"github.com/go-i2p/go-i2p/lib/transport/padding"
|
||||
@ -124,3 +131,69 @@ func (s *NTCP2Session) buildAesStaticKey() (*aes.AESSymmetricKey, error) {
|
||||
AESStaticKey.IV = staticIV[:]
|
||||
return &AESStaticKey, nil
|
||||
}
|
||||
|
||||
func (s *NTCP2Session) deriveChacha20Key(ephemeralKey []byte) ([]byte, error) {
|
||||
remoteStaticKey, err := s.peerStaticKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Perform DH between Alice's ephemeral key and Bob's static key
|
||||
// This is the "es" operation in Noise XK
|
||||
sharedSecret, err := s.computeSharedSecret(ephemeralKey, remoteStaticKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply KDF to derive the key
|
||||
// This typically involves HKDF with appropriate info string
|
||||
hashProtocol := crypto.SHA256
|
||||
h := hmac.New(hashProtocol.New, []byte("NTCP2-KDF1"))
|
||||
h.Write(sharedSecret)
|
||||
return h.Sum(nil)[:32], nil // ChaCha20 requires a 32-byte key
|
||||
}
|
||||
|
||||
func (c *NTCP2Session) computeSharedSecret(ephemeralKey, param []byte) ([]byte, error) {
|
||||
if len(ephemeralKey) != 32 || len(param) != 32 {
|
||||
return nil, oops.Errorf("invalid key length, expected 32 bytes")
|
||||
}
|
||||
|
||||
// Convert byte slices to X25519 keys
|
||||
var ephKey, staticKey [32]byte
|
||||
copy(ephKey[:], ephemeralKey)
|
||||
copy(staticKey[:], param)
|
||||
// Compute the shared secret using X25519
|
||||
var sharedSecret [32]byte
|
||||
shared, err := curve25519.X25519(ephKey[:], staticKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(sharedSecret[:], shared)
|
||||
|
||||
return sharedSecret[:], nil
|
||||
}
|
||||
|
||||
func (c *NTCP2Session) encryptSessionRequestOptions(sessionRequestMessage *messages.SessionRequest, obfuscatedX []byte) ([]byte, error) {
|
||||
chacha20Key, err := c.deriveChacha20Key(sessionRequestMessage.XContent[:])
|
||||
if err != nil {
|
||||
return nil, oops.Errorf("failed to derive ChaCha20 key: %v", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Create associated data (AD) according to NTCP2 spec:
|
||||
// AD = obfuscated X value (ensures binding between the AES and ChaCha layers)
|
||||
ad := obfuscatedX
|
||||
|
||||
// Encrypt options block and authenticate both options and padding
|
||||
// ChaCha20-Poly1305 encrypts plaintext and appends auth tag
|
||||
optionsData := sessionRequestMessage.Options.Data()
|
||||
ciphertext := aead.Seal(nil, nonce, optionsData, ad)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user