Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 5 | package vc |
| 6 | |
| 7 | import ( |
| 8 | "bytes" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 9 | "io" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 10 | |
Matt Rosencrantz | 94502cf | 2015-03-18 09:43:44 -0700 | [diff] [blame] | 11 | "v.io/v23/rpc/version" |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 12 | "v.io/v23/security" |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 13 | "v.io/v23/verror" |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 14 | "v.io/v23/vom" |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 15 | |
Suharsh Sivakumar | dcc11d7 | 2015-05-11 12:19:20 -0700 | [diff] [blame] | 16 | "v.io/x/ref/runtime/internal/lib/iobuf" |
| 17 | "v.io/x/ref/runtime/internal/rpc/stream" |
| 18 | "v.io/x/ref/runtime/internal/rpc/stream/crypto" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 19 | ) |
| 20 | |
Andres Erbsen | ffa4574 | 2014-08-13 10:13:11 -0700 | [diff] [blame] | 21 | var ( |
| 22 | authServerContextTag = []byte("VCauthS\x00") |
| 23 | authClientContextTag = []byte("VCauthC\x00") |
| 24 | ) |
| 25 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 26 | var ( |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 27 | // These errors are intended to be used as arguments to higher |
| 28 | // level errors and hence {1}{2} is omitted from their format |
| 29 | // strings to avoid repeating these n-times in the final error |
| 30 | // message visible to the user. |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 31 | errVomEncodeBlessing = reg(".errVomEncodeRequest", "failed to encode blessing{:3}") |
| 32 | errHandshakeMessage = reg(".errHandshakeMessage", "failed to read hanshake message{:3}") |
| 33 | errInvalidSignatureInMessage = reg(".errInvalidSignatureInMessage", "signature does not verify in authentication handshake message") |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 34 | errFailedToCreateSelfBlessing = reg(".errFailedToCreateSelfBlessing", "failed to create self blessing{:3}") |
| 35 | errNoBlessingsToPresentToServer = reg(".errerrNoBlessingsToPresentToServer ", "no blessings to present as a server") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 36 | ) |
| 37 | |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 38 | // TODO(jhahn): Add len(ChannelBinding) > 0 check in writeBlessing/readBlessings |
| 39 | // to make sure that the auth protocol only works when len(ChannelBinding) > 0 |
| 40 | // once we deprecate RPCv10. |
| 41 | |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 42 | // AuthenticateAsServer executes the authentication protocol at the server. |
| 43 | // It returns the blessings shared by the client, and the discharges shared |
| 44 | // by the server. |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 45 | func AuthenticateAsServer(conn io.ReadWriteCloser, crypter crypto.Crypter, v version.RPCVersion, principal security.Principal, server security.Blessings, dc DischargeClient) (security.Blessings, map[string]security.Discharge, error) { |
Asim Shankar | 2bf7b1e | 2015-02-27 00:45:12 -0800 | [diff] [blame] | 46 | if server.IsZero() { |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 47 | return security.Blessings{}, nil, verror.New(stream.ErrSecurity, nil, verror.New(errNoBlessingsToPresentToServer, nil)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 48 | } |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 49 | var serverDischarges []security.Discharge |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 50 | if tpcavs := server.ThirdPartyCaveats(); len(tpcavs) > 0 && dc != nil { |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 51 | serverDischarges = dc.PrepareDischarges(nil, tpcavs, security.DischargeImpetus{}) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 52 | } |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 53 | if err := writeBlessings(conn, authServerContextTag, crypter, principal, server, serverDischarges, v); err != nil { |
| 54 | return security.Blessings{}, nil, err |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 55 | } |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 56 | // Note that since the client uses a self-signed blessing to authenticate |
| 57 | // during VC setup, it does not share any discharges. |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 58 | client, _, err := readBlessings(conn, authClientContextTag, crypter, v) |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 59 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 60 | return security.Blessings{}, nil, err |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 61 | } |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 62 | return client, mkDischargeMap(serverDischarges), nil |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 63 | } |
| 64 | |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 65 | // AuthenticateAsClient executes the authentication protocol at the client. |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 66 | // It returns the blessing shared by the client, and the blessings and any |
| 67 | // discharges shared by the server. |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 68 | // |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 69 | // The client will only share its blessings if the server (who shares its |
| 70 | // blessings first) is authorized as per the authorizer for this RPC. |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 71 | func AuthenticateAsClient(conn io.ReadWriteCloser, crypter crypto.Crypter, v version.RPCVersion, params security.CallParams, auth *ServerAuthorizer) (security.Blessings, security.Blessings, map[string]security.Discharge, error) { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 72 | server, serverDischarges, err := readBlessings(conn, authServerContextTag, crypter, v) |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 73 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 74 | return security.Blessings{}, security.Blessings{}, nil, err |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 75 | } |
Ankur | 50a5f39 | 2015-02-27 18:46:30 -0800 | [diff] [blame] | 76 | // Authorize the server based on the provided authorizer. |
| 77 | if auth != nil { |
| 78 | params.RemoteBlessings = server |
| 79 | params.RemoteDischarges = serverDischarges |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 80 | if err := auth.Authorize(params); err != nil { |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 81 | // Note this error type should match with the one in HandshakeDialedVCPreAuthenticated(). |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 82 | return security.Blessings{}, security.Blessings{}, nil, verror.New(stream.ErrNotTrusted, nil, err) |
Ankur | 50a5f39 | 2015-02-27 18:46:30 -0800 | [diff] [blame] | 83 | } |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 84 | } |
Ankur | 50a5f39 | 2015-02-27 18:46:30 -0800 | [diff] [blame] | 85 | |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 86 | // The client shares its blessings at RPC time (as the blessings may vary |
| 87 | // across RPCs). During VC handshake, the client simply sends a self-signed |
| 88 | // blessing in order to reveal its public key to the server. |
Ankur | 50a5f39 | 2015-02-27 18:46:30 -0800 | [diff] [blame] | 89 | principal := params.LocalPrincipal |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 90 | client, err := principal.BlessSelf("vcauth") |
Ankur | 50a5f39 | 2015-02-27 18:46:30 -0800 | [diff] [blame] | 91 | if err != nil { |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 92 | return security.Blessings{}, security.Blessings{}, nil, verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateSelfBlessing, nil, err)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 93 | } |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 94 | if err := writeBlessings(conn, authClientContextTag, crypter, principal, client, nil, v); err != nil { |
| 95 | return security.Blessings{}, security.Blessings{}, nil, err |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 96 | } |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 97 | return client, server, serverDischarges, nil |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 98 | } |
| 99 | |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 100 | func writeBlessings(w io.Writer, tag []byte, crypter crypto.Crypter, p security.Principal, b security.Blessings, discharges []security.Discharge, v version.RPCVersion) error { |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 101 | signature, err := p.Sign(append(tag, crypter.ChannelBinding()...)) |
| 102 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 103 | return err |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 104 | } |
| 105 | var buf bytes.Buffer |
Jungho Ahn | 5d1fe97 | 2015-04-27 17:51:32 -0700 | [diff] [blame] | 106 | enc := vom.NewEncoder(&buf) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 107 | if err := enc.Encode(signature); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 108 | return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 109 | } |
Asim Shankar | b07ec69 | 2015-02-27 23:40:44 -0800 | [diff] [blame] | 110 | if err := enc.Encode(b); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 111 | return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 112 | } |
Matt Rosencrantz | 1ca4718 | 2015-04-22 17:13:28 -0700 | [diff] [blame] | 113 | if err := enc.Encode(discharges); err != nil { |
| 114 | return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err)) |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 115 | } |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 116 | msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes())) |
| 117 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 118 | return err |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 119 | } |
| 120 | defer msg.Release() |
Jungho Ahn | 5d1fe97 | 2015-04-27 17:51:32 -0700 | [diff] [blame] | 121 | enc = vom.NewEncoder(w) |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 122 | if err := enc.Encode(msg.Contents); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 123 | return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err)) |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 124 | } |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 125 | return nil |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 126 | } |
| 127 | |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 128 | func readBlessings(r io.Reader, tag []byte, crypter crypto.Crypter, v version.RPCVersion) (security.Blessings, map[string]security.Discharge, error) { |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 129 | var msg []byte |
Asim Shankar | 2bf7b1e | 2015-02-27 00:45:12 -0800 | [diff] [blame] | 130 | var noBlessings security.Blessings |
Jungho Ahn | 5d1fe97 | 2015-04-27 17:51:32 -0700 | [diff] [blame] | 131 | dec := vom.NewDecoder(r) |
Asim Shankar | dae12eb | 2015-02-23 12:09:30 -0800 | [diff] [blame] | 132 | if err := dec.Decode(&msg); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 133 | return noBlessings, nil, verror.New(stream.ErrNetwork, nil, verror.New(errHandshakeMessage, nil, err)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 134 | } |
| 135 | buf, err := crypter.Decrypt(iobuf.NewSlice(msg)) |
| 136 | if err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 137 | return noBlessings, nil, err |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 138 | } |
| 139 | defer buf.Release() |
Jungho Ahn | 5d1fe97 | 2015-04-27 17:51:32 -0700 | [diff] [blame] | 140 | dec = vom.NewDecoder(bytes.NewReader(buf.Contents)) |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 141 | var ( |
Asim Shankar | b07ec69 | 2015-02-27 23:40:44 -0800 | [diff] [blame] | 142 | blessings security.Blessings |
| 143 | sig security.Signature |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 144 | ) |
| 145 | if err = dec.Decode(&sig); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 146 | return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 147 | } |
Asim Shankar | b07ec69 | 2015-02-27 23:40:44 -0800 | [diff] [blame] | 148 | if err = dec.Decode(&blessings); err != nil { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 149 | return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 150 | } |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 151 | var discharges []security.Discharge |
Matt Rosencrantz | 1ca4718 | 2015-04-22 17:13:28 -0700 | [diff] [blame] | 152 | if err := dec.Decode(&discharges); err != nil { |
| 153 | return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err) |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 154 | } |
Ankur | e49a86a | 2014-11-11 18:52:43 -0800 | [diff] [blame] | 155 | if !sig.Verify(blessings.PublicKey(), append(tag, crypter.ChannelBinding()...)) { |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 156 | return noBlessings, nil, verror.New(stream.ErrSecurity, nil, verror.New(errInvalidSignatureInMessage, nil)) |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 157 | } |
Cosmos Nicolaou | 185c0c6 | 2015-04-13 21:22:43 -0700 | [diff] [blame] | 158 | return blessings, mkDischargeMap(discharges), nil |
Ankur | edd74ee | 2015-03-04 16:38:45 -0800 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | func mkDischargeMap(discharges []security.Discharge) map[string]security.Discharge { |
| 162 | if len(discharges) == 0 { |
| 163 | return nil |
| 164 | } |
| 165 | m := make(map[string]security.Discharge, len(discharges)) |
| 166 | for _, d := range discharges { |
| 167 | m[d.ID()] = d |
| 168 | } |
| 169 | return m |
Asim Shankar | 6b0510a | 2014-10-01 12:05:06 -0700 | [diff] [blame] | 170 | } |
Jungho Ahn | 19e84b2 | 2015-05-18 13:22:27 -0700 | [diff] [blame^] | 171 | |
| 172 | func bindClientPrincipalToChannel(crypter crypto.Crypter, p security.Principal) ([]byte, error) { |
| 173 | sig, err := p.Sign(append(authClientContextTag, crypter.ChannelBinding()...)) |
| 174 | if err != nil { |
| 175 | return nil, err |
| 176 | } |
| 177 | var buf bytes.Buffer |
| 178 | enc := vom.NewEncoder(&buf) |
| 179 | if err := enc.Encode(sig); err != nil { |
| 180 | return nil, verror.New(errVomEncodeBlessing, nil, err) |
| 181 | } |
| 182 | msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes())) |
| 183 | if err != nil { |
| 184 | return nil, err |
| 185 | } |
| 186 | defer msg.Release() |
| 187 | signature := make([]byte, len(msg.Contents)) |
| 188 | copy(signature, msg.Contents) |
| 189 | return signature, nil |
| 190 | } |
| 191 | |
| 192 | func verifyClientPrincipalBoundToChannel(signature []byte, crypter crypto.Crypter, publicKey security.PublicKey) error { |
| 193 | msg, err := crypter.Decrypt(iobuf.NewSlice(signature)) |
| 194 | if err != nil { |
| 195 | return err |
| 196 | } |
| 197 | defer msg.Release() |
| 198 | dec := vom.NewDecoder(bytes.NewReader(msg.Contents)) |
| 199 | var sig security.Signature |
| 200 | if err = dec.Decode(&sig); err != nil { |
| 201 | return verror.New(errHandshakeMessage, nil, err) |
| 202 | } |
| 203 | if !sig.Verify(publicKey, append(authClientContextTag, crypter.ChannelBinding()...)) { |
| 204 | return verror.New(errInvalidSignatureInMessage, nil) |
| 205 | } |
| 206 | return nil |
| 207 | } |