blob: b8d8bfad9f773a973e964f932d19c7ba35b07eb7 [file] [log] [blame]
package vc
import (
"bytes"
"errors"
"fmt"
"io"
"v.io/core/veyron/runtimes/google/ipc/stream/crypto"
"v.io/core/veyron/runtimes/google/lib/iobuf"
"v.io/v23/context"
"v.io/v23/ipc/version"
"v.io/v23/security"
"v.io/v23/vom"
)
var (
authServerContextTag = []byte("VCauthS\x00")
authClientContextTag = []byte("VCauthC\x00")
)
var (
errSameChannelPublicKey = errors.New("same public keys for both ends of the channel")
errChannelIDMismatch = errors.New("channel id does not match expectation")
errChecksumMismatch = errors.New("checksum mismatch")
errInvalidSignatureInMessage = errors.New("signature does not verify in authentication handshake message")
errNoCertificatesReceived = errors.New("no certificates received")
errSingleCertificateRequired = errors.New("exactly one X.509 certificate chain with exactly one certificate is required")
)
// AuthenticateAsServer executes the authentication protocol at the server and
// returns the blessings used to authenticate the client.
func AuthenticateAsServer(conn io.ReadWriteCloser, principal security.Principal, server security.Blessings, dc DischargeClient, crypter crypto.Crypter, v version.IPCVersion) (client security.Blessings, clientDischarges map[string]security.Discharge, err error) {
defer conn.Close()
if server.IsZero() {
return security.Blessings{}, nil, errors.New("no blessings to present as a server")
}
var discharges []security.Discharge
if tpcavs := server.ThirdPartyCaveats(); len(tpcavs) > 0 && dc != nil {
discharges = dc.PrepareDischarges(nil, tpcavs, security.DischargeImpetus{})
}
if err = writeBlessings(conn, authServerContextTag, crypter, principal, server, discharges, v); err != nil {
return
}
if client, clientDischarges, err = readBlessings(conn, authClientContextTag, crypter, v); err != nil {
return
}
return
}
// AuthenticateAsClient executes the authentication protocol at the client and
// returns the blessings used to authenticate both ends.
//
// The client will only share its identity if its blessing store has one marked
// for the server (who shares its blessings first).
//
// TODO(ashankar): Seems like there is no way the blessing store
// can say that it does NOT want to share the default blessing with the server?
func AuthenticateAsClient(ctx *context.T, conn io.ReadWriteCloser, principal security.Principal, dc DischargeClient, crypter crypto.Crypter, v version.IPCVersion) (server, client security.Blessings, serverDischarges map[string]security.Discharge, err error) {
defer conn.Close()
if server, serverDischarges, err = readBlessings(conn, authServerContextTag, crypter, v); err != nil {
return
}
serverB, invalidB := server.ForContext(security.NewContext(&security.ContextParams{
LocalPrincipal: principal,
RemoteBlessings: server,
RemoteDischarges: serverDischarges,
// TODO(ashankar): Get the local and remote endpoint here?
// There is also a bootstrapping problem here. For example, let's say
// (1) server has the blessing "provider/server" with a PeerIdentity caveat of "provider/client"
// (2) Client has a blessing "provider/client" tagged for "provider/server" in its BlessingStore
// How do we get that working?
// One option is to have a UnionOfBlessings of all blessings of the client in the BlessingStore
// made available to serverAuthContext.LocalBlessings for this call.
Context: ctx,
}))
client = principal.BlessingStore().ForPeer(serverB...)
if client.IsZero() {
err = NewErrNoBlessingsForPeer(ctx, serverB, invalidB)
return
}
var discharges []security.Discharge
if dc != nil {
discharges = dc.PrepareDischarges(ctx, client.ThirdPartyCaveats(), security.DischargeImpetus{})
}
if err = writeBlessings(conn, authClientContextTag, crypter, principal, client, discharges, v); err != nil {
return
}
return
}
func writeBlessings(w io.Writer, tag []byte, crypter crypto.Crypter, p security.Principal, b security.Blessings, discharges []security.Discharge, v version.IPCVersion) error {
signature, err := p.Sign(append(tag, crypter.ChannelBinding()...))
if err != nil {
return err
}
var buf bytes.Buffer
enc, err := vom.NewEncoder(&buf)
if err != nil {
return err
}
if err := enc.Encode(signature); err != nil {
return err
}
if err := enc.Encode(security.MarshalBlessings(b)); err != nil {
return err
}
if v >= version.IPCVersion7 {
wired := make([]security.WireDischarge, len(discharges))
for i, d := range discharges {
wired[i] = security.MarshalDischarge(d)
}
if err := enc.Encode(wired); err != nil {
return err
}
} else if v >= version.IPCVersion5 {
if err := enc.Encode(discharges); err != nil {
return err
}
}
msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
if err != nil {
return err
}
defer msg.Release()
enc, err = vom.NewEncoder(w)
if err != nil {
return err
}
return enc.Encode(msg.Contents)
}
func readBlessings(r io.Reader, tag []byte, crypter crypto.Crypter, v version.IPCVersion) (security.Blessings, map[string]security.Discharge, error) {
var msg []byte
var noBlessings security.Blessings
dec, err := vom.NewDecoder(r)
if err != nil {
return noBlessings, nil, fmt.Errorf("failed to create new decoder: %v", err)
}
if err := dec.Decode(&msg); err != nil {
return noBlessings, nil, fmt.Errorf("failed to read handshake message: %v", err)
}
buf, err := crypter.Decrypt(iobuf.NewSlice(msg))
if err != nil {
return noBlessings, nil, err
}
defer buf.Release()
dec, err = vom.NewDecoder(bytes.NewReader(buf.Contents))
if err != nil {
return noBlessings, nil, fmt.Errorf("failed to create new decoder: %v", err)
}
var (
wireb security.WireBlessings
sig security.Signature
)
if err = dec.Decode(&sig); err != nil {
return noBlessings, nil, err
}
if err = dec.Decode(&wireb); err != nil {
return noBlessings, nil, err
}
var discharges map[string]security.Discharge
if v >= version.IPCVersion7 {
var wired []security.WireDischarge
if err = dec.Decode(&wired); err != nil {
return noBlessings, nil, err
}
if len(wired) > 0 {
discharges = make(map[string]security.Discharge)
for _, w := range wired {
d := security.NewDischarge(w)
discharges[d.ID()] = d
}
}
} else if v >= version.IPCVersion5 {
var list []security.Discharge
if err = dec.Decode(&list); err != nil {
return noBlessings, nil, err
}
if len(list) > 0 {
discharges = make(map[string]security.Discharge)
for _, d := range list {
discharges[d.ID()] = d
}
}
}
blessings, err := security.NewBlessings(wireb)
if err != nil {
return noBlessings, nil, err
}
if !sig.Verify(blessings.PublicKey(), append(tag, crypter.ChannelBinding()...)) {
return noBlessings, nil, errInvalidSignatureInMessage
}
return blessings, discharges, nil
}