blob: e35916f99abdf246b2e5c3da388a5ea33876139e [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package conn
import (
"crypto/rand"
"reflect"
"sync"
"time"
"golang.org/x/crypto/nacl/box"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/flow"
"v.io/v23/flow/message"
"v.io/v23/naming"
"v.io/v23/rpc/version"
"v.io/v23/security"
"v.io/v23/verror"
"v.io/v23/vom"
iflow "v.io/x/ref/runtime/internal/flow"
)
var (
authDialerTag = []byte("AuthDial\x00")
authAcceptorTag = []byte("AuthAcpt\x00")
)
func (c *Conn) dialHandshake(
ctx *context.T,
versions version.RPCVersionRange,
auth flow.PeerAuthorizer) (names []string, rejected []security.RejectedBlessing, rtt time.Duration, err error) {
binding, remoteEndpoint, rttstart, err := c.setup(ctx, versions, true)
if err != nil {
return nil, nil, 0, err
}
dialedEP := c.remote
c.remote.RoutingID = remoteEndpoint.RoutingID
bflow := c.newFlowLocked(
ctx,
blessingsFlowID,
security.Blessings{},
security.Blessings{},
nil,
nil,
naming.Endpoint{},
true,
true,
0,
true)
bflow.releaseLocked(DefaultBytesBufferedPerFlow)
c.blessingsFlow = newBlessingsFlow(ctx, bflow)
rBlessings, rDischarges, rttend, err := c.readRemoteAuth(ctx, binding, true)
if err != nil {
return nil, nil, 0, err
}
rtt = rttend.Sub(rttstart)
if rBlessings.IsZero() {
err = NewErrAcceptorBlessingsMissing(ctx)
return nil, nil, rtt, err
}
if c.MatchesRID(dialedEP) {
// If we hadn't reached the endpoint we expected we would have treated this connection as
// a proxy, and proxies aren't authorized. In this case we didn't find a proxy, so go ahead
// and authorize the connection.
names, rejected, err = auth.AuthorizePeer(ctx, c.local, c.remote, rBlessings, rDischarges)
if err != nil {
return names, rejected, rtt, iflow.MaybeWrapError(verror.ErrNotTrusted, ctx, err)
}
}
signedBinding, err := v23.GetPrincipal(ctx).Sign(append(authDialerTag, binding...))
if err != nil {
return names, rejected, rtt, err
}
lAuth := &message.Auth{ChannelBinding: signedBinding}
// The client sends its blessings without any blessing-pattern encryption to the
// server as it has already authorized the server. Thus the 'peers' argument to
// blessingsFlow.send is nil.
if lAuth.BlessingsKey, _, err = c.blessingsFlow.send(ctx, c.localBlessings, nil, nil); err != nil {
return names, rejected, rtt, err
}
c.mu.Lock()
err = c.sendMessageLocked(ctx, true, expressPriority, lAuth)
c.mu.Unlock()
return names, rejected, rtt, err
}
// MatchesRID returns true if the given endpoint matches the routing
// ID of the remote server. Also returns true if the given ep has
// the null routing id (in which case it is assumed that any connected
// server must be the target since nothing has been specified).
func (c *Conn) MatchesRID(ep naming.Endpoint) bool {
return ep.RoutingID == naming.NullRoutingID ||
c.remote.RoutingID == ep.RoutingID
}
func (c *Conn) acceptHandshake(
ctx *context.T,
versions version.RPCVersionRange,
authorizedPeers []security.BlessingPattern) (rtt time.Duration, err error) {
binding, remoteEndpoint, _, err := c.setup(ctx, versions, false)
if err != nil {
return rtt, err
}
c.remote = remoteEndpoint
bflw := c.newFlowLocked(
ctx,
blessingsFlowID,
security.Blessings{},
security.Blessings{},
nil,
nil,
naming.Endpoint{},
true,
true,
0,
true)
c.blessingsFlow = newBlessingsFlow(ctx, bflw)
signedBinding, err := v23.GetPrincipal(ctx).Sign(append(authAcceptorTag, binding...))
if err != nil {
return rtt, err
}
lAuth := &message.Auth{
ChannelBinding: signedBinding,
}
lAuth.BlessingsKey, lAuth.DischargeKey, err = c.blessingsFlow.send(
ctx, c.localBlessings, c.localDischarges, authorizedPeers)
if err != nil {
return rtt, err
}
c.mu.Lock()
rttstart := time.Now()
err = c.sendMessageLocked(ctx, true, expressPriority, lAuth)
c.mu.Unlock()
if err != nil {
return rtt, err
}
_, _, rttend, err := c.readRemoteAuth(ctx, binding, false)
rtt = rttend.Sub(rttstart)
return rtt, err
}
func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange, dialer bool) ([]byte, naming.Endpoint, time.Time, error) {
var rttstart time.Time
pk, sk, err := box.GenerateKey(rand.Reader)
if err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
lSetup := &message.Setup{
Versions: versions,
PeerLocalEndpoint: c.local,
PeerNaClPublicKey: pk,
Mtu: defaultMtu,
SharedTokens: DefaultBytesBufferedPerFlow,
}
if !c.remote.IsZero() {
lSetup.PeerRemoteEndpoint = c.remote
}
ch := make(chan error, 1)
go func() {
c.mu.Lock()
rttstart = time.Now()
ch <- c.sendMessageLocked(ctx, true, expressPriority, lSetup)
c.mu.Unlock()
}()
msg, err := c.mp.readMsg(ctx)
if err != nil {
<-ch
return nil, naming.Endpoint{}, rttstart, NewErrRecv(ctx, "unknown", err)
}
rSetup, valid := msg.(*message.Setup)
if !valid {
<-ch
return nil, naming.Endpoint{}, rttstart, NewErrUnexpectedMsg(ctx, reflect.TypeOf(msg).String())
}
if err := <-ch; err != nil {
remoteStr := ""
if !c.remote.IsZero() {
remoteStr = c.remote.String()
}
return nil, naming.Endpoint{}, rttstart, NewErrSend(ctx, "setup", remoteStr, err)
}
if c.version, err = version.CommonVersion(ctx, lSetup.Versions, rSetup.Versions); err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
if c.local.IsZero() {
c.local = rSetup.PeerRemoteEndpoint
}
if rSetup.Mtu != 0 {
c.mtu = rSetup.Mtu
} else {
c.mtu = defaultMtu
}
c.lshared = lSetup.SharedTokens
if rSetup.SharedTokens != 0 && rSetup.SharedTokens < c.lshared {
c.lshared = rSetup.SharedTokens
}
if rSetup.PeerNaClPublicKey == nil {
return nil, naming.Endpoint{}, rttstart, NewErrMissingSetupOption(ctx, "peerNaClPublicKey")
}
c.mu.Lock()
binding := c.mp.setupEncryption(ctx, pk, sk, rSetup.PeerNaClPublicKey)
c.mu.Unlock()
if c.version >= version.RPCVersion14 {
// We include the setup messages in the channel binding to prevent attacks
// where a man in the middle changes fields in the Setup message (e.g. a
// downgrade attack wherein a MITM attacker changes the Version field of
// the Setup message to a lower-security version.)
// We always put the dialer first in the binding.
if dialer {
if binding, err = message.Append(ctx, lSetup, nil); err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
if binding, err = message.Append(ctx, rSetup, binding); err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
} else {
if binding, err = message.Append(ctx, rSetup, nil); err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
if binding, err = message.Append(ctx, lSetup, binding); err != nil {
return nil, naming.Endpoint{}, rttstart, err
}
}
}
// if we're encapsulated in another flow, tell that flow to stop
// encrypting now that we've started.
if f, ok := c.mp.rw.(*flw); ok {
f.disableEncryption()
}
return binding, rSetup.PeerLocalEndpoint, rttstart, nil
}
func (c *Conn) readRemoteAuth(ctx *context.T, binding []byte, dialer bool) (security.Blessings, map[string]security.Discharge, time.Time, error) {
tag := authDialerTag
if dialer {
tag = authAcceptorTag
}
var (
rauth *message.Auth
err error
rttend time.Time
rBlessings security.Blessings
rDischarges map[string]security.Discharge
)
for {
msg, err := c.mp.readMsg(ctx)
if err != nil {
remote := ""
if !c.remote.IsZero() {
remote = c.remote.String()
}
return security.Blessings{}, nil, rttend, NewErrRecv(ctx, remote, err)
}
if rauth, _ = msg.(*message.Auth); rauth != nil {
rttend = time.Now()
break
}
if err = c.handleMessage(ctx, msg); err != nil {
return security.Blessings{}, nil, rttend, err
}
}
// Only read the blessings if we were the dialer. Any blessings from the dialer
// will be sent later.
if rauth.BlessingsKey != 0 {
rBlessings, rDischarges, err = c.blessingsFlow.getRemote(
ctx, rauth.BlessingsKey, rauth.DischargeKey)
if err != nil {
return security.Blessings{}, nil, rttend, err
}
c.mu.Lock()
c.rPublicKey = rBlessings.PublicKey()
c.remoteBlessings = rBlessings
c.remoteDischarges = rDischarges
c.remoteValid = make(chan struct{})
c.mu.Unlock()
}
if c.rPublicKey == nil {
return security.Blessings{}, nil, rttend, NewErrNoPublicKey(ctx)
}
if !rauth.ChannelBinding.Verify(c.rPublicKey, append(tag, binding...)) {
return security.Blessings{}, nil, rttend, NewErrInvalidChannelBinding(ctx)
}
return rBlessings, rDischarges, rttend, nil
}
type blessingsFlow struct {
enc *vom.Encoder
dec *vom.Decoder
f *flw
mu sync.Mutex
nextKey uint64
incoming *inCache
outgoing *outCache
}
// inCache keeps track of incoming blessings, discharges, and keys.
type inCache struct {
dkeys map[uint64]uint64 // bkey -> dkey of the latest discharges.
blessings map[uint64]security.Blessings // keyed by bkey
discharges map[uint64][]security.Discharge // keyed by dkey
}
// outCache keeps track of outgoing blessings, discharges, and keys.
type outCache struct {
bkeys map[string]uint64 // blessings uid -> bkey
dkeys map[uint64]uint64 // blessings bkey -> dkey of latest discharges
blessings map[uint64]security.Blessings // keyed by bkey
discharges map[uint64][]security.Discharge // keyed by dkey
}
func newBlessingsFlow(ctx *context.T, f *flw) *blessingsFlow {
b := &blessingsFlow{
f: f,
enc: vom.NewEncoder(f),
dec: vom.NewDecoder(f),
nextKey: 1,
incoming: &inCache{
blessings: make(map[uint64]security.Blessings),
dkeys: make(map[uint64]uint64),
discharges: make(map[uint64][]security.Discharge),
},
outgoing: &outCache{
bkeys: make(map[string]uint64),
dkeys: make(map[uint64]uint64),
blessings: make(map[uint64]security.Blessings),
discharges: make(map[uint64][]security.Discharge),
},
}
return b
}
func (b *blessingsFlow) receiveBlessingsLocked(ctx *context.T, bkey uint64, blessings security.Blessings) error {
// When accepting, make sure the blessings received are bound to the conn's
// remote public key.
b.f.conn.mu.Lock()
if pk := b.f.conn.rPublicKey; pk != nil && !reflect.DeepEqual(blessings.PublicKey(), pk) {
b.f.conn.mu.Unlock()
return NewErrBlessingsNotBound(ctx)
}
b.f.conn.mu.Unlock()
b.incoming.blessings[bkey] = blessings
return nil
}
func (b *blessingsFlow) receiveDischargesLocked(ctx *context.T, bkey, dkey uint64, discharges []security.Discharge) {
b.incoming.discharges[dkey] = discharges
b.incoming.dkeys[bkey] = dkey
}
func (b *blessingsFlow) receiveLocked(ctx *context.T, bd BlessingsFlowMessage) error {
switch bd := bd.(type) {
case BlessingsFlowMessageBlessings:
bkey, blessings := bd.Value.BKey, bd.Value.Blessings
if err := b.receiveBlessingsLocked(ctx, bkey, blessings); err != nil {
return err
}
case BlessingsFlowMessageEncryptedBlessings:
bkey, ciphertexts := bd.Value.BKey, bd.Value.Ciphertexts
var blessings security.Blessings
if err := decrypt(ctx, ciphertexts, &blessings); err != nil {
// TODO(ataly): This error should not be returned if the
// client has explicitly set the peer authorizer to nil.
// In that case, the client does not care whether the server's
// blessings can be decrypted or not. Ideally we should just
// pass this error to the peer authorizer and handle it there.
return iflow.MaybeWrapError(verror.ErrNotTrusted, ctx, NewErrCannotDecryptBlessings(ctx, err))
}
if err := b.receiveBlessingsLocked(ctx, bkey, blessings); err != nil {
return err
}
case BlessingsFlowMessageDischarges:
bkey, dkey, discharges := bd.Value.BKey, bd.Value.DKey, bd.Value.Discharges
b.receiveDischargesLocked(ctx, bkey, dkey, discharges)
case BlessingsFlowMessageEncryptedDischarges:
bkey, dkey, ciphertexts := bd.Value.BKey, bd.Value.DKey, bd.Value.Ciphertexts
var discharges []security.Discharge
if err := decrypt(ctx, ciphertexts, &discharges); err != nil {
return iflow.MaybeWrapError(verror.ErrNotTrusted, ctx, NewErrCannotDecryptDischarges(ctx, err))
}
b.receiveDischargesLocked(ctx, bkey, dkey, discharges)
}
return nil
}
// getRemote gets the remote blessings and discharges associated with the given
// bkey and dkey. We will read messages from the wire until we receive the
// looked for blessings. This method is normally called from the read loop of
// the conn, so all the packets for the desired blessing must have been received
// and buffered before calling this function. This property is guaranteed since
// we always send blessings and discharges before sending their bkey/dkey
// references in a control message.
func (b *blessingsFlow) getRemote(ctx *context.T, bkey, dkey uint64) (security.Blessings, map[string]security.Discharge, error) {
defer b.mu.Unlock()
b.mu.Lock()
for {
blessings, hasB := b.incoming.blessings[bkey]
if hasB {
if dkey == 0 {
return blessings, nil, nil
}
discharges, hasD := b.incoming.discharges[dkey]
if hasD {
return blessings, dischargeMap(discharges), nil
}
}
var received BlessingsFlowMessage
err := b.dec.Decode(&received)
if err != nil {
return security.Blessings{}, nil, err
}
if err := b.receiveLocked(ctx, received); err != nil {
b.f.conn.internalClose(ctx, false, err)
return security.Blessings{}, nil, err
}
}
}
func (b *blessingsFlow) encodeBlessingsLocked(ctx *context.T, blessings security.Blessings, bkey uint64, peers []security.BlessingPattern) error {
if len(peers) == 0 {
// blessings can be encoded in plaintext
return b.enc.Encode(BlessingsFlowMessageBlessings{Blessings{
BKey: bkey,
Blessings: blessings,
}})
}
ciphertexts, err := encrypt(ctx, peers, blessings)
if err != nil {
return NewErrCannotEncryptBlessings(ctx, peers, err)
}
return b.enc.Encode(BlessingsFlowMessageEncryptedBlessings{EncryptedBlessings{
BKey: bkey,
Ciphertexts: ciphertexts,
}})
}
func (b *blessingsFlow) encodeDischargesLocked(ctx *context.T, discharges []security.Discharge, bkey, dkey uint64, peers []security.BlessingPattern) error {
if len(peers) == 0 {
// discharges can be encoded in plaintext
return b.enc.Encode(BlessingsFlowMessageDischarges{Discharges{
Discharges: discharges,
DKey: dkey,
BKey: bkey,
}})
}
ciphertexts, err := encrypt(ctx, peers, discharges)
if err != nil {
return NewErrCannotEncryptDischarges(ctx, peers, err)
}
return b.enc.Encode(BlessingsFlowMessageEncryptedDischarges{EncryptedDischarges{
DKey: dkey,
BKey: bkey,
Ciphertexts: ciphertexts,
}})
}
func (b *blessingsFlow) send(
ctx *context.T,
blessings security.Blessings,
discharges map[string]security.Discharge,
peers []security.BlessingPattern) (bkey, dkey uint64, err error) {
if blessings.IsZero() {
return 0, 0, nil
}
defer b.mu.Unlock()
b.mu.Lock()
buid := string(blessings.UniqueID())
bkey, hasB := b.outgoing.bkeys[buid]
if !hasB {
bkey = b.nextKey
b.nextKey++
b.outgoing.bkeys[buid] = bkey
b.outgoing.blessings[bkey] = blessings
if err := b.encodeBlessingsLocked(ctx, blessings, bkey, peers); err != nil {
return 0, 0, err
}
}
if len(discharges) == 0 {
return bkey, 0, nil
}
dkey, hasD := b.outgoing.dkeys[bkey]
if hasD && equalDischarges(discharges, b.outgoing.discharges[dkey]) {
return bkey, dkey, nil
}
dlist := dischargeList(discharges)
dkey = b.nextKey
b.nextKey++
b.outgoing.dkeys[bkey] = dkey
b.outgoing.discharges[dkey] = dlist
return bkey, dkey, b.encodeDischargesLocked(ctx, dlist, bkey, dkey, peers)
}
func (b *blessingsFlow) close(ctx *context.T, err error) {
b.f.close(ctx, false, err)
}
func dischargeList(in map[string]security.Discharge) []security.Discharge {
out := make([]security.Discharge, 0, len(in))
for _, d := range in {
out = append(out, d)
}
return out
}
func dischargeMap(in []security.Discharge) map[string]security.Discharge {
out := make(map[string]security.Discharge, len(in))
for _, d := range in {
out[d.ID()] = d
}
return out
}
func equalDischarges(m map[string]security.Discharge, s []security.Discharge) bool {
if len(m) != len(s) {
return false
}
for _, d := range s {
inm, ok := m[d.ID()]
if !ok || !d.Equivalent(inm) {
return false
}
}
return true
}