Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 1 | package vif |
| 2 | |
| 3 | import ( |
| 4 | "crypto/rand" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "io" |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 8 | |
Cosmos Nicolaou | d412cb2 | 2014-12-15 22:06:32 -0800 | [diff] [blame] | 9 | "golang.org/x/crypto/nacl/box" |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 10 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 11 | "v.io/core/veyron/runtimes/google/ipc/stream/crypto" |
| 12 | "v.io/core/veyron/runtimes/google/ipc/stream/message" |
| 13 | "v.io/core/veyron/runtimes/google/ipc/stream/vc" |
| 14 | "v.io/core/veyron/runtimes/google/ipc/version" |
| 15 | "v.io/core/veyron/runtimes/google/lib/iobuf" |
| 16 | "v.io/core/veyron2/context" |
| 17 | "v.io/core/veyron2/ipc/stream" |
| 18 | ipcversion "v.io/core/veyron2/ipc/version" |
| 19 | "v.io/core/veyron2/options" |
| 20 | "v.io/core/veyron2/security" |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 21 | ) |
| 22 | |
| 23 | var ( |
| 24 | errUnsupportedEncryptVersion = errors.New("unsupported encryption version") |
| 25 | errVersionNegotiationFailed = errors.New("encryption version negotiation failed") |
| 26 | nullCipher crypto.NullControlCipher |
| 27 | ) |
| 28 | |
| 29 | // privateData includes secret data we need for encryption. |
| 30 | type privateData struct { |
| 31 | naclBoxPrivateKey [32]byte |
| 32 | } |
| 33 | |
| 34 | // AuthenticateAsClient sends a HopSetup message if possible. If so, it chooses |
| 35 | // encryption based on the max supported version. |
| 36 | // |
| 37 | // The sequence is initiated by the client. |
| 38 | // |
| 39 | // - If the versions include IPCVersion6 or greater, the client sends a |
| 40 | // HopSetup message to the server, containing the client's supported |
| 41 | // versions, and the client's crypto options. The HopSetup message |
| 42 | // is sent in the clear. |
| 43 | // |
| 44 | // - When the server receives the HopSetup message, it calls |
| 45 | // AuthenticateAsServer, which constructs a response HopSetup containing |
| 46 | // the server's version range, and any crypto options. |
| 47 | // |
| 48 | // - For IPCVersion6, the client and server generate fresh public/private key |
| 49 | // pairs, sending the public key to the peer as a crypto option. The |
| 50 | // remainder of the communication is encrypted as HopSetupStream messages |
| 51 | // using NewControlCipherIPC6, which is based on |
Matt Rosencrantz | 7c88fb1 | 2014-11-18 01:12:45 +0000 | [diff] [blame] | 52 | // code.google.com/p/go.crypto/nacl/box. |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 53 | // |
| 54 | // - Once the encrypted HopSetupStream channel is setup, the client and |
| 55 | // server authenticate using the vc.AuthenticateAs{Client,Server} protocol. |
| 56 | // |
| 57 | // Note that the HopSetup messages are sent in the clear, so they are subject to |
| 58 | // modification by a man-in-the-middle, which can currently force a downgrade by |
| 59 | // modifying the acceptable version ranges downward. This can be addressed by |
| 60 | // including a hash of the HopSetup message in the encrypted stream. It is |
| 61 | // likely that this will be addressed in subsequent protocol versions (or it may |
| 62 | // not be addressed at all if IPCVersion6 becomes the only supported version). |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 63 | func AuthenticateAsClient(ctx *context.T, writer io.Writer, reader *iobuf.Reader, versions *version.Range, principal security.Principal, dc vc.DischargeClient) (crypto.ControlCipher, error) { |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 64 | if versions == nil { |
| 65 | versions = version.SupportedRange |
| 66 | } |
| 67 | if principal == nil { |
| 68 | // If there is no principal, we do not support encryption/authentication. |
| 69 | var err error |
| 70 | versions, err = versions.Intersect(&version.Range{Min: 0, Max: ipcversion.IPCVersion5}) |
| 71 | if err != nil { |
| 72 | return nil, err |
| 73 | } |
| 74 | } |
| 75 | if versions.Max < ipcversion.IPCVersion6 { |
| 76 | return nullCipher, nil |
| 77 | } |
| 78 | |
| 79 | // The client has not yet sent its public data. Construct it and send it. |
| 80 | pvt, pub, err := makeHopSetup(versions) |
| 81 | if err != nil { |
| 82 | return nil, err |
| 83 | } |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 84 | if err := message.WriteTo(writer, &pub, nullCipher); err != nil { |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 85 | return nil, err |
| 86 | } |
| 87 | |
| 88 | // Read the server's public data. |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 89 | pmsg, err := message.ReadFrom(reader, nullCipher) |
| 90 | if err != nil { |
| 91 | return nil, err |
| 92 | } |
| 93 | ppub, ok := pmsg.(*message.HopSetup) |
| 94 | if !ok { |
| 95 | return nil, errVersionNegotiationFailed |
| 96 | } |
| 97 | |
| 98 | // Choose the max version in the intersection. |
| 99 | vrange, err := pub.Versions.Intersect(&ppub.Versions) |
| 100 | if err != nil { |
| 101 | return nil, err |
| 102 | } |
| 103 | v := vrange.Max |
| 104 | if v < ipcversion.IPCVersion6 { |
| 105 | return nullCipher, nil |
| 106 | } |
| 107 | |
| 108 | // Perform the authentication. |
| 109 | switch v { |
| 110 | case ipcversion.IPCVersion6: |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 111 | return authenticateAsClientIPC6(ctx, writer, reader, principal, dc, &pvt, &pub, ppub) |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 112 | default: |
| 113 | return nil, errUnsupportedEncryptVersion |
| 114 | } |
| 115 | } |
| 116 | |
Matt Rosencrantz | 4f8ac60 | 2014-12-29 14:42:48 -0800 | [diff] [blame] | 117 | func authenticateAsClientIPC6(ctx *context.T, writer io.Writer, reader *iobuf.Reader, principal security.Principal, dc vc.DischargeClient, |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 118 | pvt *privateData, pub, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| 119 | pbox := ppub.NaclBox() |
| 120 | if pbox == nil { |
| 121 | return nil, errVersionNegotiationFailed |
| 122 | } |
| 123 | c := crypto.NewControlCipherIPC6(&pbox.PublicKey, &pvt.naclBoxPrivateKey, false) |
| 124 | sconn := newSetupConn(writer, reader, c) |
| 125 | // TODO(jyh): act upon the authentication results. |
Asim Shankar | f4864f4 | 2014-11-25 18:53:05 -0800 | [diff] [blame] | 126 | _, _, _, err := vc.AuthenticateAsClient(ctx, sconn, principal, dc, crypto.NewNullCrypter(), ipcversion.IPCVersion6) |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 127 | if err != nil { |
| 128 | return nil, err |
| 129 | } |
| 130 | return c, nil |
| 131 | } |
| 132 | |
| 133 | // AuthenticateAsServer handles a HopSetup message, choosing authentication |
| 134 | // based on the max common version. |
| 135 | // |
| 136 | // See AuthenticateAsClient for a description of the negotiation. |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 137 | func AuthenticateAsServer(writer io.Writer, reader *iobuf.Reader, versions *version.Range, principal security.Principal, lBlessings security.Blessings, |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 138 | dc vc.DischargeClient, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| 139 | var err error |
| 140 | if versions == nil { |
| 141 | versions = version.SupportedRange |
| 142 | } |
| 143 | if principal == nil { |
| 144 | // If there is no principal, we don't support encryption/authentication. |
| 145 | versions, err = versions.Intersect(&version.Range{Min: 0, Max: ipcversion.IPCVersion5}) |
| 146 | if err != nil { |
| 147 | return nil, err |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // Create our public data and send it to the client. |
| 152 | pvt, pub, err := makeHopSetup(versions) |
| 153 | if err != nil { |
| 154 | return nil, err |
| 155 | } |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 156 | if err := message.WriteTo(writer, &pub, nullCipher); err != nil { |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 157 | return nil, err |
| 158 | } |
| 159 | |
| 160 | // Choose the max version in common. |
| 161 | vrange, err := pub.Versions.Intersect(&ppub.Versions) |
| 162 | if err != nil { |
| 163 | return nil, err |
| 164 | } |
| 165 | v := vrange.Max |
| 166 | if v < ipcversion.IPCVersion6 { |
| 167 | return nullCipher, nil |
| 168 | } |
| 169 | |
| 170 | // Perform authentication. |
| 171 | switch v { |
| 172 | case ipcversion.IPCVersion6: |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 173 | return authenticateAsServerIPC6(writer, reader, principal, lBlessings, dc, &pvt, &pub, ppub) |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 174 | default: |
| 175 | return nil, errUnsupportedEncryptVersion |
| 176 | } |
| 177 | } |
| 178 | |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 179 | func authenticateAsServerIPC6(writer io.Writer, reader *iobuf.Reader, principal security.Principal, lBlessings security.Blessings, dc vc.DischargeClient, |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 180 | pvt *privateData, pub, ppub *message.HopSetup) (crypto.ControlCipher, error) { |
| 181 | box := ppub.NaclBox() |
| 182 | if box == nil { |
| 183 | return nil, errVersionNegotiationFailed |
| 184 | } |
| 185 | c := crypto.NewControlCipherIPC6(&box.PublicKey, &pvt.naclBoxPrivateKey, true) |
Jungho Ahn | 4b9a519 | 2015-02-02 13:11:08 -0800 | [diff] [blame^] | 186 | sconn := newSetupConn(writer, reader, c) |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 187 | // TODO(jyh): act upon authentication results. |
| 188 | _, _, err := vc.AuthenticateAsServer(sconn, principal, lBlessings, dc, crypto.NewNullCrypter(), ipcversion.IPCVersion6) |
| 189 | if err != nil { |
| 190 | return nil, err |
| 191 | } |
| 192 | return c, nil |
| 193 | } |
| 194 | |
| 195 | // serverAuthOptions extracts the Principal from the options list. |
| 196 | func serverAuthOptions(lopts []stream.ListenerOpt) (principal security.Principal, lBlessings security.Blessings, dischargeClient vc.DischargeClient, err error) { |
| 197 | var securityLevel options.VCSecurityLevel |
| 198 | for _, o := range lopts { |
| 199 | switch v := o.(type) { |
| 200 | case vc.DischargeClient: |
| 201 | dischargeClient = v |
| 202 | case vc.LocalPrincipal: |
| 203 | principal = v.Principal |
| 204 | case options.VCSecurityLevel: |
| 205 | securityLevel = v |
| 206 | case options.ServerBlessings: |
| 207 | lBlessings = v.Blessings |
| 208 | } |
| 209 | } |
| 210 | switch securityLevel { |
| 211 | case options.VCSecurityConfidential: |
| 212 | if principal == nil { |
| 213 | principal = vc.AnonymousPrincipal |
| 214 | } |
| 215 | if lBlessings == nil { |
| 216 | lBlessings = principal.BlessingStore().Default() |
| 217 | } |
| 218 | case options.VCSecurityNone: |
| 219 | principal = nil |
| 220 | default: |
| 221 | err = fmt.Errorf("unrecognized VC security level: %v", securityLevel) |
| 222 | } |
| 223 | return |
| 224 | } |
| 225 | |
| 226 | // clientAuthOptions extracts the client authentication options from the options |
| 227 | // list. |
Matt Rosencrantz | 4f8ac60 | 2014-12-29 14:42:48 -0800 | [diff] [blame] | 228 | func clientAuthOptions(lopts []stream.VCOpt) (ctx *context.T, principal security.Principal, dischargeClient vc.DischargeClient, err error) { |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 229 | var securityLevel options.VCSecurityLevel |
Suharsh Sivakumar | 1131687 | 2014-11-25 15:57:00 -0800 | [diff] [blame] | 230 | var noDischarges bool |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 231 | for _, o := range lopts { |
| 232 | switch v := o.(type) { |
Asim Shankar | f4864f4 | 2014-11-25 18:53:05 -0800 | [diff] [blame] | 233 | case vc.DialContext: |
| 234 | ctx = v.T |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 235 | case vc.DischargeClient: |
| 236 | dischargeClient = v |
| 237 | case vc.LocalPrincipal: |
| 238 | principal = v.Principal |
| 239 | case options.VCSecurityLevel: |
| 240 | securityLevel = v |
Suharsh Sivakumar | 1131687 | 2014-11-25 15:57:00 -0800 | [diff] [blame] | 241 | case vc.NoDischarges: |
| 242 | noDischarges = true |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 243 | } |
| 244 | } |
Suharsh Sivakumar | 1131687 | 2014-11-25 15:57:00 -0800 | [diff] [blame] | 245 | if noDischarges { |
| 246 | dischargeClient = nil |
| 247 | } |
Jason Hickey | 96d30e8 | 2014-11-13 07:40:00 -0800 | [diff] [blame] | 248 | switch securityLevel { |
| 249 | case options.VCSecurityConfidential: |
| 250 | if principal == nil { |
| 251 | principal = vc.AnonymousPrincipal |
| 252 | } |
| 253 | case options.VCSecurityNone: |
| 254 | principal = nil |
| 255 | default: |
| 256 | err = fmt.Errorf("unrecognized VC security level: %v", securityLevel) |
| 257 | } |
| 258 | return |
| 259 | } |
| 260 | |
| 261 | // makeHopSetup constructs the options that this process can support. |
| 262 | func makeHopSetup(versions *version.Range) (pvt privateData, pub message.HopSetup, err error) { |
| 263 | pub.Versions = *versions |
| 264 | var pubKey, pvtKey *[32]byte |
| 265 | pubKey, pvtKey, err = box.GenerateKey(rand.Reader) |
| 266 | pub.Options = append(pub.Options, &message.NaclBox{PublicKey: *pubKey}) |
| 267 | pvt.naclBoxPrivateKey = *pvtKey |
| 268 | return |
| 269 | } |