blob: 11b67aa55648dc79ba297e55b6a0921f1fc4996f [file] [log] [blame]
// Copyright 2016 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 grpc
// TODO(krakauer): better name
import (
_ "bufio"
_ "bytes"
"crypto/rand"
_ "encoding/binary"
_ "errors"
"fmt"
_ "io"
"log"
"net"
"time"
"github.com/golang/protobuf/proto"
"golang.org/x/crypto/nacl/box"
"google.golang.org/grpc/credentials"
"v.io/x/ref/runtime/internal/flow/conn/grpc/handshake"
"v.io/x/ref/runtime/internal/rpc/version"
)
const (
// Just for the sake of stubbing. When succeed is true, we pass back the same net.Conn we got in.
// It is not encrypted or authenticated.
succeed = false
defaultMtu = 1 << 16
)
// This package provides a set of methods that can be exposed to non-Vanadium RPC systems (e.g.
// gRPC) so that they can utilize Vanadium's authentication (i.e. mutual authentication) and
// authorization (i.e. blessings) capabilities.
//
// This package is explicitly aimed at GRPC for now. See package
// "google.golang.org/grpc/credentials" for more information.
// TODO(krakauer):
// - We'll ignoring blessings at first. Once mutual auth works without it, we can add blessing support.
// - We'll ignore caveats and discharges at first.
// - Use timeouts. See net package docs.
// - When is it my responsibility to close a net.Conn?
type GRPCInfo struct{}
func (GRPCInfo) AuthType() string {
return "vanadium"
}
// 1 - Send client's public key
// 2 - Get server's public key
// 3 - Compute shared key
// 4 - Compute channel bindings
// If everything checks out at this point, consider the the connection authenticated and return a
// net.Conn that encrypts messages as they are written.
//
// Later:
// 5 - Blessing support
// 6 - Caveat and discharge support.
func ClientHandshake(addr string, rawConn net.Conn, timeout time.Duration) (net.Conn, credentials.AuthInfo, error) {
log.Printf("In ClientHandshake.\n")
return setupSharedSecrets(rawConn, true)
}
// 1 - Send the server's public key
// 2 - Get the client's public key
// 3 - Compute shared key
// 4 - Compute channel bindings
// If everything checks out at this point, consider the the connection authenticated and return a
// net.Conn that encrypts messages as they are written.
//
// Later:
// 5 - Blessing support
// 6 - Caveat and discharge support
func ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
log.Printf("In ServerHandshake.\n")
return setupSharedSecrets(rawConn, true)
}
// Generates the shared key and channel bindings for both the server and client handshakes.
func setupSharedSecrets(rawConn net.Conn, doprint bool) (net.Conn, credentials.AuthInfo, error) {
pk, sk, err := box.GenerateKey(rand.Reader)
setupReq := &handshake.Setup{
// Versions are typedefed uint32s.
MinVersion: uint32(version.Supported.Min),
MaxVersion: uint32(version.Supported.Max),
PeerNaclPublicKey: pk[:],
Mtu: defaultMtu,
}
if doprint {
log.Printf("setupReq: %v", setupReq)
log.Printf("pk: %v", pk)
}
// Now we have to send this message to the server... do we just send it over rawConn while using box?
// if proto.Size(setupReq) < box.Overhead {
// return nil, nil, errors.New("Message too short") // TODO: what's this check for? It's in box_cipher.go.
// }
// There's nothing to seal with with 1st time. We just send our public key as plaintext.
// First we have to serialize the setup message.
setupReqData, err := proto.Marshal(setupReq)
rawConn.Write(setupReqData)
// Wait until we have our entire reponse.
log.Printf("Going to wait on Copy.\n")
// resBuf := bytes.Buffer{}
// io.Copy(&resBuf, rawConn)
resBuf := make([]byte, 4096) // TODO better (dynamic) size or way of reading?
bytesRead, err := rawConn.Read(resBuf)
if err != nil {
return nil, nil, err
}
log.Printf("Done waiting on Copy.\n")
log.Printf("Received %d bytes.\n", bytesRead)
setupRes := &handshake.Setup{}
// err = proto.Unmarshal(resBuf.Bytes(), setupRes)
err = proto.Unmarshal(resBuf[:bytesRead], setupRes)
if err != nil {
if doprint {
log.Printf("Failed to unmarshal proto.\n")
log.Printf("resBuf: %v", resBuf)
}
return nil, nil, err
}
// TODO: negotiate stuff with the remote (version, mtu, etc.). For now, we are using identical
// protos, so get this working after.
var sharedKey [32]byte
// var peerPublicKey = make([]byte, 32) // TODO does this need both?
// binary.LittleEndian.PutUint32(peerPublicKey, setupRes.PeerNaclPublicKey)
// var peerPublicKeyArr [32]byte
// copy(peerPublicKeyArr[:], peerPublicKey)
var peerPublicKeyArr [32]byte
copy(peerPublicKeyArr[:], setupRes.PeerNaclPublicKey)
box.Precompute(&sharedKey, &peerPublicKeyArr, sk)
if doprint {
log.Printf("setupRes: %v", setupRes)
// log.Printf("peerPublicKeyArr: %v", peerPublicKeyArr)
log.Printf("sharedKey: %v\n", sharedKey)
}
// TODO: maybe this should use a constructor. Also, we can just read the keys directly into this.
// Declare this at the beginning.
log.Printf("rawConn is of type: %T", rawConn)
secureConn := &conn{
rawConn: rawConn,
publicKey: pk,
secretKey: sk,
sharedKey: &sharedKey,
// binding: /* TODO */,
}
log.Printf("secureConn: %#v\n", secureConn)
return secureConn, GRPCInfo{}, nil
}
func SecurityProtocol() string {
return "vanadium"
}
// Returns the latest version supported.
func SecurityVersion() string {
return fmt.Sprint(version.Supported.Max)
}