OLD | NEW |
(Empty) | |
| 1 // Copyright 2011 The Go 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 |
| 5 package ssh |
| 6 |
| 7 import ( |
| 8 "errors" |
| 9 "io" |
| 10 ) |
| 11 |
| 12 // authenticate authenticates with the remote server. See RFC 4252. |
| 13 func (c *ClientConn) authenticate(session []byte) error { |
| 14 // initiate user auth session |
| 15 if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{ser
viceUserAuth})); err != nil { |
| 16 return err |
| 17 } |
| 18 packet, err := c.readPacket() |
| 19 if err != nil { |
| 20 return err |
| 21 } |
| 22 var serviceAccept serviceAcceptMsg |
| 23 if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != ni
l { |
| 24 return err |
| 25 } |
| 26 // during the authentication phase the client first attempts the "none"
method |
| 27 // then any untried methods suggested by the server. |
| 28 tried, remain := make(map[string]bool), make(map[string]bool) |
| 29 for auth := ClientAuth(new(noneAuth)); auth != nil; { |
| 30 ok, methods, err := auth.auth(session, c.config.User, c.transpor
t, c.config.rand()) |
| 31 if err != nil { |
| 32 return err |
| 33 } |
| 34 if ok { |
| 35 // success |
| 36 return nil |
| 37 } |
| 38 tried[auth.method()] = true |
| 39 delete(remain, auth.method()) |
| 40 for _, meth := range methods { |
| 41 if tried[meth] { |
| 42 // if we've tried meth already, skip it. |
| 43 continue |
| 44 } |
| 45 remain[meth] = true |
| 46 } |
| 47 auth = nil |
| 48 for _, a := range c.config.Auth { |
| 49 if remain[a.method()] { |
| 50 auth = a |
| 51 break |
| 52 } |
| 53 } |
| 54 } |
| 55 return errors.New("ssh: unable to authenticate, no supported methods rem
ain") |
| 56 } |
| 57 |
| 58 // A ClientAuth represents an instance of an RFC 4252 authentication method. |
| 59 type ClientAuth interface { |
| 60 // auth authenticates user over transport t. |
| 61 // Returns true if authentication is successful. |
| 62 // If authentication is not successful, a []string of alternative |
| 63 // method names is returned. |
| 64 auth(session []byte, user string, t *transport, rand io.Reader) (bool, [
]string, error) |
| 65 |
| 66 // method returns the RFC 4252 method name. |
| 67 method() string |
| 68 } |
| 69 |
| 70 // "none" authentication, RFC 4252 section 5.2. |
| 71 type noneAuth int |
| 72 |
| 73 func (n *noneAuth) auth(session []byte, user string, t *transport, rand io.Reade
r) (bool, []string, error) { |
| 74 if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{ |
| 75 User: user, |
| 76 Service: serviceSSH, |
| 77 Method: "none", |
| 78 })); err != nil { |
| 79 return false, nil, err |
| 80 } |
| 81 |
| 82 return handleAuthResponse(t) |
| 83 } |
| 84 |
| 85 func (n *noneAuth) method() string { |
| 86 return "none" |
| 87 } |
| 88 |
| 89 // "password" authentication, RFC 4252 Section 8. |
| 90 type passwordAuth struct { |
| 91 ClientPassword |
| 92 } |
| 93 |
| 94 func (p *passwordAuth) auth(session []byte, user string, t *transport, rand io.R
eader) (bool, []string, error) { |
| 95 type passwordAuthMsg struct { |
| 96 User string |
| 97 Service string |
| 98 Method string |
| 99 Reply bool |
| 100 Password string |
| 101 } |
| 102 |
| 103 pw, err := p.Password(user) |
| 104 if err != nil { |
| 105 return false, nil, err |
| 106 } |
| 107 |
| 108 if err := t.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{ |
| 109 User: user, |
| 110 Service: serviceSSH, |
| 111 Method: "password", |
| 112 Reply: false, |
| 113 Password: pw, |
| 114 })); err != nil { |
| 115 return false, nil, err |
| 116 } |
| 117 |
| 118 return handleAuthResponse(t) |
| 119 } |
| 120 |
| 121 func (p *passwordAuth) method() string { |
| 122 return "password" |
| 123 } |
| 124 |
| 125 // A ClientPassword implements access to a client's passwords. |
| 126 type ClientPassword interface { |
| 127 // Password returns the password to use for user. |
| 128 Password(user string) (password string, err error) |
| 129 } |
| 130 |
| 131 // ClientAuthPassword returns a ClientAuth using password authentication. |
| 132 func ClientAuthPassword(impl ClientPassword) ClientAuth { |
| 133 return &passwordAuth{impl} |
| 134 } |
| 135 |
| 136 // ClientKeyring implements access to a client key ring. |
| 137 type ClientKeyring interface { |
| 138 // Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if |
| 139 // no key exists at i. |
| 140 Key(i int) (key interface{}, err error) |
| 141 |
| 142 // Sign returns a signature of the given data using the i'th key |
| 143 // and the supplied random source. |
| 144 Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) |
| 145 } |
| 146 |
| 147 // "publickey" authentication, RFC 4252 Section 7. |
| 148 type publickeyAuth struct { |
| 149 ClientKeyring |
| 150 } |
| 151 |
| 152 type publickeyAuthMsg struct { |
| 153 User string |
| 154 Service string |
| 155 Method string |
| 156 // HasSig indicates to the reciver packet that the auth request is signe
d and |
| 157 // should be used for authentication of the request. |
| 158 HasSig bool |
| 159 Algoname string |
| 160 Pubkey string |
| 161 // Sig is defined as []byte so marshal will exclude it during validateKe
y |
| 162 Sig []byte `ssh:"rest"` |
| 163 } |
| 164 |
| 165 func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io.
Reader) (bool, []string, error) { |
| 166 |
| 167 // Authentication is performed in two stages. The first stage sends an |
| 168 // enquiry to test if each key is acceptable to the remote. The second |
| 169 // stage attempts to authenticate with the valid keys obtained in the |
| 170 // first stage. |
| 171 |
| 172 var index int |
| 173 // a map of public keys to their index in the keyring |
| 174 validKeys := make(map[int]interface{}) |
| 175 for { |
| 176 key, err := p.Key(index) |
| 177 if err != nil { |
| 178 return false, nil, err |
| 179 } |
| 180 if key == nil { |
| 181 // no more keys in the keyring |
| 182 break |
| 183 } |
| 184 |
| 185 if ok, err := p.validateKey(key, user, t); ok { |
| 186 validKeys[index] = key |
| 187 } else { |
| 188 if err != nil { |
| 189 return false, nil, err |
| 190 } |
| 191 } |
| 192 index++ |
| 193 } |
| 194 |
| 195 // methods that may continue if this auth is not successful. |
| 196 var methods []string |
| 197 for i, key := range validKeys { |
| 198 pubkey := serializePublickey(key) |
| 199 algoname := algoName(key) |
| 200 sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, use
rAuthRequestMsg{ |
| 201 User: user, |
| 202 Service: serviceSSH, |
| 203 Method: p.method(), |
| 204 }, []byte(algoname), pubkey)) |
| 205 if err != nil { |
| 206 return false, nil, err |
| 207 } |
| 208 // manually wrap the serialized signature in a string |
| 209 s := serializeSignature(algoname, sign) |
| 210 sig := make([]byte, stringLength(s)) |
| 211 marshalString(sig, s) |
| 212 msg := publickeyAuthMsg{ |
| 213 User: user, |
| 214 Service: serviceSSH, |
| 215 Method: p.method(), |
| 216 HasSig: true, |
| 217 Algoname: algoname, |
| 218 Pubkey: string(pubkey), |
| 219 Sig: sig, |
| 220 } |
| 221 p := marshal(msgUserAuthRequest, msg) |
| 222 if err := t.writePacket(p); err != nil { |
| 223 return false, nil, err |
| 224 } |
| 225 success, methods, err := handleAuthResponse(t) |
| 226 if err != nil { |
| 227 return false, nil, err |
| 228 } |
| 229 if success { |
| 230 return success, methods, err |
| 231 } |
| 232 } |
| 233 return false, methods, nil |
| 234 } |
| 235 |
| 236 // validateKey validates the key provided it is acceptable to the server. |
| 237 func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport)
(bool, error) { |
| 238 pubkey := serializePublickey(key) |
| 239 algoname := algoName(key) |
| 240 msg := publickeyAuthMsg{ |
| 241 User: user, |
| 242 Service: serviceSSH, |
| 243 Method: p.method(), |
| 244 HasSig: false, |
| 245 Algoname: algoname, |
| 246 Pubkey: string(pubkey), |
| 247 } |
| 248 if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil { |
| 249 return false, err |
| 250 } |
| 251 |
| 252 return p.confirmKeyAck(key, t) |
| 253 } |
| 254 |
| 255 func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, erro
r) { |
| 256 pubkey := serializePublickey(key) |
| 257 algoname := algoName(key) |
| 258 |
| 259 for { |
| 260 packet, err := t.readPacket() |
| 261 if err != nil { |
| 262 return false, err |
| 263 } |
| 264 switch packet[0] { |
| 265 case msgUserAuthBanner: |
| 266 // TODO(gpaul): add callback to present the banner to th
e user |
| 267 case msgUserAuthPubKeyOk: |
| 268 msg := decode(packet).(*userAuthPubKeyOkMsg) |
| 269 if msg.Algo != algoname || msg.PubKey != string(pubkey)
{ |
| 270 return false, nil |
| 271 } |
| 272 return true, nil |
| 273 case msgUserAuthFailure: |
| 274 return false, nil |
| 275 default: |
| 276 return false, UnexpectedMessageError{msgUserAuthSuccess,
packet[0]} |
| 277 } |
| 278 } |
| 279 panic("unreachable") |
| 280 } |
| 281 |
| 282 func (p *publickeyAuth) method() string { |
| 283 return "publickey" |
| 284 } |
| 285 |
| 286 // ClientAuthKeyring returns a ClientAuth using public key authentication. |
| 287 func ClientAuthKeyring(impl ClientKeyring) ClientAuth { |
| 288 return &publickeyAuth{impl} |
| 289 } |
| 290 |
| 291 // handleAuthResponse returns whether the preceding authentication request succe
eded |
| 292 // along with a list of remaining authentication methods to try next and |
| 293 // an error if an unexpected response was received. |
| 294 func handleAuthResponse(t *transport) (bool, []string, error) { |
| 295 for { |
| 296 packet, err := t.readPacket() |
| 297 if err != nil { |
| 298 return false, nil, err |
| 299 } |
| 300 |
| 301 switch packet[0] { |
| 302 case msgUserAuthBanner: |
| 303 // TODO: add callback to present the banner to the user |
| 304 case msgUserAuthFailure: |
| 305 msg := decode(packet).(*userAuthFailureMsg) |
| 306 return false, msg.Methods, nil |
| 307 case msgUserAuthSuccess: |
| 308 return true, nil, nil |
| 309 case msgDisconnect: |
| 310 return false, nil, io.EOF |
| 311 default: |
| 312 return false, nil, UnexpectedMessageError{msgUserAuthSuc
cess, packet[0]} |
| 313 } |
| 314 } |
| 315 panic("unreachable") |
| 316 } |
OLD | NEW |