transport.go (9418B)
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 "bufio" 9 "bytes" 10 "errors" 11 "io" 12 "log" 13 ) 14 15 // debugTransport if set, will print packet types as they go over the 16 // wire. No message decoding is done, to minimize the impact on timing. 17 const debugTransport = false 18 19 const ( 20 gcm128CipherID = "aes128-gcm@openssh.com" 21 gcm256CipherID = "aes256-gcm@openssh.com" 22 aes128cbcID = "aes128-cbc" 23 tripledescbcID = "3des-cbc" 24 ) 25 26 // packetConn represents a transport that implements packet based 27 // operations. 28 type packetConn interface { 29 // Encrypt and send a packet of data to the remote peer. 30 writePacket(packet []byte) error 31 32 // Read a packet from the connection. The read is blocking, 33 // i.e. if error is nil, then the returned byte slice is 34 // always non-empty. 35 readPacket() ([]byte, error) 36 37 // Close closes the write-side of the connection. 38 Close() error 39 } 40 41 // transport is the keyingTransport that implements the SSH packet 42 // protocol. 43 type transport struct { 44 reader connectionState 45 writer connectionState 46 47 bufReader *bufio.Reader 48 bufWriter *bufio.Writer 49 rand io.Reader 50 isClient bool 51 io.Closer 52 } 53 54 // packetCipher represents a combination of SSH encryption/MAC 55 // protocol. A single instance should be used for one direction only. 56 type packetCipher interface { 57 // writeCipherPacket encrypts the packet and writes it to w. The 58 // contents of the packet are generally scrambled. 59 writeCipherPacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error 60 61 // readCipherPacket reads and decrypts a packet of data. The 62 // returned packet may be overwritten by future calls of 63 // readPacket. 64 readCipherPacket(seqnum uint32, r io.Reader) ([]byte, error) 65 } 66 67 // connectionState represents one side (read or write) of the 68 // connection. This is necessary because each direction has its own 69 // keys, and can even have its own algorithms 70 type connectionState struct { 71 packetCipher 72 seqNum uint32 73 dir direction 74 pendingKeyChange chan packetCipher 75 } 76 77 // prepareKeyChange sets up key material for a keychange. The key changes in 78 // both directions are triggered by reading and writing a msgNewKey packet 79 // respectively. 80 func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { 81 ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult) 82 if err != nil { 83 return err 84 } 85 t.reader.pendingKeyChange <- ciph 86 87 ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult) 88 if err != nil { 89 return err 90 } 91 t.writer.pendingKeyChange <- ciph 92 93 return nil 94 } 95 96 func (t *transport) printPacket(p []byte, write bool) { 97 if len(p) == 0 { 98 return 99 } 100 who := "server" 101 if t.isClient { 102 who = "client" 103 } 104 what := "read" 105 if write { 106 what = "write" 107 } 108 109 log.Println(what, who, p[0]) 110 } 111 112 // Read and decrypt next packet. 113 func (t *transport) readPacket() (p []byte, err error) { 114 for { 115 p, err = t.reader.readPacket(t.bufReader) 116 if err != nil { 117 break 118 } 119 if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) { 120 break 121 } 122 } 123 if debugTransport { 124 t.printPacket(p, false) 125 } 126 127 return p, err 128 } 129 130 func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { 131 packet, err := s.packetCipher.readCipherPacket(s.seqNum, r) 132 s.seqNum++ 133 if err == nil && len(packet) == 0 { 134 err = errors.New("ssh: zero length packet") 135 } 136 137 if len(packet) > 0 { 138 switch packet[0] { 139 case msgNewKeys: 140 select { 141 case cipher := <-s.pendingKeyChange: 142 s.packetCipher = cipher 143 default: 144 return nil, errors.New("ssh: got bogus newkeys message") 145 } 146 147 case msgDisconnect: 148 // Transform a disconnect message into an 149 // error. Since this is lowest level at which 150 // we interpret message types, doing it here 151 // ensures that we don't have to handle it 152 // elsewhere. 153 var msg disconnectMsg 154 if err := Unmarshal(packet, &msg); err != nil { 155 return nil, err 156 } 157 return nil, &msg 158 } 159 } 160 161 // The packet may point to an internal buffer, so copy the 162 // packet out here. 163 fresh := make([]byte, len(packet)) 164 copy(fresh, packet) 165 166 return fresh, err 167 } 168 169 func (t *transport) writePacket(packet []byte) error { 170 if debugTransport { 171 t.printPacket(packet, true) 172 } 173 return t.writer.writePacket(t.bufWriter, t.rand, packet) 174 } 175 176 func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error { 177 changeKeys := len(packet) > 0 && packet[0] == msgNewKeys 178 179 err := s.packetCipher.writeCipherPacket(s.seqNum, w, rand, packet) 180 if err != nil { 181 return err 182 } 183 if err = w.Flush(); err != nil { 184 return err 185 } 186 s.seqNum++ 187 if changeKeys { 188 select { 189 case cipher := <-s.pendingKeyChange: 190 s.packetCipher = cipher 191 default: 192 panic("ssh: no key material for msgNewKeys") 193 } 194 } 195 return err 196 } 197 198 func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport { 199 t := &transport{ 200 bufReader: bufio.NewReader(rwc), 201 bufWriter: bufio.NewWriter(rwc), 202 rand: rand, 203 reader: connectionState{ 204 packetCipher: &streamPacketCipher{cipher: noneCipher{}}, 205 pendingKeyChange: make(chan packetCipher, 1), 206 }, 207 writer: connectionState{ 208 packetCipher: &streamPacketCipher{cipher: noneCipher{}}, 209 pendingKeyChange: make(chan packetCipher, 1), 210 }, 211 Closer: rwc, 212 } 213 t.isClient = isClient 214 215 if isClient { 216 t.reader.dir = serverKeys 217 t.writer.dir = clientKeys 218 } else { 219 t.reader.dir = clientKeys 220 t.writer.dir = serverKeys 221 } 222 223 return t 224 } 225 226 type direction struct { 227 ivTag []byte 228 keyTag []byte 229 macKeyTag []byte 230 } 231 232 var ( 233 serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} 234 clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} 235 ) 236 237 // setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as 238 // described in RFC 4253, section 6.4. direction should either be serverKeys 239 // (to setup server->client keys) or clientKeys (for client->server keys). 240 func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { 241 cipherMode := cipherModes[algs.Cipher] 242 243 iv := make([]byte, cipherMode.ivSize) 244 key := make([]byte, cipherMode.keySize) 245 246 generateKeyMaterial(iv, d.ivTag, kex) 247 generateKeyMaterial(key, d.keyTag, kex) 248 249 var macKey []byte 250 if !aeadCiphers[algs.Cipher] { 251 macMode := macModes[algs.MAC] 252 macKey = make([]byte, macMode.keySize) 253 generateKeyMaterial(macKey, d.macKeyTag, kex) 254 } 255 256 return cipherModes[algs.Cipher].create(key, iv, macKey, algs) 257 } 258 259 // generateKeyMaterial fills out with key material generated from tag, K, H 260 // and sessionId, as specified in RFC 4253, section 7.2. 261 func generateKeyMaterial(out, tag []byte, r *kexResult) { 262 var digestsSoFar []byte 263 264 h := r.Hash.New() 265 for len(out) > 0 { 266 h.Reset() 267 h.Write(r.K) 268 h.Write(r.H) 269 270 if len(digestsSoFar) == 0 { 271 h.Write(tag) 272 h.Write(r.SessionID) 273 } else { 274 h.Write(digestsSoFar) 275 } 276 277 digest := h.Sum(nil) 278 n := copy(out, digest) 279 out = out[n:] 280 if len(out) > 0 { 281 digestsSoFar = append(digestsSoFar, digest...) 282 } 283 } 284 } 285 286 const packageVersion = "SSH-2.0-Go" 287 288 // Sends and receives a version line. The versionLine string should 289 // be US ASCII, start with "SSH-2.0-", and should not include a 290 // newline. exchangeVersions returns the other side's version line. 291 func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { 292 // Contrary to the RFC, we do not ignore lines that don't 293 // start with "SSH-2.0-" to make the library usable with 294 // nonconforming servers. 295 for _, c := range versionLine { 296 // The spec disallows non US-ASCII chars, and 297 // specifically forbids null chars. 298 if c < 32 { 299 return nil, errors.New("ssh: junk character in version line") 300 } 301 } 302 if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { 303 return 304 } 305 306 them, err = readVersion(rw) 307 return them, err 308 } 309 310 // maxVersionStringBytes is the maximum number of bytes that we'll 311 // accept as a version string. RFC 4253 section 4.2 limits this at 255 312 // chars 313 const maxVersionStringBytes = 255 314 315 // Read version string as specified by RFC 4253, section 4.2. 316 func readVersion(r io.Reader) ([]byte, error) { 317 versionString := make([]byte, 0, 64) 318 var ok bool 319 var buf [1]byte 320 321 for length := 0; length < maxVersionStringBytes; length++ { 322 _, err := io.ReadFull(r, buf[:]) 323 if err != nil { 324 return nil, err 325 } 326 // The RFC says that the version should be terminated with \r\n 327 // but several SSH servers actually only send a \n. 328 if buf[0] == '\n' { 329 if !bytes.HasPrefix(versionString, []byte("SSH-")) { 330 // RFC 4253 says we need to ignore all version string lines 331 // except the one containing the SSH version (provided that 332 // all the lines do not exceed 255 bytes in total). 333 versionString = versionString[:0] 334 continue 335 } 336 ok = true 337 break 338 } 339 340 // non ASCII chars are disallowed, but we are lenient, 341 // since Go doesn't use null-terminated strings. 342 343 // The RFC allows a comment after a space, however, 344 // all of it (version and comments) goes into the 345 // session hash. 346 versionString = append(versionString, buf[0]) 347 } 348 349 if !ok { 350 return nil, errors.New("ssh: overflow reading version string") 351 } 352 353 // There might be a '\r' on the end which we should remove. 354 if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { 355 versionString = versionString[:len(versionString)-1] 356 } 357 return versionString, nil 358 }