package noise
import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"os"
"runtime/debug"
"time"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/libp2p/go-libp2p/p2p/security/noise/pb"
"github.com/flynn/noise"
pool "github.com/libp2p/go-buffer-pool"
"google.golang.org/protobuf/proto"
)
const payloadSigPrefix = "noise-libp2p-static-key:"
var cipherSuite = noise .NewCipherSuite (noise .DH25519 , noise .CipherChaChaPoly , noise .HashSHA256 )
func (s *secureSession ) runHandshake (ctx context .Context ) (err error ) {
defer func () {
if rerr := recover (); rerr != nil {
fmt .Fprintf (os .Stderr , "caught panic: %s\n%s\n" , rerr , debug .Stack ())
err = fmt .Errorf ("panic in Noise handshake: %s" , rerr )
}
}()
kp , err := noise .DH25519 .GenerateKeypair (rand .Reader )
if err != nil {
return fmt .Errorf ("error generating static keypair: %w" , err )
}
cfg := noise .Config {
CipherSuite : cipherSuite ,
Pattern : noise .HandshakeXX ,
Initiator : s .initiator ,
StaticKeypair : kp ,
Prologue : s .prologue ,
}
hs , err := noise .NewHandshakeState (cfg )
if err != nil {
return fmt .Errorf ("error initializing handshake state: %w" , err )
}
if deadline , ok := ctx .Deadline (); ok {
if err := s .SetDeadline (deadline ); err == nil {
defer s .SetDeadline (time .Time {})
}
}
hbuf := pool .Get (2 << 10 )
defer pool .Put (hbuf )
if s .initiator {
if err := s .sendHandshakeMessage (hs , nil , hbuf ); err != nil {
return fmt .Errorf ("error sending handshake message: %w" , err )
}
plaintext , err := s .readHandshakeMessage (hs )
if err != nil {
return fmt .Errorf ("error reading handshake message: %w" , err )
}
rcvdEd , err := s .handleRemoteHandshakePayload (plaintext , hs .PeerStatic ())
if err != nil {
return err
}
if s .initiatorEarlyDataHandler != nil {
if err := s .initiatorEarlyDataHandler .Received (ctx , s .insecureConn , rcvdEd ); err != nil {
return err
}
}
var ed *pb .NoiseExtensions
if s .initiatorEarlyDataHandler != nil {
ed = s .initiatorEarlyDataHandler .Send (ctx , s .insecureConn , s .remoteID )
}
payload , err := s .generateHandshakePayload (kp , ed )
if err != nil {
return err
}
if err := s .sendHandshakeMessage (hs , payload , hbuf ); err != nil {
return fmt .Errorf ("error sending handshake message: %w" , err )
}
return nil
} else {
if _ , err := s .readHandshakeMessage (hs ); err != nil {
return fmt .Errorf ("error reading handshake message: %w" , err )
}
var ed *pb .NoiseExtensions
if s .responderEarlyDataHandler != nil {
ed = s .responderEarlyDataHandler .Send (ctx , s .insecureConn , s .remoteID )
}
payload , err := s .generateHandshakePayload (kp , ed )
if err != nil {
return err
}
if err := s .sendHandshakeMessage (hs , payload , hbuf ); err != nil {
return fmt .Errorf ("error sending handshake message: %w" , err )
}
plaintext , err := s .readHandshakeMessage (hs )
if err != nil {
return fmt .Errorf ("error reading handshake message: %w" , err )
}
rcvdEd , err := s .handleRemoteHandshakePayload (plaintext , hs .PeerStatic ())
if err != nil {
return err
}
if s .responderEarlyDataHandler != nil {
if err := s .responderEarlyDataHandler .Received (ctx , s .insecureConn , rcvdEd ); err != nil {
return err
}
}
return nil
}
}
func (s *secureSession ) setCipherStates (cs1 , cs2 *noise .CipherState ) {
if s .initiator {
s .enc = cs1
s .dec = cs2
} else {
s .enc = cs2
s .dec = cs1
}
}
func (s *secureSession ) sendHandshakeMessage (hs *noise .HandshakeState , payload []byte , hbuf []byte ) error {
bz , cs1 , cs2 , err := hs .WriteMessage (hbuf [:LengthPrefixLength ], payload )
if err != nil {
return err
}
binary .BigEndian .PutUint16 (bz , uint16 (len (bz )-LengthPrefixLength ))
_, err = s .writeMsgInsecure (bz )
if err != nil {
return err
}
if cs1 != nil && cs2 != nil {
s .setCipherStates (cs1 , cs2 )
}
return nil
}
func (s *secureSession ) readHandshakeMessage (hs *noise .HandshakeState ) ([]byte , error ) {
l , err := s .readNextInsecureMsgLen ()
if err != nil {
return nil , err
}
buf := pool .Get (l )
defer pool .Put (buf )
if err := s .readNextMsgInsecure (buf ); err != nil {
return nil , err
}
msg , cs1 , cs2 , err := hs .ReadMessage (nil , buf )
if err != nil {
return nil , err
}
if cs1 != nil && cs2 != nil {
s .setCipherStates (cs1 , cs2 )
}
return msg , nil
}
func (s *secureSession ) generateHandshakePayload (localStatic noise .DHKey , ext *pb .NoiseExtensions ) ([]byte , error ) {
localKeyRaw , err := crypto .MarshalPublicKey (s .LocalPublicKey ())
if err != nil {
return nil , fmt .Errorf ("error serializing libp2p identity key: %w" , err )
}
toSign := append ([]byte (payloadSigPrefix ), localStatic .Public ...)
signedPayload , err := s .localKey .Sign (toSign )
if err != nil {
return nil , fmt .Errorf ("error sigining handshake payload: %w" , err )
}
payloadEnc , err := proto .Marshal (&pb .NoiseHandshakePayload {
IdentityKey : localKeyRaw ,
IdentitySig : signedPayload ,
Extensions : ext ,
})
if err != nil {
return nil , fmt .Errorf ("error marshaling handshake payload: %w" , err )
}
return payloadEnc , nil
}
func (s *secureSession ) handleRemoteHandshakePayload (payload []byte , remoteStatic []byte ) (*pb .NoiseExtensions , error ) {
nhp := new (pb .NoiseHandshakePayload )
err := proto .Unmarshal (payload , nhp )
if err != nil {
return nil , fmt .Errorf ("error unmarshaling remote handshake payload: %w" , err )
}
remotePubKey , err := crypto .UnmarshalPublicKey (nhp .GetIdentityKey ())
if err != nil {
return nil , err
}
id , err := peer .IDFromPublicKey (remotePubKey )
if err != nil {
return nil , err
}
if s .checkPeerID && s .remoteID != id {
return nil , sec .ErrPeerIDMismatch {Expected : s .remoteID , Actual : id }
}
sig := nhp .GetIdentitySig ()
msg := append ([]byte (payloadSigPrefix ), remoteStatic ...)
ok , err := remotePubKey .Verify (msg , sig )
if err != nil {
return nil , fmt .Errorf ("error verifying signature: %w" , err )
} else if !ok {
return nil , fmt .Errorf ("handshake signature invalid" )
}
s .remoteID = id
s .remoteKey = remotePubKey
return nhp .Extensions , nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .