// Accepts an incoming tunneling request from a remote, initializes and stores // the new tunnel into the connection state. func (c *Connection) buildTunnel(remote uint64, id uint64, key []byte, addrs []string, timeout time.Duration) (*Tunnel, error) { deadline := time.Now().Add(timeout) // Create the local tunnel endpoint c.tunLock.Lock() tunId := c.tunIdx tun := &Tunnel{ id: tunId, owner: c, term: make(chan struct{}), } c.tunIdx++ c.tunLive[tunId] = tun c.tunLock.Unlock() // Dial the remote tunnel listener var err error var strm *stream.Stream for _, addr := range addrs { strm, err = stream.Dial(addr, timeout) if err == nil { break } } // If no error occurred, initialize the client endpoint if err == nil { var conn *link.Link conn, err = c.initClientTunnel(strm, remote, id, key, deadline) if err != nil { if err := strm.Close(); err != nil { log.Printf("iris: failed to close uninitialized client tunnel stream: %v.", err) } } else { // Make sure the tunnel wasn't terminated since (init/close race) tun.lock.Lock() select { case <-tun.term: conn.Close() err = ErrTerminating default: tun.conn = conn } tun.lock.Unlock() } } // Tunneling failed, clean up and report error if err != nil { c.tunLock.Lock() delete(c.tunLive, tunId) c.tunLock.Unlock() return nil, err } return tun, nil }
// Initializes a stream into an encrypted tunnel link. func (c *Connection) initClientTunnel(strm *stream.Stream, remote uint64, id uint64, key []byte, deadline time.Time) (*link.Link, error) { // Set a socket deadline for finishing the handshake strm.Sock().SetDeadline(deadline) defer strm.Sock().SetDeadline(time.Time{}) // Send the unencrypted tunnel id to associate with the remote tunnel init := &initPacket{ConnId: remote, TunId: id} if err := strm.Send(init); err != nil { return nil, err } // Create the encrypted link and authorize it hasher := func() hash.Hash { return config.HkdfHash.New() } hkdf := hkdf.New(hasher, key, config.HkdfSalt, config.HkdfInfo) conn := link.New(strm, hkdf, false) // Send and retrieve an authorization to verify both directions auth := &proto.Message{ Head: proto.Header{ Meta: &authPacket{Id: id}, }, } if err := conn.SendDirect(auth); err != nil { return nil, err } if msg, err := conn.RecvDirect(); err != nil { return nil, err } else if auth, ok := msg.Head.Meta.(*authPacket); !ok || auth.Id != id { return nil, errors.New("protocol violation") } conn.Start(config.IrisTunnelBuffer) // Return the initialized link return conn, nil }
// Executes the server side authentication and returns either the agreed secret // session key or the a failure reason. func (l *Listener) serverAuth(strm *stream.Stream, req *authRequest) ([]byte, error) { // Create a new STS session stsSess, err := sts.New(rand.Reader, config.StsGroup, config.StsGenerator, config.StsCipher, config.StsCipherBits, config.StsSigHash) if err != nil { return nil, fmt.Errorf("failed to create STS session: %v", err) } // Accept the incoming key exchange request and send back own exp + auth token exp, token, err := stsSess.Accept(rand.Reader, l.key, req.Exp) if err != nil { return nil, fmt.Errorf("failed to accept incoming exchange: %v", err) } if err = strm.Send(authChallenge{exp, token}); err != nil { return nil, fmt.Errorf("failed to encode auth challenge: %v", err) } if err = strm.Flush(); err != nil { return nil, fmt.Errorf("failed to flush auth challenge: %v", err) } // Receive the foreign auth token and if verifies conclude session resp := new(authResponse) if err = strm.Recv(resp); err != nil { return nil, fmt.Errorf("failed to decode auth response: %v", err) } if err = stsSess.Finalize(&l.key.PublicKey, resp.Token); err != nil { return nil, fmt.Errorf("failed to finalize exchange: %v", err) } return stsSess.Secret() }
// Initializes a stream into an encrypted tunnel link. func (o *Overlay) initServerTunnel(strm *stream.Stream) error { // Set a socket deadline for finishing the handshake strm.Sock().SetDeadline(time.Now().Add(config.IrisTunnelInitTimeout)) defer strm.Sock().SetDeadline(time.Time{}) // Fetch the unencrypted client initiator init := new(initPacket) if err := strm.Recv(init); err != nil { return err } o.lock.RLock() c, ok := o.conns[init.ConnId] o.lock.RUnlock() if !ok { return errors.New("connection not found") } c.tunLock.RLock() tun, ok := c.tunLive[init.TunId] c.tunLock.RUnlock() if !ok { return errors.New("tunnel not found") } // Create the encrypted link hasher := func() hash.Hash { return config.HkdfHash.New() } hkdf := hkdf.New(hasher, tun.secret, config.HkdfSalt, config.HkdfInfo) conn := link.New(strm, hkdf, true) // Send and retrieve an authorization to verify both directions auth := &proto.Message{ Head: proto.Header{ Meta: &authPacket{Id: tun.id}, }, } if err := conn.SendDirect(auth); err != nil { return err } if msg, err := conn.RecvDirect(); err != nil { return err } else if auth, ok := msg.Head.Meta.(*authPacket); !ok || auth.Id != tun.id { return errors.New("protocol violation") } conn.Start(config.IrisTunnelBuffer) // Send back the initialized link to the pending tunnel select { case tun.initDone <- conn: // Connection handled by initiator return nil case <-tun.initStop: // Initiator timed out or terminated, close conn.Close() return nil // No error, since tunnel was handled, albeit not as expected } }
// Client side of the STS session negotiation. func clientAuth(strm *stream.Stream, key *rsa.PrivateKey) ([]byte, error) { // Set an overall time limit for the handshake to complete strm.Sock().SetDeadline(time.Now().Add(config.SessionShakeTimeout)) defer strm.Sock().SetDeadline(time.Time{}) // Create a new empty session stsSess, err := sts.New(rand.Reader, config.StsGroup, config.StsGenerator, config.StsCipher, config.StsCipherBits, config.StsSigHash) if err != nil { return nil, fmt.Errorf("failed to create new session: %v", err) } // Initiate a key exchange, send the exponential exp, err := stsSess.Initiate() if err != nil { return nil, fmt.Errorf("failed to initiate key exchange: %v", err) } req := &initRequest{ Auth: &authRequest{exp}, } if err = strm.Send(req); err != nil { return nil, fmt.Errorf("failed to send auth request: %v", err) } if err = strm.Flush(); err != nil { return nil, fmt.Errorf("failed to flush auth request: %v", err) } // Receive the foreign exponential and auth token and if verifies, send own auth chall := new(authChallenge) if err = strm.Recv(chall); err != nil { return nil, fmt.Errorf("failed to receive auth challenge: %v", err) } token, err := stsSess.Verify(rand.Reader, key, &key.PublicKey, chall.Exp, chall.Token) if err != nil { return nil, fmt.Errorf("failed to verify acceptor auth token: %v", err) } if err = strm.Send(authResponse{token}); err != nil { return nil, fmt.Errorf("failed to send auth response: %v", err) } if err = strm.Flush(); err != nil { return nil, fmt.Errorf("failed to flush auth response: %v", err) } return stsSess.Secret() }
// Server side of the STS session negotiation. func (l *Listener) serverHandle(strm *stream.Stream, timeout time.Duration) { // Make sure the authentication is synced with the sink defer l.pendWait.Done() // Set an overall time limit for the handshake to complete strm.Sock().SetDeadline(time.Now().Add(config.SessionShakeTimeout)) defer strm.Sock().SetDeadline(time.Time{}) // Fetch the session request and multiplex on the contents req := new(initRequest) if err := strm.Recv(req); err != nil { log.Printf("session: failed to retrieve initiation request: %v", err) if err = strm.Close(); err != nil { log.Printf("session: failed to close uninitialized stream: %v.", err) } return } switch { case req.Auth != nil: // Authenticate and clean up if unsuccessful secret, err := l.serverAuth(strm, req.Auth) if err != nil { log.Printf("session: failed to authenticate remote stream: %v.", err) if err = strm.Close(); err != nil { log.Printf("session: failed to close unauthenticated stream: %v.", err) } return } // Create the session and link a data channel to it sess := newSession(strm, secret, true) if err = l.serverLink(sess); err != nil { log.Printf("session: failed to retrieve data link: %v.", err) if err = strm.Close(); err != nil { log.Printf("session: failed to close unlinked stream: %v.", err) } return } // Session setup complete, send upstream select { case l.Sink <- sess: // Ok case <-time.After(timeout): log.Printf("session: established session not handled in %v, dropping.", timeout) if err = sess.Close(); err != nil { log.Printf("session: failed to close established session: %v.", err) } } case req.Link != nil: // Extract the temporary session id and link this stream to it l.pendLock.Lock() res, ok := l.pends[req.Link.Id] l.pendLock.Unlock() if ok { select { case res <- strm: // Ok, link succeeded default: log.Printf("session: established data stream not handled.") if err := strm.Close(); err != nil { log.Printf("session: failed to close established data stream: %v.", err) } } } } }