| package vif |
| |
| import ( |
| "crypto/rand" |
| "errors" |
| "fmt" |
| "io" |
| "net" |
| |
| "golang.org/x/crypto/nacl/box" |
| |
| "v.io/core/veyron/runtimes/google/ipc/stream/crypto" |
| "v.io/core/veyron/runtimes/google/ipc/stream/message" |
| "v.io/core/veyron/runtimes/google/ipc/stream/vc" |
| "v.io/core/veyron/runtimes/google/ipc/version" |
| "v.io/core/veyron/runtimes/google/lib/iobuf" |
| "v.io/core/veyron2/context" |
| "v.io/core/veyron2/ipc/stream" |
| ipcversion "v.io/core/veyron2/ipc/version" |
| "v.io/core/veyron2/options" |
| "v.io/core/veyron2/security" |
| ) |
| |
| var ( |
| errUnsupportedEncryptVersion = errors.New("unsupported encryption version") |
| errVersionNegotiationFailed = errors.New("encryption version negotiation failed") |
| nullCipher crypto.NullControlCipher |
| ) |
| |
| // privateData includes secret data we need for encryption. |
| type privateData struct { |
| naclBoxPrivateKey [32]byte |
| } |
| |
| // AuthenticateAsClient sends a HopSetup message if possible. If so, it chooses |
| // encryption based on the max supported version. |
| // |
| // The sequence is initiated by the client. |
| // |
| // - If the versions include IPCVersion6 or greater, the client sends a |
| // HopSetup message to the server, containing the client's supported |
| // versions, and the client's crypto options. The HopSetup message |
| // is sent in the clear. |
| // |
| // - When the server receives the HopSetup message, it calls |
| // AuthenticateAsServer, which constructs a response HopSetup containing |
| // the server's version range, and any crypto options. |
| // |
| // - For IPCVersion6, the client and server generate fresh public/private key |
| // pairs, sending the public key to the peer as a crypto option. The |
| // remainder of the communication is encrypted as HopSetupStream messages |
| // using NewControlCipherIPC6, which is based on |
| // code.google.com/p/go.crypto/nacl/box. |
| // |
| // - Once the encrypted HopSetupStream channel is setup, the client and |
| // server authenticate using the vc.AuthenticateAs{Client,Server} protocol. |
| // |
| // Note that the HopSetup messages are sent in the clear, so they are subject to |
| // modification by a man-in-the-middle, which can currently force a downgrade by |
| // modifying the acceptable version ranges downward. This can be addressed by |
| // including a hash of the HopSetup message in the encrypted stream. It is |
| // likely that this will be addressed in subsequent protocol versions (or it may |
| // not be addressed at all if IPCVersion6 becomes the only supported version). |
| func AuthenticateAsClient(ctx context.T, conn net.Conn, versions *version.Range, principal security.Principal, dc vc.DischargeClient) (crypto.ControlCipher, error) { |
| if versions == nil { |
| versions = version.SupportedRange |
| } |
| if principal == nil { |
| // If there is no principal, we do not support encryption/authentication. |
| var err error |
| versions, err = versions.Intersect(&version.Range{Min: 0, Max: ipcversion.IPCVersion5}) |
| if err != nil { |
| return nil, err |
| } |
| } |
| if versions.Max < ipcversion.IPCVersion6 { |
| return nullCipher, nil |
| } |
| |
| // The client has not yet sent its public data. Construct it and send it. |
| pvt, pub, err := makeHopSetup(versions) |
| if err != nil { |
| return nil, err |
| } |
| if err := message.WriteTo(conn, &pub, nullCipher); err != nil { |
| return nil, err |
| } |
| |
| // Read the server's public data. |
| pool := iobuf.NewPool(0) |
| reader := iobuf.NewReader(pool, conn) |
| pmsg, err := message.ReadFrom(reader, nullCipher) |
| if err != nil { |
| return nil, err |
| } |
| ppub, ok := pmsg.(*message.HopSetup) |
| if !ok { |
| return nil, errVersionNegotiationFailed |
| } |
| |
| // Choose the max version in the intersection. |
| vrange, err := pub.Versions.Intersect(&ppub.Versions) |
| if err != nil { |
| return nil, err |
| } |
| v := vrange.Max |
| if v < ipcversion.IPCVersion6 { |
| return nullCipher, nil |
| } |
| |
| // Perform the authentication. |
| switch v { |
| case ipcversion.IPCVersion6: |
| return authenticateAsClientIPC6(ctx, conn, reader, principal, dc, &pvt, &pub, ppub) |
| default: |
| return nil, errUnsupportedEncryptVersion |
| } |
| } |
| |
| func authenticateAsClientIPC6(ctx context.T, writer io.Writer, reader *iobuf.Reader, principal security.Principal, dc vc.DischargeClient, |
| pvt *privateData, pub, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| pbox := ppub.NaclBox() |
| if pbox == nil { |
| return nil, errVersionNegotiationFailed |
| } |
| c := crypto.NewControlCipherIPC6(&pbox.PublicKey, &pvt.naclBoxPrivateKey, false) |
| sconn := newSetupConn(writer, reader, c) |
| // TODO(jyh): act upon the authentication results. |
| _, _, _, err := vc.AuthenticateAsClient(ctx, sconn, principal, dc, crypto.NewNullCrypter(), ipcversion.IPCVersion6) |
| if err != nil { |
| return nil, err |
| } |
| return c, nil |
| } |
| |
| // AuthenticateAsServer handles a HopSetup message, choosing authentication |
| // based on the max common version. |
| // |
| // See AuthenticateAsClient for a description of the negotiation. |
| func AuthenticateAsServer(conn net.Conn, versions *version.Range, principal security.Principal, lBlessings security.Blessings, |
| dc vc.DischargeClient, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| var err error |
| if versions == nil { |
| versions = version.SupportedRange |
| } |
| if principal == nil { |
| // If there is no principal, we don't support encryption/authentication. |
| versions, err = versions.Intersect(&version.Range{Min: 0, Max: ipcversion.IPCVersion5}) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // Create our public data and send it to the client. |
| pvt, pub, err := makeHopSetup(versions) |
| if err != nil { |
| return nil, err |
| } |
| if err := message.WriteTo(conn, &pub, nullCipher); err != nil { |
| return nil, err |
| } |
| |
| // Choose the max version in common. |
| vrange, err := pub.Versions.Intersect(&ppub.Versions) |
| if err != nil { |
| return nil, err |
| } |
| v := vrange.Max |
| if v < ipcversion.IPCVersion6 { |
| return nullCipher, nil |
| } |
| |
| // Perform authentication. |
| switch v { |
| case ipcversion.IPCVersion6: |
| return authenticateAsServerIPC6(conn, principal, lBlessings, dc, &pvt, &pub, ppub) |
| default: |
| return nil, errUnsupportedEncryptVersion |
| } |
| } |
| |
| func authenticateAsServerIPC6(conn net.Conn, principal security.Principal, lBlessings security.Blessings, dc vc.DischargeClient, |
| pvt *privateData, pub, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| box := ppub.NaclBox() |
| if box == nil { |
| return nil, errVersionNegotiationFailed |
| } |
| c := crypto.NewControlCipherIPC6(&box.PublicKey, &pvt.naclBoxPrivateKey, true) |
| pool := iobuf.NewPool(0) |
| reader := iobuf.NewReader(pool, conn) |
| sconn := newSetupConn(conn, reader, c) |
| // TODO(jyh): act upon authentication results. |
| _, _, err := vc.AuthenticateAsServer(sconn, principal, lBlessings, dc, crypto.NewNullCrypter(), ipcversion.IPCVersion6) |
| if err != nil { |
| return nil, err |
| } |
| return c, nil |
| } |
| |
| // serverAuthOptions extracts the Principal from the options list. |
| func serverAuthOptions(lopts []stream.ListenerOpt) (principal security.Principal, lBlessings security.Blessings, dischargeClient vc.DischargeClient, err error) { |
| var securityLevel options.VCSecurityLevel |
| for _, o := range lopts { |
| switch v := o.(type) { |
| case vc.DischargeClient: |
| dischargeClient = v |
| case vc.LocalPrincipal: |
| principal = v.Principal |
| case options.VCSecurityLevel: |
| securityLevel = v |
| case options.ServerBlessings: |
| lBlessings = v.Blessings |
| } |
| } |
| switch securityLevel { |
| case options.VCSecurityConfidential: |
| if principal == nil { |
| principal = vc.AnonymousPrincipal |
| } |
| if lBlessings == nil { |
| lBlessings = principal.BlessingStore().Default() |
| } |
| case options.VCSecurityNone: |
| principal = nil |
| default: |
| err = fmt.Errorf("unrecognized VC security level: %v", securityLevel) |
| } |
| return |
| } |
| |
| // clientAuthOptions extracts the client authentication options from the options |
| // list. |
| func clientAuthOptions(lopts []stream.VCOpt) (ctx context.T, principal security.Principal, dischargeClient vc.DischargeClient, err error) { |
| var securityLevel options.VCSecurityLevel |
| var noDischarges bool |
| for _, o := range lopts { |
| switch v := o.(type) { |
| case vc.DialContext: |
| ctx = v.T |
| case vc.DischargeClient: |
| dischargeClient = v |
| case vc.LocalPrincipal: |
| principal = v.Principal |
| case options.VCSecurityLevel: |
| securityLevel = v |
| case vc.NoDischarges: |
| noDischarges = true |
| } |
| } |
| if noDischarges { |
| dischargeClient = nil |
| } |
| switch securityLevel { |
| case options.VCSecurityConfidential: |
| if principal == nil { |
| principal = vc.AnonymousPrincipal |
| } |
| case options.VCSecurityNone: |
| principal = nil |
| default: |
| err = fmt.Errorf("unrecognized VC security level: %v", securityLevel) |
| } |
| return |
| } |
| |
| // makeHopSetup constructs the options that this process can support. |
| func makeHopSetup(versions *version.Range) (pvt privateData, pub message.HopSetup, err error) { |
| pub.Versions = *versions |
| var pubKey, pvtKey *[32]byte |
| pubKey, pvtKey, err = box.GenerateKey(rand.Reader) |
| pub.Options = append(pub.Options, &message.NaclBox{PublicKey: *pubKey}) |
| pvt.naclBoxPrivateKey = *pvtKey |
| return |
| } |