sync golang ssh x/crypto/ssh: ParsePrivateKey errors out with encrypted private keys RSA and DSA keys if encrypted have the phrase ENCRYPTED in their Proc-Type block header according to RFC 1421 Section 4.6.1.1. This CL checks for that phrase and errors out if we encounter it, since we don't yet have decryption of encrypted private keys. Fixes golang/go#6650 Change-Id: I5b157716a2f93557d289af5f62994234a2e7a0ed Reviewed-on: https://go-review.googlesource.com/29676 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/terminal: implement ReadPassword and IsTerminal Fixes golang/go#13085. Change-Id: I2fcdd60e5e8db032d6fa3ce76198bdc7a63f3cf6 Reviewed-on: https://go-review.googlesource.com/16722 Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> ssh: Consistent error handling in examples After discussion around an example SFTP implementation: https://github.com/pkg/sftp/pull/54 it has been suggested that errors should be handled using log.Fatal rather than panic, and that the actual underlying error should also be logged. In the existing SSH examples there are several different styles of error handling using both panic and log.Fatalf. This patch uses log.Fatal consistently for all of these cases. Change-Id: I2cebfae1821530dc3c5bbc46d451fe026bed582f Reviewed-on: https://go-review.googlesource.com/16736 Reviewed-by: Russ Cox <rsc@golang.org> x/crypto/ssh: public key authentication example Fixes golang/go#13902. Adds public key authentication to the password authentication example. Change-Id: I4af0ca627fb15b617cc1ba1c6e0954b013f4d94f Reviewed-on: https://go-review.googlesource.com/29374 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: fix height/width order in RequestPty example The RequestPty function takes the size arguments in the order height, then width, instead of the more common width, then height. 80 is a very common width for a terminal, so when the example reads RequestPty(..., 80, 40, ...), it's easy to assume that the order is width-height. Switching the order should make it more obvious what is going on. Change-Id: I1d6266b1c0dcde5ee6e31a6d26d2dcaf14fec58a Reviewed-on: https://go-review.googlesource.com/18290 Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> ssh: add CryptoPublicKey interface, expose underlying crypto.PublicKey When implemented by ssh.PublicKey types, the new CryptoPublicKey interface exposes the public key in the the crypto.PublicKey form via a CryptoPublicKey() method. This is useful for example in a custom ServerConfig.PublicKeyCallback function to check or record additional details about the underlying crypto.PublicKey Change-Id: I4429df42c6fc5119f7c0023a539aaa9c59648bba Reviewed-on: https://go-review.googlesource.com/23974 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> sync with golang/ssh upstream Allow hyphen-characters within usernames (#9) * Allow hyphen-characters within usernames. * Allow up to 32 characters for usernames. ssh: bound DH public values to [2, p-2]. Previously this code bounded the values to [1, p-1]. This protects against invalid values that could take lots of CPU time to calculate with. But the standard bounding is [2, p-2] so mirror that. Since the DH exchange is signed anyway, this is not a security fix. Change-Id: Ibef01805a596a433b0699d7a09c076344fa8c070 Reviewed-on: https://go-review.googlesource.com/30590 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> crypto/ssh: fix comment for ssh.NewPublicKey Change-Id: I88bb7859259c82cd77ab2d26b728143281761def Reviewed-on: https://go-review.googlesource.com/25232 Reviewed-by: Russ Cox <rsc@golang.org> x/crypto/ssh: Add FingerprintLegacyMD5 and FingerprintSHA256 methods Implement a standards-compliant fingerprint format method (RFC 4716 section 4) and a newer SHA256 fingerprint format method. Fixes golang/go#12292 Change-Id: I4f3f8fc1d0a263cb3b0964d0078e69006a39d1a5 Reviewed-on: https://go-review.googlesource.com/32814 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> x/crypto/ssh/terminal: replace \n with \r\n. 911fafb28f4 made MakeRaw match C's behaviour. This included clearing the OPOST flag, which means that one now needs to write \r\n for a newline, otherwise the cursor doesn't move back to the beginning and the terminal prints a staircase. (Dear god, we're still emulating line printers.) This change causes the terminal package to do the required transformation. Fixes golang/go#17364. Change-Id: Ida15d3cf701a21eaa59161ab61b3ed4dee2ded46 Reviewed-on: https://go-review.googlesource.com/33902 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> crypto/ssh: use net.IP.Equal instead of bytes.Equal A net.IP may be represented by both by a 4 as well as a 16 byte long byte slice. Because of this, it is not safe to compare IP addresses using bytes.Equal as the same IP address using a different internal representation will produce mismatches. Change-Id: I0d228771cf363ccfb9532f8bc2a2fc8eff61f6e9 Reviewed-on: https://go-review.googlesource.com/34450 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/terminal: fix a typo Change-Id: Iafe2ebb6d37afd2a64aa72750a722d4860bb735e Reviewed-on: https://go-review.googlesource.com/34535 Run-TryBot: Mikio Hara <mikioh.mikioh@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> all: fix some vet warnings Change-Id: I85c2912a6862c6c251450f2a0926ecd33a9fb8e7 Reviewed-on: https://go-review.googlesource.com/34815 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/terminal: consistent return value for Restore This patch makes the Restore function return nil on success to be consistent with other functions like MakeRaw. Change-Id: I81e63f568787dd88466a5bb30cb87c4c3be75a5c Reviewed-on: https://go-review.googlesource.com/34952 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> x/ssh: filter debug and ignore messages in transport.readPacket. This prevents these messages from confusing higher layers of the protocol. Fixes #16927. Change-Id: If18d8d02bdde3c0470e29a7280cd355d3e55ad78 Reviewed-on: https://go-review.googlesource.com/34959 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh: make client auth tests less chatty. Change-Id: Ib35ce0e7437e32a3fa24a9330c479306b7fa6880 Reviewed-on: https://go-review.googlesource.com/35011 Reviewed-by: Adam Langley <agl@golang.org> ssh: rewrite (re)keying logic. Use channels and a dedicated write loop for managing the rekeying process. This lets us collect packets to be written while a key exchange is in progress. Previously, the read loop ran the key exchange, and writers would block if a key exchange was going on. If a reader wrote back a packet while processing a read packet, it could block, stopping the read loop, thus causing a deadlock. Such coupled read/writes are inherent with handling requests that want a response (eg. keepalive, opening/closing channels etc.). The buffered channels (most channels have capacity 16) papered over these problems, but under load SSH connections would occasionally deadlock. Fixes #18439. Change-Id: I7c14ff4991fa3100a5d36025125d0cf1119c471d Reviewed-on: https://go-review.googlesource.com/35012 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh/terminal: fix line endings handling in ReadPassword Fixes golang/go#16552 Change-Id: I18a9c9b42fe042c4871b3efb3f51bef7cca335d0 Reviewed-on: https://go-review.googlesource.com/25355 Reviewed-by: Adam Langley <alangley@gmail.com> Reviewed-by: Adam Langley <agl@golang.org> ssh/terminal: consume data before checking for an error. According to the io.Reader docs, Alex had it right the first time. (See discussion on https://golang.org/cl/25355.) Change-Id: Ib6fb9dfb99009e034263574e82d7e9d4828df38f Reviewed-on: https://go-review.googlesource.com/35242 TryBot-Result: Gobot Gobot <gobot@golang.org> Run-TryBot: Adam Langley <agl@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Adam Langley <agl@golang.org> crypto/ssh: fix parsing order for ssh.ParseDSAPrivateKey The inline struct has the wrong order for the public and private key parts. Change-Id: Ib3a5d6846296a2300241331a2ad398579e042ca9 Reviewed-on: https://go-review.googlesource.com/35351 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: soft code internal channel size for testing purposes Change-Id: I2ee0ed4ba82d2d156a7896551dea04b28cdeceb0 Reviewed-on: https://go-review.googlesource.com/35184 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh: make sure we execute the initial key exchange only once The initial kex is started from both sides simultaneously, and before, we could consume the the incoming kex request before we consumed from our internal channel. This would result in initiating a key exchange just after completing the initial one, which is not only an extra delay, but also an error when using OpenSSH (OpenSSH does not support key exchanges during user authentication). Change-Id: Ia7e0748ea2bca80ae97d187bcf2931ab6422276b Reviewed-on: https://go-review.googlesource.com/35851 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh: rationalize rekeying decisions. 1) Always force a key exchange if we exchange 2^31 packets. In the past this might not happen if RekeyThreshold was set to a very large interval. 2) Follow recommendations from RFC 4344 for block ciphers. For AES, we can encrypt 2^(blocksize/4) blocks under the same keys. On modern hardware, the previous default of 1Gb could force a key exchange within ~10 seconds. Since the key exchange takes 3 roundtrips (send kex init, send DH init, send NEW_KEYS), this is relatively expensive on high-latency links. Change-Id: I1297124a307c541b7bf22d814d136ec0c6d8ed97 Reviewed-on: https://go-review.googlesource.com/35410 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh: add debug print at the lowest level This is a simple minded, fast print, suitable for debugging timing sensitive issues. Change-Id: I9ae3df5fe86f1883c1fa9265b6f7f9a98d33747e Reviewed-on: https://go-review.googlesource.com/36054 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> ssh: reset buffered packets after sending Since encryption messes up the packets, the wrongly retained packets look like noise and cause application protocol errors or panics in the SSH library. This normally triggers very rarely: the mandatory key exchange doesn't have parallel writes, so this failure condition would be setup on the first key exchange, take effect only after the second key exchange. Fortunately, the tests against openssh exercise this. This change adds also adds a unittest. Fixes #18850. Change-Id: I656c8b94bfb265831daa118f4d614a2f0c65d2af Reviewed-on: https://go-review.googlesource.com/36056 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: Support multiple source-addresses, don't require IPv4 in tests. The ssh tests currently require 127.0.0.1 to work which isn't necessarily available everywhere. To fix the source-address tests, support comma-separated source-address values per the PROTOCOL.certkeys file: Comma-separated list of source addresses from which this certificate is accepted for authentication. Addresses are specified in CIDR format (nn.nn.nn.nn/nn or hhhh::hhhh/nn). If this option is not present then certificates may be presented from any source address. Change-Id: I87536ff81ffa005c073da103021ebc0dfb12b620 Reviewed-on: https://go-review.googlesource.com/36110 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Heschi Kreinick <heschi@google.com> ssh: prevent double kex at connection start, 2nd try The previous attempt would fail in the following scenario: * select picks "first" kex from requestKex * read loop receives a remote kex, posts on requestKex (which is now empty) [*] for sending out a response, and sends pendingKex on startKex. * select picks pendingKex from startKex, and proceeds to run the key exchange. * the posting on requestKex in [*] now triggers a second key exchange. Fixes #18861. Change-Id: I443e82f1d04c7f17d1485fdb87072b9feec26aa8 Reviewed-on: https://go-review.googlesource.com/36055 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh/agent: fix another test to not require IPv4. Missed a copy/paste of netPipe in change 36110. Change-Id: I1a850dd9273d71fadc0519cf4cb2a2de6ecae4c2 Reviewed-on: https://go-review.googlesource.com/36259 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh: Add the hmac-sha2-256-etm@openssh.com algorithm Fixes golang/go#17676 Change-Id: I96c51431b174898a6bc0f6bec7f4561d5d64819f Reviewed-on: https://go-review.googlesource.com/35513 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: require host key checking in the ClientConfig This change breaks existing behavior. Before, a missing ClientConfig.HostKeyCallback would cause host key checking to be disabled. In this configuration, establishing a connection to any host just works, so today, most SSH client code in the wild does not perform any host key checks. This makes it easy to perform a MITM attack: * SSH installations that use keyboard-interactive or password authentication can be attacked with MITM, thereby stealing passwords. * Clients that use public-key authentication with agent forwarding are also vulnerable: the MITM server could allow the login to succeed, and then immediately ask the agent to authenticate the login to the real server. * Clients that use public-key authentication without agent forwarding are harder to attack unnoticedly: an attacker cannot authenticate the login to the real server, so it cannot in general present a convincing server to the victim. Now, a missing HostKeyCallback will cause the handshake to fail. This change also provides InsecureIgnoreHostKey() and FixedHostKey(key) as ready made host checkers. A simplistic parser for OpenSSH's known_hosts file is given as an example. This change does not provide a full-fledged parser, as it has complexity (wildcards, revocation, hashed addresses) that will need further consideration. When introduced, the host checking feature maintained backward compatibility at the expense of security. We have decided this is not the right tradeoff for the SSH library. Fixes golang/go#19767 Change-Id: I45fc7ba9bd1ea29c31ec23f115cdbab99913e814 Reviewed-on: https://go-review.googlesource.com/38701 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> ssh: handle error from prepareKeyChange. Fixes #18850. Change-Id: Id3ae89233f9e95ec3238462bf2ecda3e0c515f88 Reviewed-on: https://go-review.googlesource.com/36051 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh: fix typo in unexported comment Thanks to Anisse Astier (@anisse) for noticing. Change-Id: I1c282b2bb54601cf5649e194eafd5344c70331ca Reviewed-on: https://go-review.googlesource.com/38916 Reviewed-by: dnv aps Sn <sndnvaps@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> ssh: improve client public key authentication Previously, the public key authentication for clients would send an enquiry to the remote for every key specified before attempting to authenticate with the server. Now, we immediately try to authenticate once a valid key is found. This results in exchanging fewer packets if the valid key is near the top of the list. If all keys fail, then the number of packets exchanged by the client and server is unaffected. For OpenSSH daemon, an enquiry into the validity of a key without authentication is still recorded as an authentication attempt, so any clients with more than MaxAuthTries public keys would not be able to authenticate using the previous implementation. This change will allow clients to succeed authentication if the successful key is at the start of the list of keys. Change-Id: I8ea42caf40c0864752218c3f6934e86b12f5b81a Reviewed-on: https://go-review.googlesource.com/38890 Reviewed-by: Adam Langley <agl@golang.org> ssh: reject RekeyThresholds over MaxInt64 This fixes weirdness when users use int64(-1) as sentinel value. Also, really use cipher specific default thresholds. These were added in a59c127441a8ae2ad9b0fb300ab36a6558bba697, but weren't taking effect. Add a test. Fixes golang/go#19639 Change-Id: Ie9518a0ff12fded2fca35465abb427d7a9f84340 Reviewed-on: https://go-review.googlesource.com/39431 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> ssh: fix format string in client_test.go Change-Id: I92c3916b0b5628dc2079af82202d9bfef032c708 Reviewed-on: https://go-review.googlesource.com/39430 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> ssh: Add support for RSA keys stored in OpenSSH's new format Adds support for parsing RSA keys in the openssh-key-v1 private key format. Change-Id: Iacdcbaadf72413e4067d146203604fb50b780083 Reviewed-on: https://go-review.googlesource.com/35244 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> Reviewed-by: Paul Querna <paul@querna.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: support forwarding of Unix domain socket connections This commit implements OpenSSH streamlocal extension, providing the equivalent of `ssh -L local.sock:remote.sock`. Change-Id: Idd6287d5a5669c643132bba770c3b4194615e84d Reviewed-on: https://go-review.googlesource.com/38614 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: support MaxAuthTries on ServerConfig This change breaks backwards compatibility. MaxAuthTries specifies the maximum number of authentication attempts permitted per connection. If set to a negative number, the server will allow unlimited authentication attempts. MaxAuthTries defaults to 6 if not specified, which is a backwards incompatible change. On exceeding maximum authentication attempts, the server will send a disconnect message to the client. This configuration property mirrors a similar property in sshd_config and prevents bad actors from continuously trying authentication. Change-Id: Ic77d2c29ee2fd2ae5c764becf7df91d29d03131b Reviewed-on: https://go-review.googlesource.com/35230 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: set rekeying thresholds on construction The normal handshake kicks off with a waitSession(), which guarantees that we never attempt to send data before the first kex is completed, but ensuring readPacketsLeft > 0 and writePacketsLeft > 0 helps understand that thresholds can never cause spurious rekeying at the start of a connection. Change-Id: If5bcafcda0c7d16fd21f22c664101ac5f5b487d7 Reviewed-on: https://go-review.googlesource.com/38696 Reviewed-by: Adam Langley <agl@golang.org> Run-TryBot: Adam Langley <agl@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: fix reset{Read,Write}Thresholds for initial setup Fixes a nil pointer dereference that slipped through buildbots because it was introduced by the last two commits. Change-Id: Ib269e910956cd8b3b46e217b03fde1b61572260a Reviewed-on: https://go-review.googlesource.com/40530 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/knownhosts: a parser for the OpenSSH known_hosts file format Change-Id: I271c90ff3a6d59e2e075c785a6bdb79e4b0849fa Reviewed-on: https://go-review.googlesource.com/40354 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh/knownhosts: fix variable reuse bug in checkAddrs Consider the following code: var p *int a := []int{0, 1, 2, 3} for _, i := range a { if i == 1 { p = &i } } fmt.Println(*p) // Prints 3 This prints 3 because the variable i is the exact same variable across all iterations of the loop. When the address is taken for some specific iteration, the user's intent is to capture the value of i at that given loop, but instead the value of i in the last loop is what remains. A bug this sort occurs in the check logic since the address of the knownKey is taken, but is changed upon subsequent iterations of the loop (which happens when there are multiple lines). Change-Id: Ic626778cdcde3968dcff4fa5e7206274957dcb04 Reviewed-on: https://go-review.googlesource.com/40937 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/knownhosts: support hashed hostnames Change-Id: I855a6542a2eb2ae1d223f03892c0f19da81a4f8d Reviewed-on: https://go-review.googlesource.com/40532 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Adam Langley <agl@golang.org> ssh/knownhosts: add file + linenumber for parse errors Change-Id: Iddcb145ecd8a6b51c72ad3d77b242975baf4a5cf Reviewed-on: https://go-review.googlesource.com/41210 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Sam Whited <sam@samwhited.com> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh/terminal: implement missing functions for Solaris/OmniOS terminal.MakeRaw terminal.Restore terminal.GetState terminal.GetSize Fixes golang/go#20062 Change-Id: I9ccf194215998c5b80dbedc4f248b481f0ca57a6 Reviewed-on: https://go-review.googlesource.com/41297 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> ssh/knownhosts: add IsHostAuthority. This is a breaking change. This adds a new hostkey callback which takes the hostname field restrictions into account when validating host certificates. Prior to this, a known_hosts file with the following entry @cert-authority *.example.com ssh-rsa <example.com public key> would, when passed to knownhosts.New() generate an ssh.HostKeyCallback that would accept all host certificates signed by the example.com public key, no matter what host the client was connecting to. After this change, that known_hosts entry can only be used to validate host certificates presented when connecting to hosts under *.example.com This also renames IsAuthority to IsUserAuthority to make its intended purpose more clear. Change-Id: I7188a53fdd40a8c0bc21983105317b3498f567bb Reviewed-on: https://go-review.googlesource.com/41751 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh/knownhosts: test coverage for IsHostAuthority Change-Id: Iad24fed7cec998e02620ec0eb61658786156ba41 Reviewed-on: https://go-review.googlesource.com/42530 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> crypto/ssh: fix tests on Go 1.7 on OpenBSD and Windows Dialing the 0.0.0.0 address (as returned by net.Addr().String() for a net.Listen("tcp", ":1") address) is not yet guaranteed to work. It's currently OS-dependent. For some reason it works on Go 1.8+, but it hasn't yet been defined to work reliably. Fix the tests for now (since we need to support older Go releases), even if this might work in the future. Updates golang/go#18806 Change-Id: I2f0476b1d4f2673ab64ffedfa733f2d92fceb6ff Reviewed-on: https://go-review.googlesource.com/42496 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh: change the local copy of the ServerConfig passed to NewServerConn Otherwise callers are forced to serialize access to the ServerConfig. Change-Id: Id36f4d2877ea28b18447ef777d3839b21136c22f Reviewed-on: https://go-review.googlesource.com/42821 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> x/crypto/ssh: fix host certificate principal evaluation to check for hostname only SSH host certificates are expected to contain hostnames only, not "host:port" format. This change allows Go clients to connect to OpenSSH servers that use host certificates. Note, this change will break any clients that use ssh.NewClientConn() with an `addr` that is not in `host:port` format (they will see a "missing port in address" error). Fixes bug 20273. Change-Id: I5a306c6b7b419a737e1f0f9c5ca8c585e21a45a4 Reviewed-on: https://go-review.googlesource.com/43475 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> ssh: fixing a small typo in connection.go Change-Id: Iffbed7e16a8bb32c5ff7c393f3b6ad7dcffc69ac Reviewed-on: https://go-review.googlesource.com/44340 Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> ssh: add ParsePrivateKeysWithPassphrase ssh package doesn't provide way to parse private keys with passphrase. Fixes golang/go#18692 Change-Id: Ic139f11b6dfe7ef61690d6125e0673d50a48db16 Reviewed-on: https://go-review.googlesource.com/36079 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Han-Wen Nienhuys <hanwen@google.com> ssh: clarify intended use of Permissions. The Permissions struct should be used to pass information from authentication callback to server application. Fixes golang/go#20094. Change-Id: I5542b657d053452327260707a24925286546bfdd Reviewed-on: https://go-review.googlesource.com/45311 Run-TryBot: Han-Wen Nienhuys <hanwen@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> all: gofmt ./... Change-Id: I8ffee4dc712091e424b83a9f5a3cc2a6724abefc Reviewed-on: https://go-review.googlesource.com/46050 Reviewed-by: Matt Layher <mdlayher@gmail.com>
627 lines
17 KiB
Go
627 lines
17 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/des"
|
|
"crypto/rc4"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"io/ioutil"
|
|
)
|
|
|
|
const (
|
|
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
|
|
|
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
|
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
|
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
|
// waffles on about reasonable limits.
|
|
//
|
|
// OpenSSH caps their maxPacket at 256kB so we choose to do
|
|
// the same. maxPacket is also used to ensure that uint32
|
|
// length fields do not overflow, so it should remain well
|
|
// below 4G.
|
|
maxPacket = 256 * 1024
|
|
)
|
|
|
|
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
|
// by the transport before the first key-exchange.
|
|
type noneCipher struct{}
|
|
|
|
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
|
copy(dst, src)
|
|
}
|
|
|
|
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
|
c, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cipher.NewCTR(c, iv), nil
|
|
}
|
|
|
|
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
|
return rc4.NewCipher(key)
|
|
}
|
|
|
|
type streamCipherMode struct {
|
|
keySize int
|
|
ivSize int
|
|
skip int
|
|
createFunc func(key, iv []byte) (cipher.Stream, error)
|
|
}
|
|
|
|
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
|
if len(key) < c.keySize {
|
|
panic("ssh: key length too small for cipher")
|
|
}
|
|
if len(iv) < c.ivSize {
|
|
panic("ssh: iv too small for cipher")
|
|
}
|
|
|
|
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var streamDump []byte
|
|
if c.skip > 0 {
|
|
streamDump = make([]byte, 512)
|
|
}
|
|
|
|
for remainingToDump := c.skip; remainingToDump > 0; {
|
|
dumpThisTime := remainingToDump
|
|
if dumpThisTime > len(streamDump) {
|
|
dumpThisTime = len(streamDump)
|
|
}
|
|
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
|
remainingToDump -= dumpThisTime
|
|
}
|
|
|
|
return stream, nil
|
|
}
|
|
|
|
// cipherModes documents properties of supported ciphers. Ciphers not included
|
|
// are not supported and will not be negotiated, even if explicitly requested in
|
|
// ClientConfig.Crypto.Ciphers.
|
|
var cipherModes = map[string]*streamCipherMode{
|
|
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
|
// are defined in the order specified in the RFC.
|
|
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
|
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
|
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
|
|
|
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
|
// They are defined in the order specified in the RFC.
|
|
"arcfour128": {16, 0, 1536, newRC4},
|
|
"arcfour256": {32, 0, 1536, newRC4},
|
|
|
|
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
|
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
|
// RC4) has problems with weak keys, and should be used with caution."
|
|
// RFC4345 introduces improved versions of Arcfour.
|
|
"arcfour": {16, 0, 0, newRC4},
|
|
|
|
// AES-GCM is not a stream cipher, so it is constructed with a
|
|
// special case. If we add any more non-stream ciphers, we
|
|
// should invest a cleaner way to do this.
|
|
gcmCipherID: {16, 12, 0, nil},
|
|
|
|
// CBC mode is insecure and so is not included in the default config.
|
|
// (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely
|
|
// needed, it's possible to specify a custom Config to enable it.
|
|
// You should expect that an active attacker can recover plaintext if
|
|
// you do.
|
|
aes128cbcID: {16, aes.BlockSize, 0, nil},
|
|
|
|
// 3des-cbc is insecure and is disabled by default.
|
|
tripledescbcID: {24, des.BlockSize, 0, nil},
|
|
}
|
|
|
|
// prefixLen is the length of the packet prefix that contains the packet length
|
|
// and number of padding bytes.
|
|
const prefixLen = 5
|
|
|
|
// streamPacketCipher is a packetCipher using a stream cipher.
|
|
type streamPacketCipher struct {
|
|
mac hash.Hash
|
|
cipher cipher.Stream
|
|
etm bool
|
|
|
|
// The following members are to avoid per-packet allocations.
|
|
prefix [prefixLen]byte
|
|
seqNumBytes [4]byte
|
|
padding [2 * packetSizeMultiple]byte
|
|
packetData []byte
|
|
macResult []byte
|
|
}
|
|
|
|
// readPacket reads and decrypt a single packet from the reader argument.
|
|
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var encryptedPaddingLength [1]byte
|
|
if s.mac != nil && s.etm {
|
|
copy(encryptedPaddingLength[:], s.prefix[4:5])
|
|
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
|
} else {
|
|
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
|
}
|
|
|
|
length := binary.BigEndian.Uint32(s.prefix[0:4])
|
|
paddingLength := uint32(s.prefix[4])
|
|
|
|
var macSize uint32
|
|
if s.mac != nil {
|
|
s.mac.Reset()
|
|
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
|
s.mac.Write(s.seqNumBytes[:])
|
|
if s.etm {
|
|
s.mac.Write(s.prefix[:4])
|
|
s.mac.Write(encryptedPaddingLength[:])
|
|
} else {
|
|
s.mac.Write(s.prefix[:])
|
|
}
|
|
macSize = uint32(s.mac.Size())
|
|
}
|
|
|
|
if length <= paddingLength+1 {
|
|
return nil, errors.New("ssh: invalid packet length, packet too small")
|
|
}
|
|
|
|
if length > maxPacket {
|
|
return nil, errors.New("ssh: invalid packet length, packet too large")
|
|
}
|
|
|
|
// the maxPacket check above ensures that length-1+macSize
|
|
// does not overflow.
|
|
if uint32(cap(s.packetData)) < length-1+macSize {
|
|
s.packetData = make([]byte, length-1+macSize)
|
|
} else {
|
|
s.packetData = s.packetData[:length-1+macSize]
|
|
}
|
|
|
|
if _, err := io.ReadFull(r, s.packetData); err != nil {
|
|
return nil, err
|
|
}
|
|
mac := s.packetData[length-1:]
|
|
data := s.packetData[:length-1]
|
|
|
|
if s.mac != nil && s.etm {
|
|
s.mac.Write(data)
|
|
}
|
|
|
|
s.cipher.XORKeyStream(data, data)
|
|
|
|
if s.mac != nil {
|
|
if !s.etm {
|
|
s.mac.Write(data)
|
|
}
|
|
s.macResult = s.mac.Sum(s.macResult[:0])
|
|
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
|
|
return nil, errors.New("ssh: MAC failure")
|
|
}
|
|
}
|
|
|
|
return s.packetData[:length-paddingLength-1], nil
|
|
}
|
|
|
|
// writePacket encrypts and sends a packet of data to the writer argument
|
|
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
if len(packet) > maxPacket {
|
|
return errors.New("ssh: packet too large")
|
|
}
|
|
|
|
aadlen := 0
|
|
if s.mac != nil && s.etm {
|
|
// packet length is not encrypted for EtM modes
|
|
aadlen = 4
|
|
}
|
|
|
|
paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple
|
|
if paddingLength < 4 {
|
|
paddingLength += packetSizeMultiple
|
|
}
|
|
|
|
length := len(packet) + 1 + paddingLength
|
|
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
|
|
s.prefix[4] = byte(paddingLength)
|
|
padding := s.padding[:paddingLength]
|
|
if _, err := io.ReadFull(rand, padding); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.mac != nil {
|
|
s.mac.Reset()
|
|
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
|
s.mac.Write(s.seqNumBytes[:])
|
|
|
|
if s.etm {
|
|
// For EtM algorithms, the packet length must stay unencrypted,
|
|
// but the following data (padding length) must be encrypted
|
|
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
|
|
}
|
|
|
|
s.mac.Write(s.prefix[:])
|
|
|
|
if !s.etm {
|
|
// For non-EtM algorithms, the algorithm is applied on unencrypted data
|
|
s.mac.Write(packet)
|
|
s.mac.Write(padding)
|
|
}
|
|
}
|
|
|
|
if !(s.mac != nil && s.etm) {
|
|
// For EtM algorithms, the padding length has already been encrypted
|
|
// and the packet length must remain unencrypted
|
|
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
|
}
|
|
|
|
s.cipher.XORKeyStream(packet, packet)
|
|
s.cipher.XORKeyStream(padding, padding)
|
|
|
|
if s.mac != nil && s.etm {
|
|
// For EtM algorithms, packet and padding must be encrypted
|
|
s.mac.Write(packet)
|
|
s.mac.Write(padding)
|
|
}
|
|
|
|
if _, err := w.Write(s.prefix[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.Write(packet); err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.Write(padding); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.mac != nil {
|
|
s.macResult = s.mac.Sum(s.macResult[:0])
|
|
if _, err := w.Write(s.macResult); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type gcmCipher struct {
|
|
aead cipher.AEAD
|
|
prefix [4]byte
|
|
iv []byte
|
|
buf []byte
|
|
}
|
|
|
|
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
|
c, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aead, err := cipher.NewGCM(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &gcmCipher{
|
|
aead: aead,
|
|
iv: iv,
|
|
}, nil
|
|
}
|
|
|
|
const gcmTagSize = 16
|
|
|
|
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
// Pad out to multiple of 16 bytes. This is different from the
|
|
// stream cipher because that encrypts the length too.
|
|
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
|
|
if padding < 4 {
|
|
padding += packetSizeMultiple
|
|
}
|
|
|
|
length := uint32(len(packet) + int(padding) + 1)
|
|
binary.BigEndian.PutUint32(c.prefix[:], length)
|
|
if _, err := w.Write(c.prefix[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cap(c.buf) < int(length) {
|
|
c.buf = make([]byte, length)
|
|
} else {
|
|
c.buf = c.buf[:length]
|
|
}
|
|
|
|
c.buf[0] = padding
|
|
copy(c.buf[1:], packet)
|
|
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
|
|
return err
|
|
}
|
|
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
|
if _, err := w.Write(c.buf); err != nil {
|
|
return err
|
|
}
|
|
c.incIV()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *gcmCipher) incIV() {
|
|
for i := 4 + 7; i >= 4; i-- {
|
|
c.iv[i]++
|
|
if c.iv[i] != 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
length := binary.BigEndian.Uint32(c.prefix[:])
|
|
if length > maxPacket {
|
|
return nil, errors.New("ssh: max packet length exceeded.")
|
|
}
|
|
|
|
if cap(c.buf) < int(length+gcmTagSize) {
|
|
c.buf = make([]byte, length+gcmTagSize)
|
|
} else {
|
|
c.buf = c.buf[:length+gcmTagSize]
|
|
}
|
|
|
|
if _, err := io.ReadFull(r, c.buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.incIV()
|
|
|
|
padding := plain[0]
|
|
if padding < 4 || padding >= 20 {
|
|
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
|
}
|
|
|
|
if int(padding+1) >= len(plain) {
|
|
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
|
}
|
|
plain = plain[1 : length-uint32(padding)]
|
|
return plain, nil
|
|
}
|
|
|
|
// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
|
|
type cbcCipher struct {
|
|
mac hash.Hash
|
|
macSize uint32
|
|
decrypter cipher.BlockMode
|
|
encrypter cipher.BlockMode
|
|
|
|
// The following members are to avoid per-packet allocations.
|
|
seqNumBytes [4]byte
|
|
packetData []byte
|
|
macResult []byte
|
|
|
|
// Amount of data we should still read to hide which
|
|
// verification error triggered.
|
|
oracleCamouflage uint32
|
|
}
|
|
|
|
func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
cbc := &cbcCipher{
|
|
mac: macModes[algs.MAC].new(macKey),
|
|
decrypter: cipher.NewCBCDecrypter(c, iv),
|
|
encrypter: cipher.NewCBCEncrypter(c, iv),
|
|
packetData: make([]byte, 1024),
|
|
}
|
|
if cbc.mac != nil {
|
|
cbc.macSize = uint32(cbc.mac.Size())
|
|
}
|
|
|
|
return cbc, nil
|
|
}
|
|
|
|
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
c, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cbc, nil
|
|
}
|
|
|
|
func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
|
c, err := des.NewTripleDESCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cbc, err := newCBCCipher(c, iv, key, macKey, algs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cbc, nil
|
|
}
|
|
|
|
func maxUInt32(a, b int) uint32 {
|
|
if a > b {
|
|
return uint32(a)
|
|
}
|
|
return uint32(b)
|
|
}
|
|
|
|
const (
|
|
cbcMinPacketSizeMultiple = 8
|
|
cbcMinPacketSize = 16
|
|
cbcMinPaddingSize = 4
|
|
)
|
|
|
|
// cbcError represents a verification error that may leak information.
|
|
type cbcError string
|
|
|
|
func (e cbcError) Error() string { return string(e) }
|
|
|
|
func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
p, err := c.readPacketLeaky(seqNum, r)
|
|
if err != nil {
|
|
if _, ok := err.(cbcError); ok {
|
|
// Verification error: read a fixed amount of
|
|
// data, to make distinguishing between
|
|
// failing MAC and failing length check more
|
|
// difficult.
|
|
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
|
|
}
|
|
}
|
|
return p, err
|
|
}
|
|
|
|
func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) {
|
|
blockSize := c.decrypter.BlockSize()
|
|
|
|
// Read the header, which will include some of the subsequent data in the
|
|
// case of block ciphers - this is copied back to the payload later.
|
|
// How many bytes of payload/padding will be read with this first read.
|
|
firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize)
|
|
firstBlock := c.packetData[:firstBlockLength]
|
|
if _, err := io.ReadFull(r, firstBlock); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength
|
|
|
|
c.decrypter.CryptBlocks(firstBlock, firstBlock)
|
|
length := binary.BigEndian.Uint32(firstBlock[:4])
|
|
if length > maxPacket {
|
|
return nil, cbcError("ssh: packet too large")
|
|
}
|
|
if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
|
|
// The minimum size of a packet is 16 (or the cipher block size, whichever
|
|
// is larger) bytes.
|
|
return nil, cbcError("ssh: packet too small")
|
|
}
|
|
// The length of the packet (including the length field but not the MAC) must
|
|
// be a multiple of the block size or 8, whichever is larger.
|
|
if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
|
|
return nil, cbcError("ssh: invalid packet length multiple")
|
|
}
|
|
|
|
paddingLength := uint32(firstBlock[4])
|
|
if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
|
|
return nil, cbcError("ssh: invalid packet length")
|
|
}
|
|
|
|
// Positions within the c.packetData buffer:
|
|
macStart := 4 + length
|
|
paddingStart := macStart - paddingLength
|
|
|
|
// Entire packet size, starting before length, ending at end of mac.
|
|
entirePacketSize := macStart + c.macSize
|
|
|
|
// Ensure c.packetData is large enough for the entire packet data.
|
|
if uint32(cap(c.packetData)) < entirePacketSize {
|
|
// Still need to upsize and copy, but this should be rare at runtime, only
|
|
// on upsizing the packetData buffer.
|
|
c.packetData = make([]byte, entirePacketSize)
|
|
copy(c.packetData, firstBlock)
|
|
} else {
|
|
c.packetData = c.packetData[:entirePacketSize]
|
|
}
|
|
|
|
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
|
return nil, err
|
|
} else {
|
|
c.oracleCamouflage -= uint32(n)
|
|
}
|
|
|
|
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
|
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
|
|
|
mac := c.packetData[macStart:]
|
|
if c.mac != nil {
|
|
c.mac.Reset()
|
|
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
|
c.mac.Write(c.seqNumBytes[:])
|
|
c.mac.Write(c.packetData[:macStart])
|
|
c.macResult = c.mac.Sum(c.macResult[:0])
|
|
if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
|
|
return nil, cbcError("ssh: MAC failure")
|
|
}
|
|
}
|
|
|
|
return c.packetData[prefixLen:paddingStart], nil
|
|
}
|
|
|
|
func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
|
effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
|
|
|
|
// Length of encrypted portion of the packet (header, payload, padding).
|
|
// Enforce minimum padding and packet size.
|
|
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
|
// Enforce block size.
|
|
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
|
|
|
length := encLength - 4
|
|
paddingLength := int(length) - (1 + len(packet))
|
|
|
|
// Overall buffer contains: header, payload, padding, mac.
|
|
// Space for the MAC is reserved in the capacity but not the slice length.
|
|
bufferSize := encLength + c.macSize
|
|
if uint32(cap(c.packetData)) < bufferSize {
|
|
c.packetData = make([]byte, encLength, bufferSize)
|
|
} else {
|
|
c.packetData = c.packetData[:encLength]
|
|
}
|
|
|
|
p := c.packetData
|
|
|
|
// Packet header.
|
|
binary.BigEndian.PutUint32(p, length)
|
|
p = p[4:]
|
|
p[0] = byte(paddingLength)
|
|
|
|
// Payload.
|
|
p = p[1:]
|
|
copy(p, packet)
|
|
|
|
// Padding.
|
|
p = p[len(packet):]
|
|
if _, err := io.ReadFull(rand, p); err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.mac != nil {
|
|
c.mac.Reset()
|
|
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
|
c.mac.Write(c.seqNumBytes[:])
|
|
c.mac.Write(c.packetData)
|
|
// The MAC is now appended into the capacity reserved for it earlier.
|
|
c.packetData = c.mac.Sum(c.packetData)
|
|
}
|
|
|
|
c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
|
|
|
|
if _, err := w.Write(c.packetData); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|