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