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