introduce pickupstream tools
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>
This commit is contained in:
parent
2392a6b13f
commit
76f411272b
46 changed files with 3316 additions and 786 deletions
1
.golangssh_lastpick
Normal file
1
.golangssh_lastpick
Normal file
|
|
@ -0,0 +1 @@
|
|||
abc3c1b02083efdbef727c8631a865ad7d8d8b19
|
||||
12
pick_golangssh.sh
Executable file
12
pick_golangssh.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VER_FILE=".golangssh_lastpick"
|
||||
LAST_VER=`cat $VER_FILE`
|
||||
|
||||
git fetch https://github.com/golang/crypto.git
|
||||
|
||||
for v in `git log --reverse --format=%H $LAST_VER..FETCH_HEAD ssh`; do
|
||||
git cherry-pick $v
|
||||
echo $v > $VER_FILE
|
||||
done
|
||||
|
|
@ -182,7 +182,10 @@ func TestCert(t *testing.T) {
|
|||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
listener, err = net.Listen("tcp", "[::1]:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
|
|
@ -200,6 +203,9 @@ func netPipe() (net.Conn, net.Conn, error) {
|
|||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
|
|
@ -208,9 +214,6 @@ func TestAuth(t *testing.T) {
|
|||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
|
|
@ -233,7 +236,9 @@ func TestAuth(t *testing.T) {
|
|||
conn.Close()
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conf := ssh.ClientConfig{
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
|
||||
conn, _, _, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@ package agent_test
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func ExampleClientAgent() {
|
||||
// ssh-agent has a UNIX socket under $SSH_AUTH_SOCK
|
||||
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
log.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
log.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
agentClient := agent.NewClient(conn)
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
|
|
@ -29,6 +29,7 @@ func ExampleClientAgent() {
|
|||
// wants it.
|
||||
ssh.PublicKeysCallback(agentClient.Signers),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
sshc, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ func TestSetupForwardAgent(t *testing.T) {
|
|||
incoming <- conn
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conf := ssh.ClientConfig{
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
|
|
|
|||
36
ssh/certs.go
36
ssh/certs.go
|
|
@ -251,10 +251,18 @@ type CertChecker struct {
|
|||
// for user certificates.
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// IsAuthority should return true if the key is recognized as
|
||||
// an authority. This allows for certificates to be signed by other
|
||||
// certificates.
|
||||
IsAuthority func(auth PublicKey) bool
|
||||
// IsUserAuthority should return true if the key is recognized as an
|
||||
// authority for the given user certificate. This allows for
|
||||
// certificates to be signed by other certificates. This must be set
|
||||
// if this CertChecker will be checking user certificates.
|
||||
IsUserAuthority func(auth PublicKey) bool
|
||||
|
||||
// IsHostAuthority should report whether the key is recognized as
|
||||
// an authority for this host. This allows for certificates to be
|
||||
// signed by other keys, and for those other keys to only be valid
|
||||
// signers for particular hostnames. This must be set if this
|
||||
// CertChecker will be checking host certificates.
|
||||
IsHostAuthority func(auth PublicKey, address string) bool
|
||||
|
||||
// Clock is used for verifying time stamps. If nil, time.Now
|
||||
// is used.
|
||||
|
|
@ -268,7 +276,7 @@ type CertChecker struct {
|
|||
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
||||
// public key that is not a certificate. It must implement host key
|
||||
// validation or else, if nil, all such keys are rejected.
|
||||
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error
|
||||
HostKeyFallback HostKeyCallback
|
||||
|
||||
// IsRevoked is called for each certificate so that revocation checking
|
||||
// can be implemented. It should return true if the given certificate
|
||||
|
|
@ -290,8 +298,17 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
|
|||
if cert.CertType != HostCert {
|
||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||
}
|
||||
if !c.IsHostAuthority(cert.SignatureKey, addr) {
|
||||
return fmt.Errorf("ssh: no authorities for hostname: %v", addr)
|
||||
}
|
||||
|
||||
return c.CheckCert(addr, cert)
|
||||
hostname, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pass hostname only as principal for host certificates (consistent with OpenSSH)
|
||||
return c.CheckCert(hostname, cert)
|
||||
}
|
||||
|
||||
// Authenticate checks a user certificate. Authenticate can be used as
|
||||
|
|
@ -308,6 +325,9 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
|
|||
if cert.CertType != UserCert {
|
||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||
}
|
||||
if !c.IsUserAuthority(cert.SignatureKey) {
|
||||
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -356,10 +376,6 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
|||
}
|
||||
}
|
||||
|
||||
if !c.IsAuthority(cert.SignatureKey) {
|
||||
return fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
clock := c.Clock
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func TestValidateCert(t *testing.T) {
|
|||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
checker := CertChecker{}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
checker.IsUserAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ func TestValidateCertTime(t *testing.T) {
|
|||
checker := CertChecker{
|
||||
Clock: func() time.Time { return time.Unix(ts, 0) },
|
||||
}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
checker.IsUserAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(),
|
||||
testPublicKeys["ecdsa"].Marshal())
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ func TestValidateCertTime(t *testing.T) {
|
|||
|
||||
func TestHostKeyCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
ValidPrincipals: []string{"hostname", "hostname.domain"},
|
||||
ValidPrincipals: []string{"hostname", "hostname.domain", "otherhost"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: HostCert,
|
||||
|
|
@ -168,8 +168,8 @@ func TestHostKeyCert(t *testing.T) {
|
|||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
checker := &CertChecker{
|
||||
IsAuthority: func(p PublicKey) bool {
|
||||
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||
IsHostAuthority: func(p PublicKey, addr string) bool {
|
||||
return addr == "hostname:22" && bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,14 @@ func TestHostKeyCert(t *testing.T) {
|
|||
t.Errorf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
for _, name := range []string{"hostname", "otherhost"} {
|
||||
for _, test := range []struct {
|
||||
addr string
|
||||
succeed bool
|
||||
}{
|
||||
{addr: "hostname:22", succeed: true},
|
||||
{addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22'
|
||||
{addr: "lasthost:22", succeed: false},
|
||||
} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
|
|
@ -201,16 +208,15 @@ func TestHostKeyCert(t *testing.T) {
|
|||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, name, config)
|
||||
_, _, _, err = NewClientConn(c2, test.addr, config)
|
||||
|
||||
succeed := name == "hostname"
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewClientConn(%q): %v", name, err)
|
||||
if (err == nil) != test.succeed {
|
||||
t.Fatalf("NewClientConn(%q): %v", test.addr, err)
|
||||
}
|
||||
|
||||
err = <-errc
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewServerConn(%q): %v", name, err)
|
||||
if (err == nil) != test.succeed {
|
||||
t.Fatalf("NewServerConn(%q): %v", test.addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,8 +461,8 @@ func (m *mux) newChannel(chanType string, direction channelDirection, extraData
|
|||
pending: newBuffer(),
|
||||
extPending: newBuffer(),
|
||||
direction: direction,
|
||||
incomingRequests: make(chan *Request, 16),
|
||||
msg: make(chan interface{}, 16),
|
||||
incomingRequests: make(chan *Request, chanSize),
|
||||
msg: make(chan interface{}, chanSize),
|
||||
chanType: chanType,
|
||||
extraData: extraData,
|
||||
mux: m,
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ const prefixLen = 5
|
|||
type streamPacketCipher struct {
|
||||
mac hash.Hash
|
||||
cipher cipher.Stream
|
||||
etm bool
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
prefix [prefixLen]byte
|
||||
|
|
@ -150,7 +151,14 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
|
|||
return nil, err
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
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])
|
||||
|
||||
|
|
@ -159,7 +167,12 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
|
|||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
s.mac.Write(s.prefix[:])
|
||||
if s.etm {
|
||||
s.mac.Write(s.prefix[:4])
|
||||
s.mac.Write(encryptedPaddingLength[:])
|
||||
} else {
|
||||
s.mac.Write(s.prefix[:])
|
||||
}
|
||||
macSize = uint32(s.mac.Size())
|
||||
}
|
||||
|
||||
|
|
@ -184,10 +197,17 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, 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 {
|
||||
s.mac.Write(data)
|
||||
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")
|
||||
|
|
@ -203,7 +223,13 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
|
|||
return errors.New("ssh: packet too large")
|
||||
}
|
||||
|
||||
paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
|
||||
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
|
||||
}
|
||||
|
|
@ -220,15 +246,37 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
|
|||
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)
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
s.cipher.XORKeyStream(packet, packet)
|
||||
s.cipher.XORKeyStream(padding, padding)
|
||||
|
||||
if _, err := w.Write(s.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,39 +26,41 @@ func TestPacketCiphers(t *testing.T) {
|
|||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
for cipher := range cipherModes {
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: cipher,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
for mac := range macModes {
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: cipher,
|
||||
MAC: mac,
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket(%q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
packet, err := server.readPacket(0, buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
packet, err := server.readPacket(0, buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPacket(%q, %q): %v", cipher, mac, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(packet) != want {
|
||||
t.Errorf("roundtrip(%q): got %q, want %q", cipher, packet, want)
|
||||
if string(packet) != want {
|
||||
t.Errorf("roundtrip(%q, %q): got %q, want %q", cipher, mac, packet, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -13,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, port forwarding and tunneled dialing.
|
||||
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
|||
return nil
|
||||
}
|
||||
|
||||
ch = make(chan NewChannel, 16)
|
||||
ch = make(chan NewChannel, chanSize)
|
||||
c.channelHandlers[channelType] = ch
|
||||
return ch
|
||||
}
|
||||
|
|
@ -59,6 +60,7 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
|
||||
return conn
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +70,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
|||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
if fullConf.HostKeyCallback == nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
|
||||
}
|
||||
|
||||
conn := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
|
@ -97,13 +104,11 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
|
|||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||
if err := c.transport.requestInitialKeyChange(); err != nil {
|
||||
if err := c.transport.waitSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We just did the key change, so the session ID is established.
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
|
||||
return c.clientAuthenticate(config)
|
||||
}
|
||||
|
||||
|
|
@ -175,6 +180,13 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
|||
return NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
// HostKeyCallback is the function type used for verifying server
|
||||
// keys. A HostKeyCallback must return nil if the host key is OK, or
|
||||
// an error to reject it. It receives the hostname as passed to Dial
|
||||
// or NewClientConn. The remote address is the RemoteAddr of the
|
||||
// net.Conn underlying the the SSH connection.
|
||||
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// A ClientConfig structure is used to configure a Client. It must not be
|
||||
// modified after having been passed to an SSH function.
|
||||
type ClientConfig struct {
|
||||
|
|
@ -190,10 +202,12 @@ type ClientConfig struct {
|
|||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// HostKeyCallback, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyCallback
|
||||
// implies that all host keys are accepted.
|
||||
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
// HostKeyCallback is called during the cryptographic
|
||||
// handshake to validate the server's host key. The client
|
||||
// configuration must supply this callback for the connection
|
||||
// to succeed. The functions InsecureIgnoreHostKey or
|
||||
// FixedHostKey can be used for simplistic host key checks.
|
||||
HostKeyCallback HostKeyCallback
|
||||
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
|
|
@ -211,3 +225,33 @@ type ClientConfig struct {
|
|||
// A Timeout of zero means no timeout.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// InsecureIgnoreHostKey returns a function that can be used for
|
||||
// ClientConfig.HostKeyCallback to accept any host key. It should
|
||||
// not be used for production code.
|
||||
func InsecureIgnoreHostKey() HostKeyCallback {
|
||||
return func(hostname string, remote net.Addr, key PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type fixedHostKey struct {
|
||||
key PublicKey
|
||||
}
|
||||
|
||||
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
|
||||
if f.key == nil {
|
||||
return fmt.Errorf("ssh: required host key was nil")
|
||||
}
|
||||
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
|
||||
return fmt.Errorf("ssh: host key mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FixedHostKey returns a function for use in
|
||||
// ClientConfig.HostKeyCallback to accept only a specific host key.
|
||||
func FixedHostKey(key PublicKey) HostKeyCallback {
|
||||
hk := &fixedHostKey{key}
|
||||
return hk.check
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
|||
// then any untried methods suggested by the server.
|
||||
tried := make(map[string]bool)
|
||||
var lastMethods []string
|
||||
|
||||
sessionID := c.transport.getSessionID()
|
||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||
ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand)
|
||||
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -177,31 +179,26 @@ func (cb publicKeyCallback) method() string {
|
|||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
// Authentication is performed in two stages. The first stage sends an
|
||||
// enquiry to test if each key is acceptable to the remote. The second
|
||||
// stage attempts to authenticate with the valid keys obtained in the
|
||||
// first stage.
|
||||
// Authentication is performed by sending an enquiry to test if a key is
|
||||
// acceptable to the remote. If the key is acceptable, the client will
|
||||
// attempt to authenticate with the valid key. If not the client will repeat
|
||||
// the process with the remaining keys.
|
||||
|
||||
signers, err := cb()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var validKeys []Signer
|
||||
for _, signer := range signers {
|
||||
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
|
||||
validKeys = append(validKeys, signer)
|
||||
} else {
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// methods that may continue if this auth is not successful.
|
||||
var methods []string
|
||||
for _, signer := range validKeys {
|
||||
pub := signer.PublicKey()
|
||||
for _, signer := range signers {
|
||||
ok, err := validateKey(signer.PublicKey(), user, c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pub := signer.PublicKey()
|
||||
pubKey := pub.Marshal()
|
||||
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||
User: user,
|
||||
|
|
@ -234,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
|
|||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if success {
|
||||
|
||||
// If authentication succeeds or the list of available methods does not
|
||||
// contain the "publickey" method, do not attempt to authenticate with any
|
||||
// other keys. According to RFC 4252 Section 7, the latter can occur when
|
||||
// additional authentication methods are required.
|
||||
if success || !containsMethod(methods, cb.method()) {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, methods, nil
|
||||
}
|
||||
|
||||
func containsMethod(methods []string, method string) bool {
|
||||
for _, m := range methods {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validateKey validates the key provided is acceptable to the server.
|
||||
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func tryAuth(t *testing.T, config *ClientConfig) error {
|
|||
defer c2.Close()
|
||||
|
||||
certChecker := CertChecker{
|
||||
IsAuthority: func(k PublicKey) bool {
|
||||
IsUserAuthority: func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
|
||||
},
|
||||
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
|
|
@ -76,9 +76,6 @@ func tryAuth(t *testing.T, config *ClientConfig) error {
|
|||
}
|
||||
return nil, errors.New("keyboard-interactive failed")
|
||||
},
|
||||
AuthLogCallback: func(conn ConnMetadata, method string, err error) {
|
||||
t.Logf("user %q, method %q: %v", conn.User(), method, err)
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
|
|
@ -93,6 +90,7 @@ func TestClientAuthPublicKey(t *testing.T) {
|
|||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
|
|
@ -105,6 +103,7 @@ func TestAuthMethodPassword(t *testing.T) {
|
|||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
|
|
@ -124,6 +123,7 @@ func TestAuthMethodFallback(t *testing.T) {
|
|||
return "WRONG", nil
|
||||
}),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
|
|
@ -142,6 +142,7 @@ func TestAuthMethodWrongPassword(t *testing.T) {
|
|||
Password("wrong"),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
|
|
@ -159,6 +160,7 @@ func TestAuthMethodKeyboardInteractive(t *testing.T) {
|
|||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
|
|
@ -204,6 +206,7 @@ func TestAuthMethodRSAandDSA(t *testing.T) {
|
|||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"], testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with rsa key: %v", err)
|
||||
|
|
@ -220,6 +223,7 @@ func TestClientHMAC(t *testing.T) {
|
|||
Config: Config{
|
||||
MACs: []string{mac},
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
|
||||
|
|
@ -255,6 +259,7 @@ func TestClientUnsupportedKex(t *testing.T) {
|
|||
Config: Config{
|
||||
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
|
||||
t.Errorf("got %v, expected 'common algorithm'", err)
|
||||
|
|
@ -274,22 +279,23 @@ func TestClientLoginCert(t *testing.T) {
|
|||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
User: "user",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
|
||||
|
||||
t.Log("should succeed")
|
||||
// should succeed
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("corrupted signature")
|
||||
// corrupted signature
|
||||
cert.Signature.Blob[0]++
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with corrupted sig")
|
||||
}
|
||||
|
||||
t.Log("revoked")
|
||||
// revoked
|
||||
cert.Serial = 666
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
|
|
@ -297,13 +303,13 @@ func TestClientLoginCert(t *testing.T) {
|
|||
}
|
||||
cert.Serial = 1
|
||||
|
||||
t.Log("sign with wrong key")
|
||||
// sign with wrong key
|
||||
cert.SignCert(rand.Reader, testSigners["dsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with non-authoritative key")
|
||||
}
|
||||
|
||||
t.Log("host cert")
|
||||
// host cert
|
||||
cert.CertType = HostCert
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
|
|
@ -311,14 +317,14 @@ func TestClientLoginCert(t *testing.T) {
|
|||
}
|
||||
cert.CertType = UserCert
|
||||
|
||||
t.Log("principal specified")
|
||||
// principal specified
|
||||
cert.ValidPrincipals = []string{"user"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("wrong principal specified")
|
||||
// wrong principal specified
|
||||
cert.ValidPrincipals = []string{"fred"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
|
|
@ -326,22 +332,22 @@ func TestClientLoginCert(t *testing.T) {
|
|||
}
|
||||
cert.ValidPrincipals = nil
|
||||
|
||||
t.Log("added critical option")
|
||||
// added critical option
|
||||
cert.CriticalOptions = map[string]string{"root-access": "yes"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with unrecognized critical option")
|
||||
}
|
||||
|
||||
t.Log("allowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"}
|
||||
// allowed source address
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login with source-address failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("disallowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"}
|
||||
// disallowed source address
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::42"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login with source-address succeeded")
|
||||
|
|
@ -364,6 +370,7 @@ func testPermissionsPassing(withPermissions bool, t *testing.T) {
|
|||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if withPermissions {
|
||||
clientConfig.User = "permissions"
|
||||
|
|
@ -410,6 +417,7 @@ func TestRetryableAuth(t *testing.T) {
|
|||
}), 2),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
|
|
@ -431,7 +439,8 @@ func ExampleRetryableAuthMethod(t *testing.T) {
|
|||
}
|
||||
|
||||
config := &ClientConfig{
|
||||
User: user,
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
User: user,
|
||||
Auth: []AuthMethod{
|
||||
RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
|
||||
},
|
||||
|
|
@ -451,7 +460,8 @@ func TestClientAuthNone(t *testing.T) {
|
|||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: user,
|
||||
User: user,
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
|
|
@ -464,9 +474,106 @@ func TestClientAuthNone(t *testing.T) {
|
|||
go NewClientConn(c2, "", clientConfig)
|
||||
serverConn, err := newServer(c1, serverConfig)
|
||||
if err != nil {
|
||||
t.Fatal("newServer: %v", err)
|
||||
t.Fatalf("newServer: %v", err)
|
||||
}
|
||||
if serverConn.User() != user {
|
||||
t.Fatalf("server: got %q, want %q", serverConn.User(), user)
|
||||
}
|
||||
}
|
||||
|
||||
// Test if authentication attempts are limited on server when MaxAuthTries is set
|
||||
func TestClientAuthMaxAuthTries(t *testing.T) {
|
||||
user := "testuser"
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
MaxAuthTries: 2,
|
||||
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && string(pass) == "right" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("password auth failed")
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
})
|
||||
|
||||
for tries := 2; tries < 4; tries++ {
|
||||
n := tries
|
||||
clientConfig := &ClientConfig{
|
||||
User: user,
|
||||
Auth: []AuthMethod{
|
||||
RetryableAuthMethod(PasswordCallback(func() (string, error) {
|
||||
n--
|
||||
if n == 0 {
|
||||
return "right", nil
|
||||
} else {
|
||||
return "wrong", nil
|
||||
}
|
||||
}), tries),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go newServer(c1, serverConfig)
|
||||
_, _, _, err = NewClientConn(c2, "", clientConfig)
|
||||
if tries > 2 {
|
||||
if err == nil {
|
||||
t.Fatalf("client: got no error, want %s", expectedErr)
|
||||
} else if err.Error() != expectedErr.Error() {
|
||||
t.Fatalf("client: got %s, want %s", err, expectedErr)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("client: got %s, want no error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test if authentication attempts are correctly limited on server
|
||||
// when more public keys are provided then MaxAuthTries
|
||||
func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
|
||||
signers := []Signer{}
|
||||
for i := 0; i < 6; i++ {
|
||||
signers = append(signers, testSigners["dsa"])
|
||||
}
|
||||
|
||||
validConfig := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, validConfig); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
|
||||
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
})
|
||||
invalidConfig := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(append(signers, testSigners["rsa"])...),
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
if err := tryAuth(t, invalidConfig); err == nil {
|
||||
t.Fatalf("client: got no error, want %s", expectedErr)
|
||||
} else if err.Error() != expectedErr.Error() {
|
||||
t.Fatalf("client: got %s, want %s", err, expectedErr)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package ssh
|
|||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
|
|||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
receivedVersion := make(chan string, 1)
|
||||
config.HostKeyCallback = InsecureIgnoreHostKey()
|
||||
go func() {
|
||||
version, err := readVersion(serverConn)
|
||||
if err != nil {
|
||||
|
|
@ -37,3 +39,43 @@ func TestCustomClientVersion(t *testing.T) {
|
|||
func TestDefaultClientVersion(t *testing.T) {
|
||||
testClientVersion(t, &ClientConfig{}, packageVersion)
|
||||
}
|
||||
|
||||
func TestHostKeyCheck(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
wantError string
|
||||
key PublicKey
|
||||
}{
|
||||
{"no callback", "must specify HostKeyCallback", nil},
|
||||
{"correct key", "", testSigners["rsa"].PublicKey()},
|
||||
{"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
|
||||
} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
serverConf := &ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
|
||||
go NewServerConn(c1, serverConf)
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
if tt.key != nil {
|
||||
clientConf.HostKeyCallback = FixedHostKey(tt.key)
|
||||
}
|
||||
|
||||
_, _, _, err = NewClientConn(c2, "", &clientConf)
|
||||
if err != nil {
|
||||
if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
|
||||
t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
|
||||
}
|
||||
} else if tt.wantError != "" {
|
||||
t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
|
|
@ -40,7 +41,7 @@ var supportedKexAlgos = []string{
|
|||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
|
|
@ -56,7 +57,7 @@ var supportedHostKeyAlgos = []string{
|
|||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||
// because they have reached the end of their useful life.
|
||||
var supportedMACs = []string{
|
||||
"hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
|
@ -104,6 +105,21 @@ type directionAlgorithms struct {
|
|||
Compression string
|
||||
}
|
||||
|
||||
// rekeyBytes returns a rekeying intervals in bytes.
|
||||
func (a *directionAlgorithms) rekeyBytes() int64 {
|
||||
// According to RFC4344 block ciphers should rekey after
|
||||
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
|
||||
// 128.
|
||||
switch a.Cipher {
|
||||
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID:
|
||||
return 16 * (1 << 32)
|
||||
|
||||
}
|
||||
|
||||
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
|
||||
return 1 << 30
|
||||
}
|
||||
|
||||
type algorithms struct {
|
||||
kex string
|
||||
hostKey string
|
||||
|
|
@ -171,7 +187,7 @@ type Config struct {
|
|||
|
||||
// The maximum number of bytes sent or received after which a
|
||||
// new key is negotiated. It must be at least 256. If
|
||||
// unspecified, 1 gigabyte is used.
|
||||
// unspecified, a size suitable for the chosen cipher is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
|
|
@ -215,11 +231,12 @@ func (c *Config) SetDefaults() {
|
|||
}
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// RFC 4253, section 9 suggests rekeying after 1G.
|
||||
c.RekeyThreshold = 1 << 30
|
||||
}
|
||||
if c.RekeyThreshold < minRekeyThreshold {
|
||||
// cipher specific default
|
||||
} else if c.RekeyThreshold < minRekeyThreshold {
|
||||
c.RekeyThreshold = minRekeyThreshold
|
||||
} else if c.RekeyThreshold >= math.MaxInt64 {
|
||||
// Avoid weirdness if somebody uses -1 as a threshold.
|
||||
c.RekeyThreshold = math.MaxInt64
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type ConnMetadata interface {
|
|||
// User returns the user ID for this connection.
|
||||
User() string
|
||||
|
||||
// SessionID returns the sesson hash, also denoted by H.
|
||||
// SessionID returns the session hash, also denoted by H.
|
||||
SessionID() []byte
|
||||
|
||||
// ClientVersion returns the client's version string as hashed
|
||||
|
|
|
|||
|
|
@ -19,5 +19,8 @@ others.
|
|||
References:
|
||||
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
|
||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
||||
|
||||
This package does not fall under the stability promise of the Go language itself,
|
||||
so its API may be changed when pressing needs arise.
|
||||
*/
|
||||
package ssh
|
||||
|
|
|
|||
|
|
@ -5,21 +5,45 @@
|
|||
package ssh_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func ExampleNewServerConn() {
|
||||
// Public key authentication is done by comparing
|
||||
// the public key of a received connection
|
||||
// with the entries in the authorized_keys file.
|
||||
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load authorized_keys, err: %v", err)
|
||||
}
|
||||
|
||||
authorizedKeysMap := map[string]bool{}
|
||||
for len(authorizedKeysBytes) > 0 {
|
||||
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
authorizedKeysMap[string(pubKey.Marshal())] = true
|
||||
authorizedKeysBytes = rest
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
// Remove to disable password auth.
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
|
|
@ -28,16 +52,29 @@ func ExampleNewServerConn() {
|
|||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
|
||||
// Remove to disable public key auth.
|
||||
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if authorizedKeysMap[string(pubKey.Marshal())] {
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
Extensions: map[string]string{
|
||||
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown public key for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
panic("Failed to load private key")
|
||||
log.Fatal("Failed to load private key: ", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
panic("Failed to parse private key")
|
||||
log.Fatal("Failed to parse private key: ", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
|
@ -46,19 +83,21 @@ func ExampleNewServerConn() {
|
|||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
panic("failed to listen for connection")
|
||||
log.Fatal("failed to listen for connection: ", err)
|
||||
}
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
panic("failed to accept incoming connection")
|
||||
log.Fatal("failed to accept incoming connection: ", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
panic("failed to handshake")
|
||||
log.Fatal("failed to handshake: ", err)
|
||||
}
|
||||
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
|
|
@ -74,7 +113,7 @@ func ExampleNewServerConn() {
|
|||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
panic("could not accept channel.")
|
||||
log.Fatalf("Could not accept channel: %v", err)
|
||||
}
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
|
|
@ -82,18 +121,7 @@ func ExampleNewServerConn() {
|
|||
// "shell" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "shell":
|
||||
ok = true
|
||||
if len(req.Payload) > 0 {
|
||||
// We don't accept any
|
||||
// commands, only the
|
||||
// default shell.
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
req.Reply(ok, nil)
|
||||
req.Reply(req.Type == "shell", nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
|
|
@ -112,27 +140,70 @@ func ExampleNewServerConn() {
|
|||
}
|
||||
}
|
||||
|
||||
func ExampleHostKeyCheck() {
|
||||
// Every client must provide a host key check. Here is a
|
||||
// simple-minded parse of OpenSSH's known_hosts file
|
||||
host := "hostname"
|
||||
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
var hostKey ssh.PublicKey
|
||||
for scanner.Scan() {
|
||||
fields := strings.Split(scanner.Text(), " ")
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(fields[0], host) {
|
||||
var err error
|
||||
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing %q: %v", fields[2], err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hostKey == nil {
|
||||
log.Fatalf("no hostkey for %s", host)
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: os.Getenv("USER"),
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
|
||||
_, err = ssh.Dial("tcp", host+":22", &config)
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
func ExampleDial() {
|
||||
var hostKey ssh.PublicKey
|
||||
// An SSH client is represented with a ClientConn.
|
||||
//
|
||||
// To authenticate with the remote server you must pass at least one
|
||||
// implementation of AuthMethod via the Auth field in ClientConfig.
|
||||
// implementation of AuthMethod via the Auth field in ClientConfig,
|
||||
// and provide a HostKeyCallback.
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("yourpassword"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
|
||||
if err != nil {
|
||||
panic("Failed to dial: " + err.Error())
|
||||
log.Fatal("Failed to dial: ", err)
|
||||
}
|
||||
|
||||
// Each ClientConn can support multiple interactive sessions,
|
||||
// represented by a Session.
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
panic("Failed to create session: " + err.Error())
|
||||
log.Fatal("Failed to create session: ", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
|
|
@ -141,12 +212,13 @@ func ExampleDial() {
|
|||
var b bytes.Buffer
|
||||
session.Stdout = &b
|
||||
if err := session.Run("/usr/bin/whoami"); err != nil {
|
||||
panic("Failed to run: " + err.Error())
|
||||
log.Fatal("Failed to run: " + err.Error())
|
||||
}
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
|
||||
func ExamplePublicKeys() {
|
||||
var hostKey ssh.PublicKey
|
||||
// A public key may be used to authenticate against the remote
|
||||
// server by using an unencrypted PEM-encoded private key file.
|
||||
//
|
||||
|
|
@ -169,6 +241,7 @@ func ExamplePublicKeys() {
|
|||
// Use the PublicKeys method for remote authentication.
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
|
||||
// Connect to the remote server and perform the SSH handshake.
|
||||
|
|
@ -180,23 +253,25 @@ func ExamplePublicKeys() {
|
|||
}
|
||||
|
||||
func ExampleClient_Listen() {
|
||||
var hostKey ssh.PublicKey
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
// Dial your ssh server.
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect: %s", err)
|
||||
log.Fatal("unable to connect: ", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Request the remote side to open port 8080 on all interfaces.
|
||||
l, err := conn.Listen("tcp", "0.0.0.0:8080")
|
||||
if err != nil {
|
||||
log.Fatalf("unable to register tcp forward: %v", err)
|
||||
log.Fatal("unable to register tcp forward: ", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
|
|
@ -207,23 +282,25 @@ func ExampleClient_Listen() {
|
|||
}
|
||||
|
||||
func ExampleSession_RequestPty() {
|
||||
var hostKey ssh.PublicKey
|
||||
// Create client config
|
||||
config := &ssh.ClientConfig{
|
||||
User: "username",
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password("password"),
|
||||
},
|
||||
HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
}
|
||||
// Connect to ssh server
|
||||
conn, err := ssh.Dial("tcp", "localhost:22", config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect: %s", err)
|
||||
log.Fatal("unable to connect: ", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Create a session
|
||||
session, err := conn.NewSession()
|
||||
if err != nil {
|
||||
log.Fatalf("unable to create session: %s", err)
|
||||
log.Fatal("unable to create session: ", err)
|
||||
}
|
||||
defer session.Close()
|
||||
// Set up terminal modes
|
||||
|
|
@ -233,11 +310,11 @@ func ExampleSession_RequestPty() {
|
|||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
// Request pseudo terminal
|
||||
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
|
||||
log.Fatalf("request for pseudo terminal failed: %s", err)
|
||||
if err := session.RequestPty("xterm", 40, 80, modes); err != nil {
|
||||
log.Fatal("request for pseudo terminal failed: ", err)
|
||||
}
|
||||
// Start remote shell
|
||||
if err := session.Shell(); err != nil {
|
||||
log.Fatalf("failed to start shell: %s", err)
|
||||
log.Fatal("failed to start shell: ", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
462
ssh/handshake.go
462
ssh/handshake.go
|
|
@ -19,6 +19,11 @@ import (
|
|||
// messages are wrong when using ECDH.
|
||||
const debugHandshake = false
|
||||
|
||||
// chanSize sets the amount of buffering SSH connections. This is
|
||||
// primarily for testing: setting chanSize=0 uncovers deadlocks more
|
||||
// quickly.
|
||||
const chanSize = 16
|
||||
|
||||
// keyingTransport is a packet based transport that supports key
|
||||
// changes. It need not be thread-safe. It should pass through
|
||||
// msgNewKeys in both directions.
|
||||
|
|
@ -53,34 +58,60 @@ type handshakeTransport struct {
|
|||
incoming chan []byte
|
||||
readError error
|
||||
|
||||
mu sync.Mutex
|
||||
writeError error
|
||||
sentInitPacket []byte
|
||||
sentInitMsg *kexInitMsg
|
||||
pendingPackets [][]byte // Used when a key exchange is in progress.
|
||||
|
||||
// If the read loop wants to schedule a kex, it pings this
|
||||
// channel, and the write loop will send out a kex
|
||||
// message.
|
||||
requestKex chan struct{}
|
||||
|
||||
// If the other side requests or confirms a kex, its kexInit
|
||||
// packet is sent here for the write loop to find it.
|
||||
startKex chan *pendingKex
|
||||
|
||||
// data for host key checking
|
||||
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
hostKeyCallback HostKeyCallback
|
||||
dialAddress string
|
||||
remoteAddr net.Addr
|
||||
|
||||
readSinceKex uint64
|
||||
// Algorithms agreed in the last key exchange.
|
||||
algorithms *algorithms
|
||||
|
||||
// Protects the writing side of the connection
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
sentInitPacket []byte
|
||||
sentInitMsg *kexInitMsg
|
||||
writtenSinceKex uint64
|
||||
writeError error
|
||||
readPacketsLeft uint32
|
||||
readBytesLeft int64
|
||||
|
||||
writePacketsLeft uint32
|
||||
writeBytesLeft int64
|
||||
|
||||
// The session ID or nil if first kex did not complete yet.
|
||||
sessionID []byte
|
||||
}
|
||||
|
||||
type pendingKex struct {
|
||||
otherInit []byte
|
||||
done chan error
|
||||
}
|
||||
|
||||
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
|
||||
t := &handshakeTransport{
|
||||
conn: conn,
|
||||
serverVersion: serverVersion,
|
||||
clientVersion: clientVersion,
|
||||
incoming: make(chan []byte, 16),
|
||||
config: config,
|
||||
incoming: make(chan []byte, chanSize),
|
||||
requestKex: make(chan struct{}, 1),
|
||||
startKex: make(chan *pendingKex, 1),
|
||||
|
||||
config: config,
|
||||
}
|
||||
t.cond = sync.NewCond(&t.mu)
|
||||
t.resetReadThresholds()
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// We always start with a mandatory key exchange.
|
||||
t.requestKex <- struct{}{}
|
||||
return t
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +126,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
|
|||
t.hostKeyAlgorithms = supportedHostKeyAlgos
|
||||
}
|
||||
go t.readLoop()
|
||||
go t.kexLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +134,7 @@ func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byt
|
|||
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
|
||||
t.hostKeys = config.hostKeys
|
||||
go t.readLoop()
|
||||
go t.kexLoop()
|
||||
return t
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +142,20 @@ func (t *handshakeTransport) getSessionID() []byte {
|
|||
return t.sessionID
|
||||
}
|
||||
|
||||
// waitSession waits for the session to be established. This should be
|
||||
// the first thing to call after instantiating handshakeTransport.
|
||||
func (t *handshakeTransport) waitSession() error {
|
||||
p, err := t.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p[0] != msgNewKeys {
|
||||
return fmt.Errorf("ssh: first packet should be msgNewKeys")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) id() string {
|
||||
if len(t.hostKeys) > 0 {
|
||||
return "server"
|
||||
|
|
@ -116,6 +163,20 @@ func (t *handshakeTransport) id() string {
|
|||
return "client"
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) printPacket(p []byte, write bool) {
|
||||
action := "got"
|
||||
if write {
|
||||
action = "sent"
|
||||
}
|
||||
|
||||
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
|
||||
log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p))
|
||||
} else {
|
||||
msg, err := decode(p)
|
||||
log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readPacket() ([]byte, error) {
|
||||
p, ok := <-t.incoming
|
||||
if !ok {
|
||||
|
|
@ -125,8 +186,10 @@ func (t *handshakeTransport) readPacket() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (t *handshakeTransport) readLoop() {
|
||||
first := true
|
||||
for {
|
||||
p, err := t.readOnePacket()
|
||||
p, err := t.readOnePacket(first)
|
||||
first = false
|
||||
if err != nil {
|
||||
t.readError = err
|
||||
close(t.incoming)
|
||||
|
|
@ -138,67 +201,217 @@ func (t *handshakeTransport) readLoop() {
|
|||
t.incoming <- p
|
||||
}
|
||||
|
||||
// If we can't read, declare the writing part dead too.
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError == nil {
|
||||
t.writeError = t.readError
|
||||
}
|
||||
t.cond.Broadcast()
|
||||
// Stop writers too.
|
||||
t.recordWriteError(t.readError)
|
||||
|
||||
// Unblock the writer should it wait for this.
|
||||
close(t.startKex)
|
||||
|
||||
// Don't close t.requestKex; it's also written to from writePacket.
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
||||
if t.readSinceKex > t.config.RekeyThreshold {
|
||||
if err := t.requestKeyChange(); err != nil {
|
||||
return nil, err
|
||||
func (t *handshakeTransport) pushPacket(p []byte) error {
|
||||
if debugHandshake {
|
||||
t.printPacket(p, true)
|
||||
}
|
||||
return t.conn.writePacket(p)
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) getWriteError() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.writeError
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) recordWriteError(err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError == nil && err != nil {
|
||||
t.writeError = err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) requestKeyExchange() {
|
||||
select {
|
||||
case t.requestKex <- struct{}{}:
|
||||
default:
|
||||
// something already requested a kex, so do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) resetWriteThresholds() {
|
||||
t.writePacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.writeBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else if t.algorithms != nil {
|
||||
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
|
||||
} else {
|
||||
t.writeBytesLeft = 1 << 30
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) kexLoop() {
|
||||
|
||||
write:
|
||||
for t.getWriteError() == nil {
|
||||
var request *pendingKex
|
||||
var sent bool
|
||||
|
||||
for request == nil || !sent {
|
||||
var ok bool
|
||||
select {
|
||||
case request, ok = <-t.startKex:
|
||||
if !ok {
|
||||
break write
|
||||
}
|
||||
case <-t.requestKex:
|
||||
break
|
||||
}
|
||||
|
||||
if !sent {
|
||||
if err := t.sendKexInit(); err != nil {
|
||||
t.recordWriteError(err)
|
||||
break
|
||||
}
|
||||
sent = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.getWriteError(); err != nil {
|
||||
if request != nil {
|
||||
request.done <- err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// We're not servicing t.requestKex, but that is OK:
|
||||
// we never block on sending to t.requestKex.
|
||||
|
||||
// We're not servicing t.startKex, but the remote end
|
||||
// has just sent us a kexInitMsg, so it can't send
|
||||
// another key change request, until we close the done
|
||||
// channel on the pendingKex request.
|
||||
|
||||
err := t.enterKeyExchange(request.otherInit)
|
||||
|
||||
t.mu.Lock()
|
||||
t.writeError = err
|
||||
t.sentInitPacket = nil
|
||||
t.sentInitMsg = nil
|
||||
|
||||
t.resetWriteThresholds()
|
||||
|
||||
// we have completed the key exchange. Since the
|
||||
// reader is still blocked, it is safe to clear out
|
||||
// the requestKex channel. This avoids the situation
|
||||
// where: 1) we consumed our own request for the
|
||||
// initial kex, and 2) the kex from the remote side
|
||||
// caused another send on the requestKex channel,
|
||||
clear:
|
||||
for {
|
||||
select {
|
||||
case <-t.requestKex:
|
||||
//
|
||||
default:
|
||||
break clear
|
||||
}
|
||||
}
|
||||
|
||||
request.done <- t.writeError
|
||||
|
||||
// kex finished. Push packets that we received while
|
||||
// the kex was in progress. Don't look at t.startKex
|
||||
// and don't increment writtenSinceKex: if we trigger
|
||||
// another kex while we are still busy with the last
|
||||
// one, things will become very confusing.
|
||||
for _, p := range t.pendingPackets {
|
||||
t.writeError = t.pushPacket(p)
|
||||
if t.writeError != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.pendingPackets = t.pendingPackets[:0]
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
// drain startKex channel. We don't service t.requestKex
|
||||
// because nobody does blocking sends there.
|
||||
go func() {
|
||||
for init := range t.startKex {
|
||||
init.done <- t.writeError
|
||||
}
|
||||
}()
|
||||
|
||||
// Unblock reader.
|
||||
t.conn.Close()
|
||||
}
|
||||
|
||||
// The protocol uses uint32 for packet counters, so we can't let them
|
||||
// reach 1<<32. We will actually read and write more packets than
|
||||
// this, though: the other side may send more packets, and after we
|
||||
// hit this limit on writing we will send a few more packets for the
|
||||
// key exchange itself.
|
||||
const packetRekeyThreshold = (1 << 31)
|
||||
|
||||
func (t *handshakeTransport) resetReadThresholds() {
|
||||
t.readPacketsLeft = packetRekeyThreshold
|
||||
if t.config.RekeyThreshold > 0 {
|
||||
t.readBytesLeft = int64(t.config.RekeyThreshold)
|
||||
} else if t.algorithms != nil {
|
||||
t.readBytesLeft = t.algorithms.r.rekeyBytes()
|
||||
} else {
|
||||
t.readBytesLeft = 1 << 30
|
||||
}
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
|
||||
p, err := t.conn.readPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.readSinceKex += uint64(len(p))
|
||||
if debugHandshake {
|
||||
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
|
||||
log.Printf("%s got data (packet %d bytes)", t.id(), len(p))
|
||||
} else {
|
||||
msg, err := decode(p)
|
||||
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
|
||||
}
|
||||
if t.readPacketsLeft > 0 {
|
||||
t.readPacketsLeft--
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if t.readBytesLeft > 0 {
|
||||
t.readBytesLeft -= int64(len(p))
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if debugHandshake {
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
if first && p[0] != msgKexInit {
|
||||
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
|
||||
}
|
||||
|
||||
if p[0] != msgKexInit {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
|
||||
firstKex := t.sessionID == nil
|
||||
|
||||
err = t.enterKeyExchangeLocked(p)
|
||||
if err != nil {
|
||||
// drop connection
|
||||
t.conn.Close()
|
||||
t.writeError = err
|
||||
kex := pendingKex{
|
||||
done: make(chan error, 1),
|
||||
otherInit: p,
|
||||
}
|
||||
t.startKex <- &kex
|
||||
err = <-kex.done
|
||||
|
||||
if debugHandshake {
|
||||
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
|
||||
}
|
||||
|
||||
// Unblock writers.
|
||||
t.sentInitMsg = nil
|
||||
t.sentInitPacket = nil
|
||||
t.cond.Broadcast()
|
||||
t.writtenSinceKex = 0
|
||||
t.mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.readSinceKex = 0
|
||||
t.resetReadThresholds()
|
||||
|
||||
// By default, a key exchange is hidden from higher layers by
|
||||
// translating it into msgIgnore.
|
||||
|
|
@ -213,61 +426,16 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
|
|||
return successPacket, nil
|
||||
}
|
||||
|
||||
// keyChangeCategory describes whether a key exchange is the first on a
|
||||
// connection, or a subsequent one.
|
||||
type keyChangeCategory bool
|
||||
|
||||
const (
|
||||
firstKeyExchange keyChangeCategory = true
|
||||
subsequentKeyExchange keyChangeCategory = false
|
||||
)
|
||||
|
||||
// sendKexInit sends a key change message, and returns the message
|
||||
// that was sent. After initiating the key change, all writes will be
|
||||
// blocked until the change is done, and a failed key change will
|
||||
// close the underlying transport. This function is safe for
|
||||
// concurrent use by multiple goroutines.
|
||||
func (t *handshakeTransport) sendKexInit(isFirst keyChangeCategory) error {
|
||||
var err error
|
||||
|
||||
// sendKexInit sends a key change message.
|
||||
func (t *handshakeTransport) sendKexInit() error {
|
||||
t.mu.Lock()
|
||||
// If this is the initial key change, but we already have a sessionID,
|
||||
// then do nothing because the key exchange has already completed
|
||||
// asynchronously.
|
||||
if !isFirst || t.sessionID == nil {
|
||||
_, _, err = t.sendKexInitLocked(isFirst)
|
||||
}
|
||||
t.mu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isFirst {
|
||||
if packet, err := t.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) requestInitialKeyChange() error {
|
||||
return t.sendKexInit(firstKeyExchange)
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) requestKeyChange() error {
|
||||
return t.sendKexInit(subsequentKeyExchange)
|
||||
}
|
||||
|
||||
// sendKexInitLocked sends a key change message. t.mu must be locked
|
||||
// while this happens.
|
||||
func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) {
|
||||
// kexInits may be sent either in response to the other side,
|
||||
// or because our side wants to initiate a key change, so we
|
||||
// may have already sent a kexInit. In that case, don't send a
|
||||
// second kexInit.
|
||||
defer t.mu.Unlock()
|
||||
if t.sentInitMsg != nil {
|
||||
return t.sentInitMsg, t.sentInitPacket, nil
|
||||
// kexInits may be sent either in response to the other side,
|
||||
// or because our side wants to initiate a key change, so we
|
||||
// may have already sent a kexInit. In that case, don't send a
|
||||
// second kexInit.
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := &kexInitMsg{
|
||||
|
|
@ -295,53 +463,65 @@ func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexI
|
|||
packetCopy := make([]byte, len(packet))
|
||||
copy(packetCopy, packet)
|
||||
|
||||
if err := t.conn.writePacket(packetCopy); err != nil {
|
||||
return nil, nil, err
|
||||
if err := t.pushPacket(packetCopy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.sentInitMsg = msg
|
||||
t.sentInitPacket = packet
|
||||
return msg, packet, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) writePacket(p []byte) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.writtenSinceKex > t.config.RekeyThreshold {
|
||||
t.sendKexInitLocked(subsequentKeyExchange)
|
||||
}
|
||||
for t.sentInitMsg != nil && t.writeError == nil {
|
||||
t.cond.Wait()
|
||||
}
|
||||
if t.writeError != nil {
|
||||
return t.writeError
|
||||
}
|
||||
t.writtenSinceKex += uint64(len(p))
|
||||
|
||||
switch p[0] {
|
||||
case msgKexInit:
|
||||
return errors.New("ssh: only handshakeTransport can send kexInit")
|
||||
case msgNewKeys:
|
||||
return errors.New("ssh: only handshakeTransport can send newKeys")
|
||||
default:
|
||||
return t.conn.writePacket(p)
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.writeError != nil {
|
||||
return t.writeError
|
||||
}
|
||||
|
||||
if t.sentInitMsg != nil {
|
||||
// Copy the packet so the writer can reuse the buffer.
|
||||
cp := make([]byte, len(p))
|
||||
copy(cp, p)
|
||||
t.pendingPackets = append(t.pendingPackets, cp)
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.writeBytesLeft > 0 {
|
||||
t.writeBytesLeft -= int64(len(p))
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if t.writePacketsLeft > 0 {
|
||||
t.writePacketsLeft--
|
||||
} else {
|
||||
t.requestKeyExchange()
|
||||
}
|
||||
|
||||
if err := t.pushPacket(p); err != nil {
|
||||
t.writeError = err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *handshakeTransport) Close() error {
|
||||
return t.conn.Close()
|
||||
}
|
||||
|
||||
// enterKeyExchange runs the key exchange. t.mu must be held while running this.
|
||||
func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) error {
|
||||
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
|
||||
if debugHandshake {
|
||||
log.Printf("%s entered key exchange", t.id())
|
||||
}
|
||||
myInit, myInitPacket, err := t.sendKexInitLocked(subsequentKeyExchange)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherInit := &kexInitMsg{}
|
||||
if err := Unmarshal(otherInitPacket, otherInit); err != nil {
|
||||
|
|
@ -352,20 +532,20 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
|
|||
clientVersion: t.clientVersion,
|
||||
serverVersion: t.serverVersion,
|
||||
clientKexInit: otherInitPacket,
|
||||
serverKexInit: myInitPacket,
|
||||
serverKexInit: t.sentInitPacket,
|
||||
}
|
||||
|
||||
clientInit := otherInit
|
||||
serverInit := myInit
|
||||
serverInit := t.sentInitMsg
|
||||
if len(t.hostKeys) == 0 {
|
||||
clientInit = myInit
|
||||
serverInit = otherInit
|
||||
clientInit, serverInit = serverInit, clientInit
|
||||
|
||||
magics.clientKexInit = myInitPacket
|
||||
magics.clientKexInit = t.sentInitPacket
|
||||
magics.serverKexInit = otherInitPacket
|
||||
}
|
||||
|
||||
algs, err := findAgreedAlgorithms(clientInit, serverInit)
|
||||
var err error
|
||||
t.algorithms, err = findAgreedAlgorithms(clientInit, serverInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -388,16 +568,16 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
|
|||
}
|
||||
}
|
||||
|
||||
kex, ok := kexAlgoMap[algs.kex]
|
||||
kex, ok := kexAlgoMap[t.algorithms.kex]
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
|
||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
|
||||
}
|
||||
|
||||
var result *kexResult
|
||||
if len(t.hostKeys) > 0 {
|
||||
result, err = t.server(kex, algs, &magics)
|
||||
result, err = t.server(kex, t.algorithms, &magics)
|
||||
} else {
|
||||
result, err = t.client(kex, algs, &magics)
|
||||
result, err = t.client(kex, t.algorithms, &magics)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -409,7 +589,9 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
|
|||
}
|
||||
result.SessionID = t.sessionID
|
||||
|
||||
t.conn.prepareKeyChange(algs, result)
|
||||
if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -449,11 +631,9 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if t.hostKeyCallback != nil {
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
|
@ -41,7 +42,10 @@ func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error
|
|||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
listener, err = net.Listen("tcp", "[::1]:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
|
|
@ -58,14 +62,46 @@ func netPipe() (net.Conn, net.Conn, error) {
|
|||
return c1, c2, nil
|
||||
}
|
||||
|
||||
func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTransport, server *handshakeTransport, err error) {
|
||||
// noiseTransport inserts ignore messages to check that the read loop
|
||||
// and the key exchange filters out these messages.
|
||||
type noiseTransport struct {
|
||||
keyingTransport
|
||||
}
|
||||
|
||||
func (t *noiseTransport) writePacket(p []byte) error {
|
||||
ignore := []byte{msgIgnore}
|
||||
if err := t.keyingTransport.writePacket(ignore); err != nil {
|
||||
return err
|
||||
}
|
||||
debug := []byte{msgDebug, 1, 2, 3}
|
||||
if err := t.keyingTransport.writePacket(debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.keyingTransport.writePacket(p)
|
||||
}
|
||||
|
||||
func addNoiseTransport(t keyingTransport) keyingTransport {
|
||||
return &noiseTransport{t}
|
||||
}
|
||||
|
||||
// handshakePair creates two handshakeTransports connected with each
|
||||
// other. If the noise argument is true, both transports will try to
|
||||
// confuse the other side by sending ignore and debug messages.
|
||||
func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
trC := newTransport(a, rand.Reader, true)
|
||||
trS := newTransport(b, rand.Reader, false)
|
||||
var trC, trS keyingTransport
|
||||
|
||||
trC = newTransport(a, rand.Reader, true)
|
||||
trS = newTransport(b, rand.Reader, false)
|
||||
if noise {
|
||||
trC = addNoiseTransport(trC)
|
||||
trS = addNoiseTransport(trS)
|
||||
}
|
||||
clientConf.SetDefaults()
|
||||
|
||||
v := []byte("version")
|
||||
|
|
@ -77,6 +113,13 @@ func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTran
|
|||
serverConf.SetDefaults()
|
||||
server = newServerTransport(trS, v, v, serverConf)
|
||||
|
||||
if err := server.waitSession(); err != nil {
|
||||
return nil, nil, fmt.Errorf("server.waitSession: %v", err)
|
||||
}
|
||||
if err := client.waitSession(); err != nil {
|
||||
return nil, nil, fmt.Errorf("client.waitSession: %v", err)
|
||||
}
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +127,14 @@ func TestHandshakeBasic(t *testing.T) {
|
|||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("see golang.org/issue/7237")
|
||||
}
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
|
||||
checker := &syncChecker{
|
||||
waitCall: make(chan int, 10),
|
||||
called: make(chan int, 10),
|
||||
}
|
||||
|
||||
checker.waitCall <- 1
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
|
@ -93,240 +142,195 @@ func TestHandshakeBasic(t *testing.T) {
|
|||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// Let first kex complete normally.
|
||||
<-checker.called
|
||||
|
||||
clientDone := make(chan int, 0)
|
||||
gotHalf := make(chan int, 0)
|
||||
const N = 20
|
||||
|
||||
go func() {
|
||||
defer close(clientDone)
|
||||
// Client writes a bunch of stuff, and does a key
|
||||
// change in the middle. This should not confuse the
|
||||
// handshake in progress
|
||||
for i := 0; i < 10; i++ {
|
||||
// handshake in progress. We do this twice, so we test
|
||||
// that the packet buffer is reset correctly.
|
||||
for i := 0; i < N; i++ {
|
||||
p := []byte{msgRequestSuccess, byte(i)}
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Fatalf("sendPacket: %v", err)
|
||||
}
|
||||
if i == 5 {
|
||||
if (i % 10) == 5 {
|
||||
<-gotHalf
|
||||
// halfway through, we request a key change.
|
||||
err := trC.sendKexInit(subsequentKeyExchange)
|
||||
if err != nil {
|
||||
t.Fatalf("sendKexInit: %v", err)
|
||||
}
|
||||
trC.requestKeyExchange()
|
||||
|
||||
// Wait until we can be sure the key
|
||||
// change has really started before we
|
||||
// write more.
|
||||
<-checker.called
|
||||
}
|
||||
if (i % 10) == 7 {
|
||||
// write some packets until the kex
|
||||
// completes, to test buffering of
|
||||
// packets.
|
||||
checker.waitCall <- 1
|
||||
}
|
||||
}
|
||||
trC.Close()
|
||||
}()
|
||||
|
||||
// Server checks that client messages come in cleanly
|
||||
i := 0
|
||||
for {
|
||||
p, err := trS.readPacket()
|
||||
err = nil
|
||||
for ; i < N; i++ {
|
||||
var p []byte
|
||||
p, err = trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if p[0] == msgNewKeys {
|
||||
continue
|
||||
if (i % 10) == 5 {
|
||||
gotHalf <- 1
|
||||
}
|
||||
|
||||
want := []byte{msgRequestSuccess, byte(i)}
|
||||
if bytes.Compare(p, want) != 0 {
|
||||
t.Errorf("message %d: got %q, want %q", i, p, want)
|
||||
t.Errorf("message %d: got %v, want %v", i, p, want)
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i != 10 {
|
||||
<-clientDone
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("server error: %v", err)
|
||||
}
|
||||
if i != N {
|
||||
t.Errorf("received %d messages, want 10.", i)
|
||||
}
|
||||
|
||||
// If all went well, we registered exactly 1 key change.
|
||||
if len(checker.calls) != 1 {
|
||||
t.Fatalf("got %d host key checks, want 1", len(checker.calls))
|
||||
}
|
||||
|
||||
pub := testSigners["ecdsa"].PublicKey()
|
||||
want := fmt.Sprintf("%s %v %s %x", "addr", trC.remoteAddr, pub.Type(), pub.Marshal())
|
||||
if want != checker.calls[0] {
|
||||
t.Errorf("got %q want %q for host key check", checker.calls[0], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeError(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "bad")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// send a packet
|
||||
packet := []byte{msgRequestSuccess, 42}
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// Now request a key change.
|
||||
err = trC.sendKexInit(subsequentKeyExchange)
|
||||
if err != nil {
|
||||
t.Errorf("sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
// the key change will fail, and afterwards we can't write.
|
||||
if err := trC.writePacket([]byte{msgRequestSuccess, 43}); err == nil {
|
||||
t.Errorf("writePacket after botched rekey succeeded.")
|
||||
}
|
||||
|
||||
readback, err := trS.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("server closed too soon: %v", err)
|
||||
}
|
||||
if bytes.Compare(readback, packet) != 0 {
|
||||
t.Errorf("got %q want %q", readback, packet)
|
||||
}
|
||||
readback, err = trS.readPacket()
|
||||
if err == nil {
|
||||
t.Errorf("got a message %q after failed key change", readback)
|
||||
close(checker.called)
|
||||
if _, ok := <-checker.called; ok {
|
||||
// If all went well, we registered exactly 2 key changes: one
|
||||
// that establishes the session, and one that we requested
|
||||
// additionally.
|
||||
t.Fatalf("got another host key checks after 2 handshakes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceFirstKex(t *testing.T) {
|
||||
// like handshakePair, but must access the keyingTransport.
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
var trC, trS keyingTransport
|
||||
|
||||
trC = newTransport(a, rand.Reader, true)
|
||||
|
||||
// This is the disallowed packet:
|
||||
trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
|
||||
|
||||
// Rest of the setup.
|
||||
trS = newTransport(b, rand.Reader, false)
|
||||
clientConf.SetDefaults()
|
||||
|
||||
v := []byte("version")
|
||||
client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
|
||||
|
||||
serverConf := &ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.SetDefaults()
|
||||
server := newServerTransport(trS, v, v, serverConf)
|
||||
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
// We setup the initial key exchange, but the remote side
|
||||
// tries to send serviceRequestMsg in cleartext, which is
|
||||
// disallowed.
|
||||
|
||||
err = trS.sendKexInit(firstKeyExchange)
|
||||
if err == nil {
|
||||
if err := server.waitSession(); err == nil {
|
||||
t.Errorf("server first kex init should reject unexpected packet")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeTwice(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
// Both sides should ask for the first key exchange first.
|
||||
err = trS.sendKexInit(firstKeyExchange)
|
||||
if err != nil {
|
||||
t.Errorf("server sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
err = trC.sendKexInit(firstKeyExchange)
|
||||
if err != nil {
|
||||
t.Errorf("client sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
sent := 0
|
||||
// send a packet
|
||||
packet := make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
sent++
|
||||
|
||||
// Send another packet. Use a fresh one, since writePacket destroys.
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
sent++
|
||||
|
||||
// 2nd key change.
|
||||
err = trC.sendKexInit(subsequentKeyExchange)
|
||||
if err != nil {
|
||||
t.Errorf("sendKexInit: %v", err)
|
||||
}
|
||||
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
sent++
|
||||
|
||||
packet = make([]byte, 5)
|
||||
packet[0] = msgRequestSuccess
|
||||
for i := 0; i < sent; i++ {
|
||||
msg, err := trS.readPacket()
|
||||
if err != nil {
|
||||
t.Fatalf("server closed too soon: %v", err)
|
||||
}
|
||||
|
||||
if bytes.Compare(msg, packet) != 0 {
|
||||
t.Errorf("packet %d: got %q want %q", i, msg, packet)
|
||||
}
|
||||
}
|
||||
if len(checker.calls) != 2 {
|
||||
t.Errorf("got %d key changes, want 2", len(checker.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyWrite(t *testing.T) {
|
||||
checker := &testChecker{}
|
||||
checker := &syncChecker{
|
||||
called: make(chan int, 10),
|
||||
waitCall: nil,
|
||||
}
|
||||
clientConf := &ClientConfig{HostKeyCallback: checker.Check}
|
||||
clientConf.RekeyThreshold = 500
|
||||
trC, trS, err := handshakePair(clientConf, "addr")
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
packet := make([]byte, 251)
|
||||
packet[0] = msgRequestSuccess
|
||||
if err := trC.writePacket(packet); err != nil {
|
||||
input := make([]byte, 251)
|
||||
input[0] = msgRequestSuccess
|
||||
|
||||
done := make(chan int, 1)
|
||||
const numPacket = 5
|
||||
go func() {
|
||||
defer close(done)
|
||||
j := 0
|
||||
for ; j < numPacket; j++ {
|
||||
if p, err := trS.readPacket(); err != nil {
|
||||
break
|
||||
} else if !bytes.Equal(input, p) {
|
||||
t.Errorf("got packet type %d, want %d", p[0], input[0])
|
||||
}
|
||||
}
|
||||
|
||||
if j != numPacket {
|
||||
t.Errorf("got %d, want 5 messages", j)
|
||||
}
|
||||
}()
|
||||
|
||||
<-checker.called
|
||||
|
||||
for i := 0; i < numPacket; i++ {
|
||||
p := make([]byte, len(input))
|
||||
copy(p, input)
|
||||
if err := trC.writePacket(p); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
j := 0
|
||||
for ; j < 5; j++ {
|
||||
_, err := trS.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
if i == 2 {
|
||||
// Make sure the kex is in progress.
|
||||
<-checker.called
|
||||
}
|
||||
}
|
||||
|
||||
if j != 5 {
|
||||
t.Errorf("got %d, want 5 messages", j)
|
||||
}
|
||||
|
||||
if len(checker.calls) != 2 {
|
||||
t.Errorf("got %d key changes, wanted 2", len(checker.calls))
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
type syncChecker struct {
|
||||
called chan int
|
||||
waitCall chan int
|
||||
called chan int
|
||||
}
|
||||
|
||||
func (t *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
t.called <- 1
|
||||
func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
|
||||
c.called <- 1
|
||||
if c.waitCall != nil {
|
||||
<-c.waitCall
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandshakeAutoRekeyRead(t *testing.T) {
|
||||
sync := &syncChecker{make(chan int, 2)}
|
||||
sync := &syncChecker{
|
||||
called: make(chan int, 2),
|
||||
waitCall: nil,
|
||||
}
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: sync.Check,
|
||||
}
|
||||
clientConf.RekeyThreshold = 500
|
||||
|
||||
trC, trS, err := handshakePair(clientConf, "addr")
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
|
@ -338,12 +342,19 @@ func TestHandshakeAutoRekeyRead(t *testing.T) {
|
|||
if err := trS.writePacket(packet); err != nil {
|
||||
t.Fatalf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
// While we read out the packet, a key change will be
|
||||
// initiated.
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
done := make(chan int, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if _, err := trC.readPacket(); err != nil {
|
||||
t.Fatalf("readPacket(client): %v", err)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
<-done
|
||||
<-sync.called
|
||||
}
|
||||
|
||||
|
|
@ -357,6 +368,7 @@ type errorKeyingTransport struct {
|
|||
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *errorKeyingTransport) getSessionID() []byte {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -383,20 +395,32 @@ func (n *errorKeyingTransport) readPacket() ([]byte, error) {
|
|||
|
||||
func TestHandshakeErrorHandlingRead(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, i, -1)
|
||||
testHandshakeErrorHandlingN(t, i, -1, false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingWrite(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, -1, i)
|
||||
testHandshakeErrorHandlingN(t, -1, i, false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingReadCoupled(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, i, -1, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
|
||||
for i := 0; i < 20; i++ {
|
||||
testHandshakeErrorHandlingN(t, -1, i, true)
|
||||
}
|
||||
}
|
||||
|
||||
// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
|
||||
// handshakeTransport deadlocks, the go runtime will detect it and
|
||||
// panic.
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int) {
|
||||
func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
|
||||
msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
|
||||
|
||||
a, b := memPipe()
|
||||
|
|
@ -409,37 +433,58 @@ func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int) {
|
|||
serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
|
||||
serverConn.hostKeys = []Signer{key}
|
||||
go serverConn.readLoop()
|
||||
go serverConn.kexLoop()
|
||||
|
||||
clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
|
||||
clientConf.SetDefaults()
|
||||
clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
|
||||
clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
|
||||
clientConn.hostKeyCallback = InsecureIgnoreHostKey()
|
||||
go clientConn.readLoop()
|
||||
go clientConn.kexLoop()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(4)
|
||||
|
||||
for _, hs := range []packetConn{serverConn, clientConn} {
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
err := c.writePacket(msg)
|
||||
if err != nil {
|
||||
break
|
||||
if !coupled {
|
||||
wg.Add(2)
|
||||
go func(c packetConn) {
|
||||
for i := 0; ; i++ {
|
||||
str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8)
|
||||
err := c.writePacket(Marshal(&serviceRequestMsg{str}))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
wg.Done()
|
||||
c.Close()
|
||||
}(hs)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func(c packetConn) {
|
||||
for {
|
||||
_, err := c.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err := c.writePacket(msg); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
wg.Done()
|
||||
}(hs)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
|
|
@ -448,7 +493,7 @@ func TestDisconnect(t *testing.T) {
|
|||
t.Skip("see golang.org/issue/7237")
|
||||
}
|
||||
checker := &testChecker{}
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr")
|
||||
trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
|
|
@ -484,3 +529,31 @@ func TestDisconnect(t *testing.T) {
|
|||
t.Errorf("readPacket 3 succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeRekeyDefault(t *testing.T) {
|
||||
clientConf := &ClientConfig{
|
||||
Config: Config{
|
||||
Ciphers: []string{"aes128-ctr"},
|
||||
},
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
trC, trS, err := handshakePair(clientConf, "addr", false)
|
||||
if err != nil {
|
||||
t.Fatalf("handshakePair: %v", err)
|
||||
}
|
||||
defer trC.Close()
|
||||
defer trS.Close()
|
||||
|
||||
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
|
||||
trC.Close()
|
||||
|
||||
rgb := (1024 + trC.readBytesLeft) >> 30
|
||||
wgb := (1024 + trC.writeBytesLeft) >> 30
|
||||
|
||||
if rgb != 64 {
|
||||
t.Errorf("got rekey after %dG read, want 64G", rgb)
|
||||
}
|
||||
if wgb != 64 {
|
||||
t.Errorf("got rekey after %dG write, want 64G", wgb)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
ssh/kex.go
38
ssh/kex.go
|
|
@ -77,11 +77,11 @@ type kexAlgorithm interface {
|
|||
|
||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
||||
type dhGroup struct {
|
||||
g, p *big.Int
|
||||
g, p, pMinus1 *big.Int
|
||||
}
|
||||
|
||||
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
||||
if theirPublic.Sign() <= 0 || theirPublic.Cmp(group.p) >= 0 {
|
||||
if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 {
|
||||
return nil, errors.New("ssh: DH parameter out of bounds")
|
||||
}
|
||||
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
||||
|
|
@ -90,10 +90,17 @@ func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int,
|
|||
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||
hashFunc := crypto.SHA1
|
||||
|
||||
x, err := rand.Int(randSource, group.p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var x *big.Int
|
||||
for {
|
||||
var err error
|
||||
if x, err = rand.Int(randSource, group.pMinus1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x.Sign() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
X := new(big.Int).Exp(group.g, x, group.p)
|
||||
kexDHInit := kexDHInitMsg{
|
||||
X: X,
|
||||
|
|
@ -146,9 +153,14 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha
|
|||
return
|
||||
}
|
||||
|
||||
y, err := rand.Int(randSource, group.p)
|
||||
if err != nil {
|
||||
return
|
||||
var y *big.Int
|
||||
for {
|
||||
if y, err = rand.Int(randSource, group.pMinus1); err != nil {
|
||||
return
|
||||
}
|
||||
if y.Sign() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
||||
|
|
@ -371,8 +383,9 @@ func init() {
|
|||
// 4253 and Oakley Group 2 in RFC 2409.
|
||||
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
||||
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
// This is the group called diffie-hellman-group14-sha1 in RFC
|
||||
|
|
@ -380,8 +393,9 @@ func init() {
|
|||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
||||
|
||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
g: new(big.Int).SetInt64(2),
|
||||
p: p,
|
||||
pMinus1: new(big.Int).Sub(p, bigOne),
|
||||
}
|
||||
|
||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||
|
|
|
|||
206
ssh/keys.go
206
ssh/keys.go
|
|
@ -10,10 +10,13 @@ import (
|
|||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -281,6 +284,12 @@ type PublicKey interface {
|
|||
Verify(data []byte, sig *Signature) error
|
||||
}
|
||||
|
||||
// CryptoPublicKey, if implemented by a PublicKey,
|
||||
// returns the underlying crypto.PublicKey form of the key.
|
||||
type CryptoPublicKey interface {
|
||||
CryptoPublicKey() crypto.PublicKey
|
||||
}
|
||||
|
||||
// A Signer can create signatures that verify against a public key.
|
||||
type Signer interface {
|
||||
// PublicKey returns an associated PublicKey instance.
|
||||
|
|
@ -348,6 +357,10 @@ func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
||||
}
|
||||
|
||||
func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||
return (*rsa.PublicKey)(r)
|
||||
}
|
||||
|
||||
type dsaPublicKey dsa.PublicKey
|
||||
|
||||
func (r *dsaPublicKey) Type() string {
|
||||
|
|
@ -416,6 +429,10 @@ func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||
return errors.New("ssh: signature did not verify")
|
||||
}
|
||||
|
||||
func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||
return (*dsa.PublicKey)(k)
|
||||
}
|
||||
|
||||
type dsaPrivateKey struct {
|
||||
*dsa.PrivateKey
|
||||
}
|
||||
|
|
@ -509,6 +526,10 @@ func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||
return ed25519.PublicKey(k)
|
||||
}
|
||||
|
||||
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
||||
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
||||
}
|
||||
|
|
@ -604,6 +625,10 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|||
return errors.New("ssh: signature did not verify")
|
||||
}
|
||||
|
||||
func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
||||
return (*ecdsa.PublicKey)(k)
|
||||
}
|
||||
|
||||
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
||||
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||
|
|
@ -700,8 +725,8 @@ func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|||
}
|
||||
|
||||
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey,
|
||||
// ed25519.PublicKey, or any other crypto.Signer and returns a corresponding
|
||||
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
||||
// or ed25519.PublicKey returns a corresponding PublicKey instance.
|
||||
// ECDSA keys must use P-256, P-384 or P-521.
|
||||
func NewPublicKey(key interface{}) (PublicKey, error) {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
|
|
@ -731,6 +756,26 @@ func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|||
return NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private
|
||||
// key and passphrase. It supports the same keys as
|
||||
// ParseRawPrivateKeyWithPassphrase.
|
||||
func ParsePrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (Signer, error) {
|
||||
key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// encryptedBlock tells whether a private key is
|
||||
// encrypted by examining its Proc-Type header
|
||||
// for a mention of ENCRYPTED
|
||||
// according to RFC 1421 Section 4.6.1.1.
|
||||
func encryptedBlock(block *pem.Block) bool {
|
||||
return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED")
|
||||
}
|
||||
|
||||
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
||||
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
||||
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
||||
|
|
@ -739,6 +784,10 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|||
return nil, errors.New("ssh: no key found")
|
||||
}
|
||||
|
||||
if encryptedBlock(block) {
|
||||
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
|
|
@ -753,6 +802,37 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (interface{}, error) {
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("ssh: no key found")
|
||||
}
|
||||
buf := block.Bytes
|
||||
|
||||
if encryptedBlock(block) {
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
var err error
|
||||
buf, err = x509.DecryptPEMBlock(block, passPhrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(buf)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(buf)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ParseDSAPrivateKey(buf)
|
||||
case "OPENSSH PRIVATE KEY":
|
||||
return parseOpenSSHPrivateKey(buf)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
||||
// specified by the OpenSSL DSA man page.
|
||||
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
||||
|
|
@ -761,8 +841,8 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|||
P *big.Int
|
||||
Q *big.Int
|
||||
G *big.Int
|
||||
Priv *big.Int
|
||||
Pub *big.Int
|
||||
Priv *big.Int
|
||||
}
|
||||
rest, err := asn1.Unmarshal(der, &k)
|
||||
if err != nil {
|
||||
|
|
@ -779,15 +859,15 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|||
Q: k.Q,
|
||||
G: k.G,
|
||||
},
|
||||
Y: k.Priv,
|
||||
Y: k.Pub,
|
||||
},
|
||||
X: k.Pub,
|
||||
X: k.Priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Implemented based on the documentation at
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||
func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
||||
func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||
magic := append([]byte("openssh-key-v1"), 0)
|
||||
if !bytes.Equal(magic, key[0:len(magic)]) {
|
||||
return nil, errors.New("ssh: invalid openssh private key format")
|
||||
|
|
@ -807,14 +887,15 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if w.KdfName != "none" || w.CipherName != "none" {
|
||||
return nil, errors.New("ssh: cannot decode encrypted private keys")
|
||||
}
|
||||
|
||||
pk1 := struct {
|
||||
Check1 uint32
|
||||
Check2 uint32
|
||||
Keytype string
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
Rest []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
|
||||
|
|
@ -825,22 +906,95 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
|
|||
return nil, errors.New("ssh: checkint mismatch")
|
||||
}
|
||||
|
||||
// we only handle ed25519 keys currently
|
||||
if pk1.Keytype != KeyAlgoED25519 {
|
||||
// we only handle ed25519 and rsa keys currently
|
||||
switch pk1.Keytype {
|
||||
case KeyAlgoRSA:
|
||||
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
|
||||
key := struct {
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, b := range key.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
pk := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: key.N,
|
||||
E: int(key.E.Int64()),
|
||||
},
|
||||
D: key.D,
|
||||
Primes: []*big.Int{key.P, key.Q},
|
||||
}
|
||||
|
||||
if err := pk.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk.Precompute()
|
||||
|
||||
return pk, nil
|
||||
case KeyAlgoED25519:
|
||||
key := struct {
|
||||
Pub []byte
|
||||
Priv []byte
|
||||
Comment string
|
||||
Pad []byte `ssh:"rest"`
|
||||
}{}
|
||||
|
||||
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(key.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, errors.New("ssh: private key unexpected length")
|
||||
}
|
||||
|
||||
for i, b := range key.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
||||
copy(pk, key.Priv)
|
||||
return &pk, nil
|
||||
default:
|
||||
return nil, errors.New("ssh: unhandled key type")
|
||||
}
|
||||
|
||||
for i, b := range pk1.Pad {
|
||||
if int(b) != i+1 {
|
||||
return nil, errors.New("ssh: padding not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
if len(pk1.Priv) != ed25519.PrivateKeySize {
|
||||
return nil, errors.New("ssh: private key unexpected length")
|
||||
}
|
||||
|
||||
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
||||
copy(pk, pk1.Priv)
|
||||
return &pk, nil
|
||||
}
|
||||
|
||||
// FingerprintLegacyMD5 returns the user presentation of the key's
|
||||
// fingerprint as described by RFC 4716 section 4.
|
||||
func FingerprintLegacyMD5(pubKey PublicKey) string {
|
||||
md5sum := md5.Sum(pubKey.Marshal())
|
||||
hexarray := make([]string, len(md5sum))
|
||||
for i, c := range md5sum {
|
||||
hexarray[i] = hex.EncodeToString([]byte{c})
|
||||
}
|
||||
return strings.Join(hexarray, ":")
|
||||
}
|
||||
|
||||
// FingerprintSHA256 returns the user presentation of the key's
|
||||
// fingerprint as unpadded base64 encoded sha256 hash.
|
||||
// This format was introduced from OpenSSH 6.8.
|
||||
// https://www.openssh.com/txt/release-6.8
|
||||
// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding)
|
||||
func FingerprintSHA256(pubKey PublicKey) string {
|
||||
sha256sum := sha256.Sum256(pubKey.Marshal())
|
||||
hash := base64.RawStdEncoding.EncodeToString(sha256sum[:])
|
||||
return "SHA256:" + hash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,41 @@ func TestParseECPrivateKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// See Issue https://github.com/golang/go/issues/6650.
|
||||
func TestParseEncryptedPrivateKeysFails(t *testing.T) {
|
||||
const wantSubstring = "encrypted"
|
||||
for i, tt := range testdata.PEMEncryptedKeys {
|
||||
_, err := ParsePrivateKey(tt.PEMBytes)
|
||||
if err == nil {
|
||||
t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), wantSubstring) {
|
||||
t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse encrypted private keys with passphrase
|
||||
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
|
||||
data := []byte("sign me")
|
||||
for _, tt := range testdata.PEMEncryptedKeys {
|
||||
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
|
||||
continue
|
||||
}
|
||||
sig, err := s.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Fatalf("dsa.Sign: %v", err)
|
||||
}
|
||||
if err := s.PublicKey().Verify(data, sig); err != nil {
|
||||
t.Errorf("Verify failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDSA(t *testing.T) {
|
||||
// We actually exercise the ParsePrivateKey codepath here, as opposed to
|
||||
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
|
||||
|
|
@ -309,14 +344,14 @@ func TestInvalidEntry(t *testing.T) {
|
|||
}
|
||||
|
||||
var knownHostsParseTests = []struct {
|
||||
input string
|
||||
err string
|
||||
input string
|
||||
err string
|
||||
|
||||
marker string
|
||||
comment string
|
||||
hosts []string
|
||||
rest string
|
||||
} {
|
||||
marker string
|
||||
comment string
|
||||
hosts []string
|
||||
rest string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"EOF",
|
||||
|
|
@ -375,13 +410,13 @@ var knownHostsParseTests = []struct {
|
|||
"localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
|
||||
"",
|
||||
|
||||
"", "comment comment", []string{"localhost","[host2:123]"}, "",
|
||||
"", "comment comment", []string{"localhost", "[host2:123]"}, "",
|
||||
},
|
||||
{
|
||||
"@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
|
||||
"",
|
||||
|
||||
"marker", "", []string{"localhost","[host2:123]"}, "",
|
||||
"marker", "", []string{"localhost", "[host2:123]"}, "",
|
||||
},
|
||||
{
|
||||
"@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
|
||||
|
|
@ -438,3 +473,21 @@ func TestKnownHostsParsing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintLegacyMD5(t *testing.T) {
|
||||
pub, _ := getTestKey()
|
||||
fingerprint := FingerprintLegacyMD5(pub)
|
||||
want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa
|
||||
if fingerprint != want {
|
||||
t.Errorf("got fingerprint %q want %q", fingerprint, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintSHA256(t *testing.T) {
|
||||
pub, _ := getTestKey()
|
||||
fingerprint := FingerprintSHA256(pub)
|
||||
want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa
|
||||
if fingerprint != want {
|
||||
t.Errorf("got fingerprint %q want %q", fingerprint, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
546
ssh/knownhosts/knownhosts.go
Normal file
546
ssh/knownhosts/knownhosts.go
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
// Copyright 2017 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 knownhosts implements a parser for the OpenSSH
|
||||
// known_hosts host key database.
|
||||
package knownhosts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// See the sshd manpage
|
||||
// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
|
||||
// background.
|
||||
|
||||
type addr struct{ host, port string }
|
||||
|
||||
func (a *addr) String() string {
|
||||
h := a.host
|
||||
if strings.Contains(h, ":") {
|
||||
h = "[" + h + "]"
|
||||
}
|
||||
return h + ":" + a.port
|
||||
}
|
||||
|
||||
type matcher interface {
|
||||
match([]addr) bool
|
||||
}
|
||||
|
||||
type hostPattern struct {
|
||||
negate bool
|
||||
addr addr
|
||||
}
|
||||
|
||||
func (p *hostPattern) String() string {
|
||||
n := ""
|
||||
if p.negate {
|
||||
n = "!"
|
||||
}
|
||||
|
||||
return n + p.addr.String()
|
||||
}
|
||||
|
||||
type hostPatterns []hostPattern
|
||||
|
||||
func (ps hostPatterns) match(addrs []addr) bool {
|
||||
matched := false
|
||||
for _, p := range ps {
|
||||
for _, a := range addrs {
|
||||
m := p.match(a)
|
||||
if !m {
|
||||
continue
|
||||
}
|
||||
if p.negate {
|
||||
return false
|
||||
}
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// See
|
||||
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
|
||||
// The matching of * has no regard for separators, unlike filesystem globs
|
||||
func wildcardMatch(pat []byte, str []byte) bool {
|
||||
for {
|
||||
if len(pat) == 0 {
|
||||
return len(str) == 0
|
||||
}
|
||||
if len(str) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if pat[0] == '*' {
|
||||
if len(pat) == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
for j := range str {
|
||||
if wildcardMatch(pat[1:], str[j:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if pat[0] == '?' || pat[0] == str[0] {
|
||||
pat = pat[1:]
|
||||
str = str[1:]
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *hostPattern) match(a addr) bool {
|
||||
return wildcardMatch([]byte(l.addr.host), []byte(a.host)) && l.addr.port == a.port
|
||||
}
|
||||
|
||||
type keyDBLine struct {
|
||||
cert bool
|
||||
matcher matcher
|
||||
knownKey KnownKey
|
||||
}
|
||||
|
||||
func serialize(k ssh.PublicKey) string {
|
||||
return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
|
||||
}
|
||||
|
||||
func (l *keyDBLine) match(addrs []addr) bool {
|
||||
return l.matcher.match(addrs)
|
||||
}
|
||||
|
||||
type hostKeyDB struct {
|
||||
// Serialized version of revoked keys
|
||||
revoked map[string]*KnownKey
|
||||
lines []keyDBLine
|
||||
}
|
||||
|
||||
func newHostKeyDB() *hostKeyDB {
|
||||
db := &hostKeyDB{
|
||||
revoked: make(map[string]*KnownKey),
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func keyEq(a, b ssh.PublicKey) bool {
|
||||
return bytes.Equal(a.Marshal(), b.Marshal())
|
||||
}
|
||||
|
||||
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
|
||||
h, p, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
a := addr{host: h, port: p}
|
||||
|
||||
for _, l := range db.lines {
|
||||
if l.cert && keyEq(l.knownKey.Key, remote) && l.match([]addr{a}) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRevoked can be used as a callback in ssh.CertChecker
|
||||
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
|
||||
_, ok := db.revoked[string(key.Marshal())]
|
||||
return ok
|
||||
}
|
||||
|
||||
const markerCert = "@cert-authority"
|
||||
const markerRevoked = "@revoked"
|
||||
|
||||
func nextWord(line []byte) (string, []byte) {
|
||||
i := bytes.IndexAny(line, "\t ")
|
||||
if i == -1 {
|
||||
return string(line), nil
|
||||
}
|
||||
|
||||
return string(line[:i]), bytes.TrimSpace(line[i:])
|
||||
}
|
||||
|
||||
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
|
||||
if w, next := nextWord(line); w == markerCert || w == markerRevoked {
|
||||
marker = w
|
||||
line = next
|
||||
}
|
||||
|
||||
host, line = nextWord(line)
|
||||
if len(line) == 0 {
|
||||
return "", "", nil, errors.New("knownhosts: missing host pattern")
|
||||
}
|
||||
|
||||
// ignore the keytype as it's in the key blob anyway.
|
||||
_, line = nextWord(line)
|
||||
if len(line) == 0 {
|
||||
return "", "", nil, errors.New("knownhosts: missing key type pattern")
|
||||
}
|
||||
|
||||
keyBlob, _ := nextWord(line)
|
||||
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
key, err = ssh.ParsePublicKey(keyBytes)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
|
||||
return marker, host, key, nil
|
||||
}
|
||||
|
||||
func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
|
||||
marker, pattern, key, err := parseLine(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if marker == markerRevoked {
|
||||
db.revoked[string(key.Marshal())] = &KnownKey{
|
||||
Key: key,
|
||||
Filename: filename,
|
||||
Line: linenum,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := keyDBLine{
|
||||
cert: marker == markerCert,
|
||||
knownKey: KnownKey{
|
||||
Filename: filename,
|
||||
Line: linenum,
|
||||
Key: key,
|
||||
},
|
||||
}
|
||||
|
||||
if pattern[0] == '|' {
|
||||
entry.matcher, err = newHashedHost(pattern)
|
||||
} else {
|
||||
entry.matcher, err = newHostnameMatcher(pattern)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.lines = append(db.lines, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newHostnameMatcher(pattern string) (matcher, error) {
|
||||
var hps hostPatterns
|
||||
for _, p := range strings.Split(pattern, ",") {
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var a addr
|
||||
var negate bool
|
||||
if p[0] == '!' {
|
||||
negate = true
|
||||
p = p[1:]
|
||||
}
|
||||
|
||||
if len(p) == 0 {
|
||||
return nil, errors.New("knownhosts: negation without following hostname")
|
||||
}
|
||||
|
||||
var err error
|
||||
if p[0] == '[' {
|
||||
a.host, a.port, err = net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
a.host, a.port, err = net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
a.host = p
|
||||
a.port = "22"
|
||||
}
|
||||
}
|
||||
hps = append(hps, hostPattern{
|
||||
negate: negate,
|
||||
addr: a,
|
||||
})
|
||||
}
|
||||
return hps, nil
|
||||
}
|
||||
|
||||
// KnownKey represents a key declared in a known_hosts file.
|
||||
type KnownKey struct {
|
||||
Key ssh.PublicKey
|
||||
Filename string
|
||||
Line int
|
||||
}
|
||||
|
||||
func (k *KnownKey) String() string {
|
||||
return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
|
||||
}
|
||||
|
||||
// KeyError is returned if we did not find the key in the host key
|
||||
// database, or there was a mismatch. Typically, in batch
|
||||
// applications, this should be interpreted as failure. Interactive
|
||||
// applications can offer an interactive prompt to the user.
|
||||
type KeyError struct {
|
||||
// Want holds the accepted host keys. For each key algorithm,
|
||||
// there can be one hostkey. If Want is empty, the host is
|
||||
// unknown. If Want is non-empty, there was a mismatch, which
|
||||
// can signify a MITM attack.
|
||||
Want []KnownKey
|
||||
}
|
||||
|
||||
func (u *KeyError) Error() string {
|
||||
if len(u.Want) == 0 {
|
||||
return "knownhosts: key is unknown"
|
||||
}
|
||||
return "knownhosts: key mismatch"
|
||||
}
|
||||
|
||||
// RevokedError is returned if we found a key that was revoked.
|
||||
type RevokedError struct {
|
||||
Revoked KnownKey
|
||||
}
|
||||
|
||||
func (r *RevokedError) Error() string {
|
||||
return "knownhosts: key is revoked"
|
||||
}
|
||||
|
||||
// check checks a key against the host database. This should not be
|
||||
// used for verifying certificates.
|
||||
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
|
||||
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
|
||||
return &RevokedError{Revoked: *revoked}
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(remote.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
|
||||
}
|
||||
|
||||
addrs := []addr{
|
||||
{host, port},
|
||||
}
|
||||
|
||||
if address != "" {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, addr{host, port})
|
||||
}
|
||||
|
||||
return db.checkAddrs(addrs, remoteKey)
|
||||
}
|
||||
|
||||
// checkAddrs checks if we can find the given public key for any of
|
||||
// the given addresses. If we only find an entry for the IP address,
|
||||
// or only the hostname, then this still succeeds.
|
||||
func (db *hostKeyDB) checkAddrs(addrs []addr, remoteKey ssh.PublicKey) error {
|
||||
// TODO(hanwen): are these the right semantics? What if there
|
||||
// is just a key for the IP address, but not for the
|
||||
// hostname?
|
||||
|
||||
// Algorithm => key.
|
||||
knownKeys := map[string]KnownKey{}
|
||||
for _, l := range db.lines {
|
||||
if l.match(addrs) {
|
||||
typ := l.knownKey.Key.Type()
|
||||
if _, ok := knownKeys[typ]; !ok {
|
||||
knownKeys[typ] = l.knownKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyErr := &KeyError{}
|
||||
for _, v := range knownKeys {
|
||||
keyErr.Want = append(keyErr.Want, v)
|
||||
}
|
||||
|
||||
// Unknown remote host.
|
||||
if len(knownKeys) == 0 {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
// If the remote host starts using a different, unknown key type, we
|
||||
// also interpret that as a mismatch.
|
||||
if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Read function parses file contents.
|
||||
func (db *hostKeyDB) Read(r io.Reader, filename string) error {
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
lineNum := 0
|
||||
for scanner.Scan() {
|
||||
lineNum++
|
||||
line := scanner.Bytes()
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := db.parseLine(line, filename, lineNum); err != nil {
|
||||
return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// New creates a host key callback from the given OpenSSH host key
|
||||
// files. The returned callback is for use in
|
||||
// ssh.ClientConfig.HostKeyCallback. Hashed hostnames are not supported.
|
||||
func New(files ...string) (ssh.HostKeyCallback, error) {
|
||||
db := newHostKeyDB()
|
||||
for _, fn := range files {
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := db.Read(f, fn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var certChecker ssh.CertChecker
|
||||
certChecker.IsHostAuthority = db.IsHostAuthority
|
||||
certChecker.IsRevoked = db.IsRevoked
|
||||
certChecker.HostKeyFallback = db.check
|
||||
|
||||
return certChecker.CheckHostKey, nil
|
||||
}
|
||||
|
||||
// Normalize normalizes an address into the form used in known_hosts
|
||||
func Normalize(address string) string {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
host = address
|
||||
port = "22"
|
||||
}
|
||||
entry := host
|
||||
if port != "22" {
|
||||
entry = "[" + entry + "]:" + port
|
||||
} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
|
||||
entry = "[" + entry + "]"
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// Line returns a line to add append to the known_hosts files.
|
||||
func Line(addresses []string, key ssh.PublicKey) string {
|
||||
var trimmed []string
|
||||
for _, a := range addresses {
|
||||
trimmed = append(trimmed, Normalize(a))
|
||||
}
|
||||
|
||||
return strings.Join(trimmed, ",") + " " + serialize(key)
|
||||
}
|
||||
|
||||
// HashHostname hashes the given hostname. The hostname is not
|
||||
// normalized before hashing.
|
||||
func HashHostname(hostname string) string {
|
||||
// TODO(hanwen): check if we can safely normalize this always.
|
||||
salt := make([]byte, sha1.Size)
|
||||
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("crypto/rand failure %v", err))
|
||||
}
|
||||
|
||||
hash := hashHost(hostname, salt)
|
||||
return encodeHash(sha1HashType, salt, hash)
|
||||
}
|
||||
|
||||
func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
|
||||
if len(encoded) == 0 || encoded[0] != '|' {
|
||||
err = errors.New("knownhosts: hashed host must start with '|'")
|
||||
return
|
||||
}
|
||||
components := strings.Split(encoded, "|")
|
||||
if len(components) != 4 {
|
||||
err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
|
||||
return
|
||||
}
|
||||
|
||||
hashType = components[1]
|
||||
if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
|
||||
return
|
||||
}
|
||||
if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encodeHash(typ string, salt []byte, hash []byte) string {
|
||||
return strings.Join([]string{"",
|
||||
typ,
|
||||
base64.StdEncoding.EncodeToString(salt),
|
||||
base64.StdEncoding.EncodeToString(hash),
|
||||
}, "|")
|
||||
}
|
||||
|
||||
// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||
func hashHost(hostname string, salt []byte) []byte {
|
||||
mac := hmac.New(sha1.New, salt)
|
||||
mac.Write([]byte(hostname))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
type hashedHost struct {
|
||||
salt []byte
|
||||
hash []byte
|
||||
}
|
||||
|
||||
const sha1HashType = "1"
|
||||
|
||||
func newHashedHost(encoded string) (*hashedHost, error) {
|
||||
typ, salt, hash, err := decodeHash(encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The type field seems for future algorithm agility, but it's
|
||||
// actually hardcoded in openssh currently, see
|
||||
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
|
||||
if typ != sha1HashType {
|
||||
return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
|
||||
}
|
||||
|
||||
return &hashedHost{salt: salt, hash: hash}, nil
|
||||
}
|
||||
|
||||
func (h *hashedHost) match(addrs []addr) bool {
|
||||
for _, a := range addrs {
|
||||
if bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
329
ssh/knownhosts/knownhosts_test.go
Normal file
329
ssh/knownhosts/knownhosts_test.go
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
// Copyright 2017 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 knownhosts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const edKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGBAarftlLeoyf+v+nVchEZII/vna2PCV8FaX4vsF5BX"
|
||||
const alternateEdKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXffBYeYL+WVzVru8npl5JHt2cjlr4ornFTWzoij9sx"
|
||||
const ecKeyStr = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNLCu01+wpXe3xB5olXCN4SqU2rQu0qjSRKJO4Bg+JRCPU+ENcgdA5srTU8xYDz/GEa4dzK5ldPw4J/gZgSXCMs="
|
||||
|
||||
var ecKey, alternateEdKey, edKey ssh.PublicKey
|
||||
var testAddr = &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196},
|
||||
Port: 22,
|
||||
}
|
||||
|
||||
var testAddr6 = &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196,
|
||||
1, 2, 3, 4,
|
||||
1, 2, 3, 4,
|
||||
1, 2, 3, 4,
|
||||
},
|
||||
Port: 22,
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ecKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(ecKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
edKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(edKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
alternateEdKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(alternateEdKeyStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDB(t *testing.T, s string) *hostKeyDB {
|
||||
db := newHostKeyDB()
|
||||
if err := db.Read(bytes.NewBufferString(s), "testdb"); err != nil {
|
||||
t.Fatalf("Read: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func TestRevoked(t *testing.T) {
|
||||
db := testDB(t, "\n\n@revoked * "+edKeyStr+"\n")
|
||||
want := &RevokedError{
|
||||
Revoked: KnownKey{
|
||||
Key: edKey,
|
||||
Filename: "testdb",
|
||||
Line: 3,
|
||||
},
|
||||
}
|
||||
if err := db.check("", &net.TCPAddr{
|
||||
Port: 42,
|
||||
}, edKey); err == nil {
|
||||
t.Fatal("no error for revoked key")
|
||||
} else if !reflect.DeepEqual(want, err) {
|
||||
t.Fatalf("got %#v, want %#v", want, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostAuthority(t *testing.T) {
|
||||
for _, m := range []struct {
|
||||
authorityFor string
|
||||
address string
|
||||
|
||||
good bool
|
||||
}{
|
||||
{authorityFor: "localhost", address: "localhost:22", good: true},
|
||||
{authorityFor: "localhost", address: "localhost", good: false},
|
||||
{authorityFor: "localhost", address: "localhost:1234", good: false},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost:1234", good: true},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost:22", good: false},
|
||||
{authorityFor: "[localhost]:1234", address: "localhost", good: false},
|
||||
} {
|
||||
db := testDB(t, `@cert-authority `+m.authorityFor+` `+edKeyStr)
|
||||
if ok := db.IsHostAuthority(db.lines[0].knownKey.Key, m.address); ok != m.good {
|
||||
t.Errorf("IsHostAuthority: authority %s, address %s, wanted good = %v, got good = %v",
|
||||
m.authorityFor, m.address, m.good, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBracket(t *testing.T) {
|
||||
db := testDB(t, `[git.eclipse.org]:29418,[198.41.30.196]:29418 `+edKeyStr)
|
||||
|
||||
if err := db.check("git.eclipse.org:29418", &net.TCPAddr{
|
||||
IP: net.IP{198, 41, 30, 196},
|
||||
Port: 29418,
|
||||
}, edKey); err != nil {
|
||||
t.Errorf("got error %v, want none", err)
|
||||
}
|
||||
|
||||
if err := db.check("git.eclipse.org:29419", &net.TCPAddr{
|
||||
Port: 42,
|
||||
}, edKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) > 0 {
|
||||
t.Fatalf("got Want %v, want []", ke.Want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewKeyType(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, ecKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) == 0 {
|
||||
t.Fatalf("got empty KeyError.Want")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameKeyType(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, alternateEdKey); err == nil {
|
||||
t.Fatalf("no error for unknown address")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Fatalf("got type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) == 0 {
|
||||
t.Fatalf("got empty KeyError.Want")
|
||||
} else if got, want := ke.Want[0].Key.Marshal(), edKey.Marshal(); !bytes.Equal(got, want) {
|
||||
t.Fatalf("got key %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddress(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("", testAddr, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv6Address(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", testAddr6, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
|
||||
if err := db.check("", testAddr6, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
str := fmt.Sprintf("#comment\n\nserver.org,%s %s\notherhost %s", testAddr, edKeyStr, ecKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("server.org:22", testAddr, edKey); err != nil {
|
||||
t.Errorf("got error %q, want none", err)
|
||||
}
|
||||
|
||||
want := KnownKey{
|
||||
Key: edKey,
|
||||
Filename: "testdb",
|
||||
Line: 3,
|
||||
}
|
||||
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
|
||||
t.Errorf("succeeded, want KeyError")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Errorf("got %T, want *KeyError", err)
|
||||
} else if len(ke.Want) != 1 {
|
||||
t.Errorf("got %v, want 1 entry", ke)
|
||||
} else if !reflect.DeepEqual(ke.Want[0], want) {
|
||||
t.Errorf("got %v, want %v", ke.Want[0], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegate(t *testing.T) {
|
||||
str := fmt.Sprintf("%s,!server.org %s", testAddr, edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
|
||||
t.Errorf("succeeded")
|
||||
} else if ke, ok := err.(*KeyError); !ok {
|
||||
t.Errorf("got error type %T, want *KeyError", err)
|
||||
} else if len(ke.Want) != 0 {
|
||||
t.Errorf("got expected keys %d (first of type %s), want []", len(ke.Want), ke.Want[0].Key.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcard(t *testing.T) {
|
||||
str := fmt.Sprintf("server*.domain %s", edKeyStr)
|
||||
db := testDB(t, str)
|
||||
|
||||
want := &KeyError{
|
||||
Want: []KnownKey{{
|
||||
Filename: "testdb",
|
||||
Line: 1,
|
||||
Key: edKey,
|
||||
}},
|
||||
}
|
||||
|
||||
got := db.check("server.domain:22", &net.TCPAddr{}, ecKey)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"server.org": "server.org " + edKeyStr,
|
||||
"server.org:22": "server.org " + edKeyStr,
|
||||
"server.org:23": "[server.org]:23 " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:22": "[c629:1ec4:102:304:102:304:102:304] " + edKeyStr,
|
||||
"[c629:1ec4:102:304:102:304:102:304]:23": "[c629:1ec4:102:304:102:304:102:304]:23 " + edKeyStr,
|
||||
} {
|
||||
if got := Line([]string{in}, edKey); got != want {
|
||||
t.Errorf("Line(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardMatch(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
pat, str string
|
||||
want bool
|
||||
}{
|
||||
{"a?b", "abb", true},
|
||||
{"ab", "abc", false},
|
||||
{"abc", "ab", false},
|
||||
{"a*b", "axxxb", true},
|
||||
{"a*b", "axbxb", true},
|
||||
{"a*b", "axbxbc", false},
|
||||
{"a*?", "axbxc", true},
|
||||
{"a*b*", "axxbxxxxxx", true},
|
||||
{"a*b*c", "axxbxxxxxxc", true},
|
||||
{"a*b*?", "axxbxxxxxxc", true},
|
||||
{"a*b*z", "axxbxxbxxxz", true},
|
||||
{"a*b*z", "axxbxxzxxxz", true},
|
||||
{"a*b*z", "axxbxxzxxx", false},
|
||||
} {
|
||||
got := wildcardMatch([]byte(c.pat), []byte(c.str))
|
||||
if got != c.want {
|
||||
t.Errorf("wildcardMatch(%q, %q) = %v, want %v", c.pat, c.str, got, c.want)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): test coverage for certificates.
|
||||
|
||||
const testHostname = "hostname"
|
||||
|
||||
// generated with keygen -H -f
|
||||
const encodedTestHostnameHash = "|1|IHXZvQMvTcZTUU29+2vXFgx8Frs=|UGccIWfRVDwilMBnA3WJoRAC75Y="
|
||||
|
||||
func TestHostHash(t *testing.T) {
|
||||
testHostHash(t, testHostname, encodedTestHostnameHash)
|
||||
}
|
||||
|
||||
func TestHashList(t *testing.T) {
|
||||
encoded := HashHostname(testHostname)
|
||||
testHostHash(t, testHostname, encoded)
|
||||
}
|
||||
|
||||
func testHostHash(t *testing.T, hostname, encoded string) {
|
||||
typ, salt, hash, err := decodeHash(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("decodeHash: %v", err)
|
||||
}
|
||||
|
||||
if got := encodeHash(typ, salt, hash); got != encoded {
|
||||
t.Errorf("got encoding %s want %s", got, encoded)
|
||||
}
|
||||
|
||||
if typ != sha1HashType {
|
||||
t.Fatalf("got hash type %q, want %q", typ, sha1HashType)
|
||||
}
|
||||
|
||||
got := hashHost(hostname, salt)
|
||||
if !bytes.Equal(got, hash) {
|
||||
t.Errorf("got hash %x want %x", got, hash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"127.0.0.1:22": "127.0.0.1",
|
||||
"[127.0.0.1]:22": "127.0.0.1",
|
||||
"[127.0.0.1]:23": "[127.0.0.1]:23",
|
||||
"127.0.0.1:23": "[127.0.0.1]:23",
|
||||
"[a.b.c]:22": "a.b.c",
|
||||
"[abcd:abcd:abcd:abcd]": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:22": "[abcd:abcd:abcd:abcd]",
|
||||
"[abcd:abcd:abcd:abcd]:23": "[abcd:abcd:abcd:abcd]:23",
|
||||
} {
|
||||
got := Normalize(in)
|
||||
if got != want {
|
||||
t.Errorf("Normalize(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashedHostkeyCheck(t *testing.T) {
|
||||
str := fmt.Sprintf("%s %s", HashHostname(testHostname), edKeyStr)
|
||||
db := testDB(t, str)
|
||||
if err := db.check(testHostname+":22", testAddr, edKey); err != nil {
|
||||
t.Errorf("check(%s): %v", testHostname, err)
|
||||
}
|
||||
want := &KeyError{
|
||||
Want: []KnownKey{{
|
||||
Filename: "testdb",
|
||||
Line: 1,
|
||||
Key: edKey,
|
||||
}},
|
||||
}
|
||||
if got := db.check(testHostname+":22", testAddr, alternateEdKey); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got error %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
10
ssh/mac.go
10
ssh/mac.go
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
type macMode struct {
|
||||
keySize int
|
||||
etm bool
|
||||
new func(key []byte) hash.Hash
|
||||
}
|
||||
|
||||
|
|
@ -45,13 +46,16 @@ func (t truncatingMAC) Size() int {
|
|||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
||||
|
||||
var macModes = map[string]*macMode{
|
||||
"hmac-sha2-256": {32, func(key []byte) hash.Hash {
|
||||
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha1": {20, func(key []byte) hash.Hash {
|
||||
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}},
|
||||
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
|
||||
return hmac.New(sha1.New, key)
|
||||
}},
|
||||
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
|
||||
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
|
||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
||||
}},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,9 +116,9 @@ func (m *mux) Wait() error {
|
|||
func newMux(p packetConn) *mux {
|
||||
m := &mux{
|
||||
conn: p,
|
||||
incomingChannels: make(chan NewChannel, 16),
|
||||
incomingChannels: make(chan NewChannel, chanSize),
|
||||
globalResponses: make(chan interface{}, 1),
|
||||
incomingRequests: make(chan *Request, 16),
|
||||
incomingRequests: make(chan *Request, chanSize),
|
||||
errCond: newCond(),
|
||||
}
|
||||
if debugMux {
|
||||
|
|
|
|||
|
|
@ -499,4 +499,7 @@ func TestDebug(t *testing.T) {
|
|||
if debugHandshake {
|
||||
t.Error("handshake debug switched on")
|
||||
}
|
||||
if debugTransport {
|
||||
t.Error("transport debug switched on")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
ssh/server.go
118
ssh/server.go
|
|
@ -10,26 +10,38 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The Permissions type holds fine-grained permissions that are
|
||||
// specific to a user or a specific authentication method for a
|
||||
// user. Permissions, except for "source-address", must be enforced in
|
||||
// the server application layer, after successful authentication. The
|
||||
// Permissions are passed on in ServerConn so a server implementation
|
||||
// can honor them.
|
||||
// specific to a user or a specific authentication method for a user.
|
||||
// The Permissions value for a successful authentication attempt is
|
||||
// available in ServerConn, so it can be used to pass information from
|
||||
// the user-authentication phase to the application layer.
|
||||
type Permissions struct {
|
||||
// Critical options restrict default permissions. Common
|
||||
// restrictions are "source-address" and "force-command". If
|
||||
// the server cannot enforce the restriction, or does not
|
||||
// recognize it, the user should not authenticate.
|
||||
// CriticalOptions indicate restrictions to the default
|
||||
// permissions, and are typically used in conjunction with
|
||||
// user certificates. The standard for SSH certificates
|
||||
// defines "force-command" (only allow the given command to
|
||||
// execute) and "source-address" (only allow connections from
|
||||
// the given address). The SSH package currently only enforces
|
||||
// the "source-address" critical option. It is up to server
|
||||
// implementations to enforce other critical options, such as
|
||||
// "force-command", by checking them after the SSH handshake
|
||||
// is successful. In general, SSH servers should reject
|
||||
// connections that specify critical options that are unknown
|
||||
// or not supported.
|
||||
CriticalOptions map[string]string
|
||||
|
||||
// Extensions are extra functionality that the server may
|
||||
// offer on authenticated connections. Common extensions are
|
||||
// "permit-agent-forwarding", "permit-X11-forwarding". Lack of
|
||||
// support for an extension does not preclude authenticating a
|
||||
// user.
|
||||
// offer on authenticated connections. Lack of support for an
|
||||
// extension does not preclude authenticating a user. Common
|
||||
// extensions are "permit-agent-forwarding",
|
||||
// "permit-X11-forwarding". The Go SSH library currently does
|
||||
// not act on any extension, and it is up to server
|
||||
// implementations to honor them. Extensions can be used to
|
||||
// pass data from the authentication callbacks to the server
|
||||
// application layer.
|
||||
Extensions map[string]string
|
||||
}
|
||||
|
||||
|
|
@ -44,13 +56,24 @@ type ServerConfig struct {
|
|||
// authenticating.
|
||||
NoClientAuth bool
|
||||
|
||||
// MaxAuthTries specifies the maximum number of authentication attempts
|
||||
// permitted per connection. If set to a negative number, the number of
|
||||
// attempts are unlimited. If set to zero, the number of attempts are limited
|
||||
// to 6.
|
||||
MaxAuthTries int
|
||||
|
||||
// PasswordCallback, if non-nil, is called when a user
|
||||
// attempts to authenticate using a password.
|
||||
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
|
||||
|
||||
// PublicKeyCallback, if non-nil, is called when a client attempts public
|
||||
// key authentication. It must return true if the given public key is
|
||||
// valid for the given user. For example, see CertChecker.Authenticate.
|
||||
// PublicKeyCallback, if non-nil, is called when a client
|
||||
// offers a public key for authentication. It must return true
|
||||
// if the given public key can be used to authenticate the
|
||||
// given user. For example, see CertChecker.Authenticate. A
|
||||
// call to this function does not guarantee that the key
|
||||
// offered is in fact used to authenticate. To record any data
|
||||
// depending on the public key, store it inside a
|
||||
// Permissions.Extensions entry.
|
||||
PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
||||
|
|
@ -142,6 +165,10 @@ type ServerConn struct {
|
|||
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
if fullConf.MaxAuthTries == 0 {
|
||||
fullConf.MaxAuthTries = 6
|
||||
}
|
||||
|
||||
s := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
|
@ -188,7 +215,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
|
|||
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
|
||||
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
|
||||
|
||||
if err := s.transport.requestInitialKeyChange(); err != nil {
|
||||
if err := s.transport.waitSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +258,7 @@ func isAcceptableAlgo(algo string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func checkSourceAddress(addr net.Addr, sourceAddr string) error {
|
||||
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
|
||||
if addr == nil {
|
||||
return errors.New("ssh: no address known for client, but source-address match required")
|
||||
}
|
||||
|
|
@ -241,18 +268,20 @@ func checkSourceAddress(addr net.Addr, sourceAddr string) error {
|
|||
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
|
||||
}
|
||||
|
||||
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
|
||||
if bytes.Equal(allowedIP, tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
_, ipNet, err := net.ParseCIDR(sourceAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
|
||||
}
|
||||
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
|
||||
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
|
||||
if allowedIP.Equal(tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
_, ipNet, err := net.ParseCIDR(sourceAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
|
||||
}
|
||||
|
||||
if ipNet.Contains(tcpAddr.IP) {
|
||||
return nil
|
||||
if ipNet.Contains(tcpAddr.IP) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,12 +289,27 @@ func checkSourceAddress(addr net.Addr, sourceAddr string) error {
|
|||
}
|
||||
|
||||
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
|
||||
var err error
|
||||
sessionID := s.transport.getSessionID()
|
||||
var cache pubKeyCache
|
||||
var perms *Permissions
|
||||
|
||||
authFailures := 0
|
||||
|
||||
userAuthLoop:
|
||||
for {
|
||||
if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 {
|
||||
discMsg := &disconnectMsg{
|
||||
Reason: 2,
|
||||
Message: "too many authentication failures",
|
||||
}
|
||||
|
||||
if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, discMsg
|
||||
}
|
||||
|
||||
var userAuthReq userAuthRequestMsg
|
||||
if packet, err := s.transport.readPacket(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -286,6 +330,11 @@ userAuthLoop:
|
|||
if config.NoClientAuth {
|
||||
authErr = nil
|
||||
}
|
||||
|
||||
// allow initial attempt of 'none' without penalty
|
||||
if authFailures == 0 {
|
||||
authFailures--
|
||||
}
|
||||
case "password":
|
||||
if config.PasswordCallback == nil {
|
||||
authErr = errors.New("ssh: password auth not configured")
|
||||
|
|
@ -357,6 +406,7 @@ userAuthLoop:
|
|||
if isQuery {
|
||||
// The client can query if the given public key
|
||||
// would be okay.
|
||||
|
||||
if len(payload) > 0 {
|
||||
return nil, parseError(msgUserAuthRequest)
|
||||
}
|
||||
|
|
@ -385,7 +435,7 @@ userAuthLoop:
|
|||
if !isAcceptableAlgo(sig.Format) {
|
||||
break
|
||||
}
|
||||
signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData)
|
||||
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData)
|
||||
|
||||
if err := pubKey.Verify(signedData, sig); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -406,6 +456,8 @@ userAuthLoop:
|
|||
break userAuthLoop
|
||||
}
|
||||
|
||||
authFailures++
|
||||
|
||||
var failureMsg userAuthFailureMsg
|
||||
if config.PasswordCallback != nil {
|
||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
||||
|
|
@ -421,12 +473,12 @@ userAuthLoop:
|
|||
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
||||
}
|
||||
|
||||
if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil {
|
||||
if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
|
||||
if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return perms, nil
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ func dial(handler serverType, t *testing.T) *Client {
|
|||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
User: "testuser",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
conn, chans, reqs, err := NewClientConn(c2, "", config)
|
||||
|
|
@ -641,7 +642,8 @@ func TestSessionID(t *testing.T) {
|
|||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
clientConf := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
User: "user",
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
|
@ -747,7 +749,9 @@ func TestHostKeyAlgorithms(t *testing.T) {
|
|||
|
||||
// By default, we get the preferred algorithm, which is ECDSA 256.
|
||||
|
||||
clientConf := &ClientConfig{}
|
||||
clientConf := &ClientConfig{
|
||||
HostKeyCallback: InsecureIgnoreHostKey(),
|
||||
}
|
||||
connect(clientConf, KeyAlgoECDSA256)
|
||||
|
||||
// Client asks for RSA explicitly.
|
||||
|
|
|
|||
115
ssh/streamlocal.go
Normal file
115
ssh/streamlocal.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
|
||||
// with "direct-streamlocal@openssh.com" string.
|
||||
//
|
||||
// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
|
||||
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
|
||||
type streamLocalChannelOpenDirectMsg struct {
|
||||
socketPath string
|
||||
reserved0 string
|
||||
reserved1 uint32
|
||||
}
|
||||
|
||||
// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
|
||||
// with "forwarded-streamlocal@openssh.com" string.
|
||||
type forwardedStreamLocalPayload struct {
|
||||
SocketPath string
|
||||
Reserved0 string
|
||||
}
|
||||
|
||||
// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
|
||||
// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
|
||||
type streamLocalChannelForwardMsg struct {
|
||||
socketPath string
|
||||
}
|
||||
|
||||
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
|
||||
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
|
||||
m := streamLocalChannelForwardMsg{
|
||||
socketPath,
|
||||
}
|
||||
// send message
|
||||
ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
|
||||
}
|
||||
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
|
||||
|
||||
return &unixListener{socketPath, c, ch}, nil
|
||||
}
|
||||
|
||||
func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
|
||||
msg := streamLocalChannelOpenDirectMsg{
|
||||
socketPath: socketPath,
|
||||
}
|
||||
ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
return ch, err
|
||||
}
|
||||
|
||||
type unixListener struct {
|
||||
socketPath string
|
||||
|
||||
conn *Client
|
||||
in <-chan forward
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (l *unixListener) Accept() (net.Conn, error) {
|
||||
s, ok := <-l.in
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
ch, incoming, err := s.newCh.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(incoming)
|
||||
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: &net.UnixAddr{
|
||||
Name: l.socketPath,
|
||||
Net: "unix",
|
||||
},
|
||||
raddr: &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes the listener.
|
||||
func (l *unixListener) Close() error {
|
||||
// this also closes the listener.
|
||||
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
|
||||
m := streamLocalChannelForwardMsg{
|
||||
l.socketPath,
|
||||
}
|
||||
ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *unixListener) Addr() net.Addr {
|
||||
return &net.UnixAddr{
|
||||
Name: l.socketPath,
|
||||
Net: "unix",
|
||||
}
|
||||
}
|
||||
196
ssh/tcpip.go
196
ssh/tcpip.go
|
|
@ -20,12 +20,20 @@ import (
|
|||
// addr. Incoming connections will be available by calling Accept on
|
||||
// the returned net.Listener. The listener must be serviced, or the
|
||||
// SSH connection may hang.
|
||||
// N must be "tcp", "tcp4", "tcp6", or "unix".
|
||||
func (c *Client) Listen(n, addr string) (net.Listener, error) {
|
||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
switch n {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.ListenTCP(laddr)
|
||||
case "unix":
|
||||
return c.ListenUnix(addr)
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
|
||||
}
|
||||
return c.ListenTCP(laddr)
|
||||
}
|
||||
|
||||
// Automatic port allocation is broken with OpenSSH before 6.0. See
|
||||
|
|
@ -116,7 +124,7 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
|||
}
|
||||
|
||||
// Register this forward, using the port number we obtained.
|
||||
ch := c.forwards.add(*laddr)
|
||||
ch := c.forwards.add(laddr)
|
||||
|
||||
return &tcpListener{laddr, c, ch}, nil
|
||||
}
|
||||
|
|
@ -131,7 +139,7 @@ type forwardList struct {
|
|||
// forwardEntry represents an established mapping of a laddr on a
|
||||
// remote ssh server to a channel connected to a tcpListener.
|
||||
type forwardEntry struct {
|
||||
laddr net.TCPAddr
|
||||
laddr net.Addr
|
||||
c chan forward
|
||||
}
|
||||
|
||||
|
|
@ -139,16 +147,16 @@ type forwardEntry struct {
|
|||
// arguments to add/remove/lookup should be address as specified in
|
||||
// the original forward-request.
|
||||
type forward struct {
|
||||
newCh NewChannel // the ssh client channel underlying this forward
|
||||
raddr *net.TCPAddr // the raddr of the incoming connection
|
||||
newCh NewChannel // the ssh client channel underlying this forward
|
||||
raddr net.Addr // the raddr of the incoming connection
|
||||
}
|
||||
|
||||
func (l *forwardList) add(addr net.TCPAddr) chan forward {
|
||||
func (l *forwardList) add(addr net.Addr) chan forward {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
f := forwardEntry{
|
||||
addr,
|
||||
make(chan forward, 1),
|
||||
laddr: addr,
|
||||
c: make(chan forward, 1),
|
||||
}
|
||||
l.entries = append(l.entries, f)
|
||||
return f.c
|
||||
|
|
@ -176,44 +184,69 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
|
|||
|
||||
func (l *forwardList) handleChannels(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
var payload forwardedTCPPayload
|
||||
if err := Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
var (
|
||||
laddr net.Addr
|
||||
raddr net.Addr
|
||||
err error
|
||||
)
|
||||
switch channelType := ch.ChannelType(); channelType {
|
||||
case "forwarded-tcpip":
|
||||
var payload forwardedTCPPayload
|
||||
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// RFC 4254 section 7.2 specifies that incoming
|
||||
// addresses should list the address, in string
|
||||
// format. It is implied that this should be an IP
|
||||
// address, as it would be impossible to connect to it
|
||||
// otherwise.
|
||||
laddr, err := parseTCPAddr(payload.Addr, payload.Port)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
// RFC 4254 section 7.2 specifies that incoming
|
||||
// addresses should list the address, in string
|
||||
// format. It is implied that this should be an IP
|
||||
// address, as it would be impossible to connect to it
|
||||
// otherwise.
|
||||
laddr, err = parseTCPAddr(payload.Addr, payload.Port)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
|
||||
if err != nil {
|
||||
ch.Reject(ConnectionFailed, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if ok := l.forward(*laddr, *raddr, ch); !ok {
|
||||
case "forwarded-streamlocal@openssh.com":
|
||||
var payload forwardedStreamLocalPayload
|
||||
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
|
||||
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
|
||||
continue
|
||||
}
|
||||
laddr = &net.UnixAddr{
|
||||
Name: payload.SocketPath,
|
||||
Net: "unix",
|
||||
}
|
||||
raddr = &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
|
||||
}
|
||||
if ok := l.forward(laddr, raddr, ch); !ok {
|
||||
// Section 7.2, implementations MUST reject spurious incoming
|
||||
// connections.
|
||||
ch.Reject(Prohibited, "no forward for address")
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// remove removes the forward entry, and the channel feeding its
|
||||
// listener.
|
||||
func (l *forwardList) remove(addr net.TCPAddr) {
|
||||
func (l *forwardList) remove(addr net.Addr) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for i, f := range l.entries {
|
||||
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
|
||||
if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
|
||||
l.entries = append(l.entries[:i], l.entries[i+1:]...)
|
||||
close(f.c)
|
||||
return
|
||||
|
|
@ -231,12 +264,12 @@ func (l *forwardList) closeAll() {
|
|||
l.entries = nil
|
||||
}
|
||||
|
||||
func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool {
|
||||
func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for _, f := range l.entries {
|
||||
if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port {
|
||||
f.c <- forward{ch, &raddr}
|
||||
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
|
||||
f.c <- forward{newCh: ch, raddr: raddr}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -262,7 +295,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
|
|||
}
|
||||
go DiscardRequests(incoming)
|
||||
|
||||
return &tcpChanConn{
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: l.laddr,
|
||||
raddr: s.raddr,
|
||||
|
|
@ -277,7 +310,7 @@ func (l *tcpListener) Close() error {
|
|||
}
|
||||
|
||||
// this also closes the listener.
|
||||
l.conn.forwards.remove(*l.laddr)
|
||||
l.conn.forwards.remove(l.laddr)
|
||||
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
|
||||
if err == nil && !ok {
|
||||
err = errors.New("ssh: cancel-tcpip-forward failed")
|
||||
|
|
@ -293,29 +326,52 @@ func (l *tcpListener) Addr() net.Addr {
|
|||
// Dial initiates a connection to the addr from the remote host.
|
||||
// The resulting connection has a zero LocalAddr() and RemoteAddr().
|
||||
func (c *Client) Dial(n, addr string) (net.Conn, error) {
|
||||
// Parse the address into host and numeric port.
|
||||
host, portString, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var ch Channel
|
||||
switch n {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
// Parse the address into host and numeric port.
|
||||
host, portString, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use a zero address for local and remote address.
|
||||
zeroAddr := &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: zeroAddr,
|
||||
raddr: zeroAddr,
|
||||
}, nil
|
||||
case "unix":
|
||||
var err error
|
||||
ch, err = c.dialStreamLocal(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: &net.UnixAddr{
|
||||
Name: "@",
|
||||
Net: "unix",
|
||||
},
|
||||
raddr: &net.UnixAddr{
|
||||
Name: addr,
|
||||
Net: "unix",
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use a zero address for local and remote address.
|
||||
zeroAddr := &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: 0,
|
||||
}
|
||||
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpChanConn{
|
||||
Channel: ch,
|
||||
laddr: zeroAddr,
|
||||
raddr: zeroAddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DialTCP connects to the remote address raddr on the network net,
|
||||
|
|
@ -332,7 +388,7 @@ func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpChanConn{
|
||||
return &chanConn{
|
||||
Channel: ch,
|
||||
laddr: laddr,
|
||||
raddr: raddr,
|
||||
|
|
@ -366,26 +422,26 @@ type tcpChan struct {
|
|||
Channel // the backing channel
|
||||
}
|
||||
|
||||
// tcpChanConn fulfills the net.Conn interface without
|
||||
// chanConn fulfills the net.Conn interface without
|
||||
// the tcpChan having to hold laddr or raddr directly.
|
||||
type tcpChanConn struct {
|
||||
type chanConn struct {
|
||||
Channel
|
||||
laddr, raddr net.Addr
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (t *tcpChanConn) LocalAddr() net.Addr {
|
||||
func (t *chanConn) LocalAddr() net.Addr {
|
||||
return t.laddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (t *tcpChanConn) RemoteAddr() net.Addr {
|
||||
func (t *chanConn) RemoteAddr() net.Addr {
|
||||
return t.raddr
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection.
|
||||
func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetDeadline(deadline time.Time) error {
|
||||
if err := t.SetReadDeadline(deadline); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -396,12 +452,14 @@ func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
|
|||
// A zero value for t means Read will not time out.
|
||||
// After the deadline, the error from Read will implement net.Error
|
||||
// with Timeout() == true.
|
||||
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetReadDeadline(deadline time.Time) error {
|
||||
// for compatibility with previous version,
|
||||
// the error message contains "tcpChan"
|
||||
return errors.New("ssh: tcpChan: deadline not supported")
|
||||
}
|
||||
|
||||
// SetWriteDeadline exists to satisfy the net.Conn interface
|
||||
// but is not implemented by this type. It always returns an error.
|
||||
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error {
|
||||
func (t *chanConn) SetWriteDeadline(deadline time.Time) error {
|
||||
return errors.New("ssh: tcpChan: deadline not supported")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,8 +132,11 @@ const (
|
|||
keyPasteEnd
|
||||
)
|
||||
|
||||
var pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||
var pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||
var (
|
||||
crlf = []byte{'\r', '\n'}
|
||||
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
||||
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
||||
)
|
||||
|
||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
||||
|
|
@ -333,7 +336,7 @@ func (t *Terminal) advanceCursor(places int) {
|
|||
// So, if we are stopping at the end of a line, we
|
||||
// need to write a newline so that our cursor can be
|
||||
// advanced to the next line.
|
||||
t.outBuf = append(t.outBuf, '\n')
|
||||
t.outBuf = append(t.outBuf, '\r', '\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -593,6 +596,35 @@ func (t *Terminal) writeLine(line []rune) {
|
|||
}
|
||||
}
|
||||
|
||||
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
||||
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
||||
for len(buf) > 0 {
|
||||
i := bytes.IndexByte(buf, '\n')
|
||||
todo := len(buf)
|
||||
if i >= 0 {
|
||||
todo = i
|
||||
}
|
||||
|
||||
var nn int
|
||||
nn, err = w.Write(buf[:todo])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
buf = buf[todo:]
|
||||
|
||||
if i >= 0 {
|
||||
if _, err = w.Write(crlf); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += 1
|
||||
buf = buf[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
|
@ -600,7 +632,7 @@ func (t *Terminal) Write(buf []byte) (n int, err error) {
|
|||
if t.cursorX == 0 && t.cursorY == 0 {
|
||||
// This is the easy case: there's nothing on the screen that we
|
||||
// have to move out of the way.
|
||||
return t.c.Write(buf)
|
||||
return writeWithCRLF(t.c, buf)
|
||||
}
|
||||
|
||||
// We have a prompt and possibly user input on the screen. We
|
||||
|
|
@ -620,7 +652,7 @@ func (t *Terminal) Write(buf []byte) (n int, err error) {
|
|||
}
|
||||
t.outBuf = t.outBuf[:0]
|
||||
|
||||
if n, err = t.c.Write(buf); err != nil {
|
||||
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -740,8 +772,6 @@ func (t *Terminal) readLine() (line string, err error) {
|
|||
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||
}
|
||||
|
||||
panic("unreachable") // for Go 1.0.
|
||||
}
|
||||
|
||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||
|
|
@ -890,3 +920,32 @@ func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
|||
}
|
||||
return s.entries[index], true
|
||||
}
|
||||
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||
// The slice returned does not include the \n.
|
||||
// readPasswordLine also ignores any \r it finds.
|
||||
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||
var buf [1]byte
|
||||
var ret []byte
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf[:])
|
||||
if n > 0 {
|
||||
switch buf[0] {
|
||||
case '\n':
|
||||
return ret, nil
|
||||
case '\r':
|
||||
// remove \r from passwords on Windows
|
||||
default:
|
||||
ret = append(ret, buf[0])
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
|
@ -269,6 +270,50 @@ func TestTerminalSetSize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReadPasswordLineEnd(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"\n", ""},
|
||||
{"\r\n", ""},
|
||||
{"test\r\n", "test"},
|
||||
{"testtesttesttes\n", "testtesttesttes"},
|
||||
{"testtesttesttes\r\n", "testtesttesttes"},
|
||||
{"testtesttesttesttest\n", "testtesttesttesttest"},
|
||||
{"testtesttesttesttest\r\n", "testtesttesttesttest"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := buf.WriteString(test.input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
have, err := readPasswordLine(buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
|
||||
continue
|
||||
}
|
||||
if string(have) != test.want {
|
||||
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err = buf.WriteString(test.input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
have, err = readPasswordLine(buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
|
||||
continue
|
||||
}
|
||||
if string(have) != test.want {
|
||||
t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRawState(t *testing.T) {
|
||||
fd := int(os.Stdout.Fd())
|
||||
if !IsTerminal(fd) {
|
||||
|
|
@ -289,3 +334,17 @@ func TestMakeRawState(t *testing.T) {
|
|||
t.Errorf("states do not match; was %v, expected %v", raw, st)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputNewlines(t *testing.T) {
|
||||
// \n should be changed to \r\n in terminal output.
|
||||
buf := new(bytes.Buffer)
|
||||
term := NewTerminal(buf, ">")
|
||||
|
||||
term.Write([]byte("1\n2\n"))
|
||||
output := string(buf.Bytes())
|
||||
const expected = "1\r\n2\r\n"
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("incorrect output: was %q, expected %q", output, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
|
@ -72,8 +71,10 @@ func GetState(fd int) (*State, error) {
|
|||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
||||
return err
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
|
|
@ -86,6 +87,13 @@ func GetSize(fd int) (width, height int, err error) {
|
|||
return int(dimensions[1]), int(dimensions[0]), nil
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return syscall.Read(int(r), buf)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
|
|
@ -107,27 +115,5 @@ func ReadPassword(fd int) ([]byte, error) {
|
|||
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||
}()
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
|
|
|||
128
ssh/terminal/util_solaris.go
Normal file
128
ssh/terminal/util_solaris.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
state *unix.Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldState := *val
|
||||
|
||||
newState := oldState
|
||||
newState.Lflag &^= syscall.ECHO
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||
newState.Iflag |= syscall.ICRNL
|
||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldTermios := *oldTermiosPtr
|
||||
|
||||
newTermios := oldTermios
|
||||
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||
newTermios.Oflag &^= syscall.OPOST
|
||||
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
newTermios.Cflag |= syscall.CS8
|
||||
newTermios.Cc[unix.VMIN] = 1
|
||||
newTermios.Cc[unix.VTIME] = 0
|
||||
|
||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{
|
||||
state: oldTermiosPtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, oldState *State) error {
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{
|
||||
state: oldTermiosPtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
|
@ -123,6 +122,13 @@ func GetSize(fd int) (width, height int, err error) {
|
|||
return int(info.size.x), int(info.size.y), nil
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific Windows HANDLE.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return syscall.Read(syscall.Handle(r), buf)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
|
|
@ -145,30 +151,5 @@ func ReadPassword(fd int) ([]byte, error) {
|
|||
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
|
||||
}()
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(syscall.Handle(fd), buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
if n > 0 && buf[n-1] == '\r' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
|
||||
func TestCertLogin(t *testing.T) {
|
||||
s := newServer(t)
|
||||
defer s.Shutdown()
|
||||
|
|
@ -37,11 +39,39 @@ func TestCertLogin(t *testing.T) {
|
|||
|
||||
conf := &ssh.ClientConfig{
|
||||
User: username(),
|
||||
HostKeyCallback: (&ssh.CertChecker{
|
||||
IsHostAuthority: func(pk ssh.PublicKey, addr string) bool {
|
||||
return bytes.Equal(pk.Marshal(), testPublicKeys["ca"].Marshal())
|
||||
},
|
||||
}).CheckHostKey,
|
||||
}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner))
|
||||
client, err := s.TryDial(conf)
|
||||
if err != nil {
|
||||
t.Fatalf("TryDial: %v", err)
|
||||
|
||||
for _, test := range []struct {
|
||||
addr string
|
||||
succeed bool
|
||||
}{
|
||||
{addr: "host.example.com:22", succeed: true},
|
||||
{addr: "host.example.com:10000", succeed: true}, // non-standard port must be OK
|
||||
{addr: "host.example.com", succeed: false}, // port must be specified
|
||||
{addr: "host.ex4mple.com:22", succeed: false}, // wrong host
|
||||
} {
|
||||
client, err := s.TryDialWithAddr(conf, test.addr)
|
||||
|
||||
// Always close client if opened successfully
|
||||
if err == nil {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
// Now evaluate whether the test failed or passed
|
||||
if test.succeed {
|
||||
if err != nil {
|
||||
t.Fatalf("TryDialWithAddr: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("TryDialWithAddr, unexpected success")
|
||||
}
|
||||
}
|
||||
}
|
||||
client.Close()
|
||||
}
|
||||
|
|
|
|||
128
ssh/test/dial_unix_test.go
Normal file
128
ssh/test/dial_unix_test.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2012 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package test
|
||||
|
||||
// direct-tcpip and direct-streamlocal functional tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type dialTester interface {
|
||||
TestServerConn(t *testing.T, c net.Conn)
|
||||
TestClientConn(t *testing.T, c net.Conn)
|
||||
}
|
||||
|
||||
func testDial(t *testing.T, n, listenAddr string, x dialTester) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
sshConn := server.Dial(clientConfig())
|
||||
defer sshConn.Close()
|
||||
|
||||
l, err := net.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Listen: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
testData := fmt.Sprintf("hello from %s, %s", n, listenAddr)
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
x.TestServerConn(t, c)
|
||||
|
||||
io.WriteString(c, testData)
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := sshConn.Dial(n, l.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("Dial: %v", err)
|
||||
}
|
||||
x.TestClientConn(t, conn)
|
||||
defer conn.Close()
|
||||
b, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll: %v", err)
|
||||
}
|
||||
t.Logf("got %q", string(b))
|
||||
if string(b) != testData {
|
||||
t.Fatalf("expected %q, got %q", testData, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
type tcpDialTester struct {
|
||||
listenAddr string
|
||||
}
|
||||
|
||||
func (x *tcpDialTester) TestServerConn(t *testing.T, c net.Conn) {
|
||||
host := strings.Split(x.listenAddr, ":")[0]
|
||||
prefix := host + ":"
|
||||
if !strings.HasPrefix(c.LocalAddr().String(), prefix) {
|
||||
t.Fatalf("expected to start with %q, got %q", prefix, c.LocalAddr().String())
|
||||
}
|
||||
if !strings.HasPrefix(c.RemoteAddr().String(), prefix) {
|
||||
t.Fatalf("expected to start with %q, got %q", prefix, c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (x *tcpDialTester) TestClientConn(t *testing.T, c net.Conn) {
|
||||
// we use zero addresses. see *Client.Dial.
|
||||
if c.LocalAddr().String() != "0.0.0.0:0" {
|
||||
t.Fatalf("expected \"0.0.0.0:0\", got %q", c.LocalAddr().String())
|
||||
}
|
||||
if c.RemoteAddr().String() != "0.0.0.0:0" {
|
||||
t.Fatalf("expected \"0.0.0.0:0\", got %q", c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialTCP(t *testing.T) {
|
||||
x := &tcpDialTester{
|
||||
listenAddr: "127.0.0.1:0",
|
||||
}
|
||||
testDial(t, "tcp", x.listenAddr, x)
|
||||
}
|
||||
|
||||
type unixDialTester struct {
|
||||
listenAddr string
|
||||
}
|
||||
|
||||
func (x *unixDialTester) TestServerConn(t *testing.T, c net.Conn) {
|
||||
if c.LocalAddr().String() != x.listenAddr {
|
||||
t.Fatalf("expected %q, got %q", x.listenAddr, c.LocalAddr().String())
|
||||
}
|
||||
if c.RemoteAddr().String() != "@" {
|
||||
t.Fatalf("expected \"@\", got %q", c.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (x *unixDialTester) TestClientConn(t *testing.T, c net.Conn) {
|
||||
if c.RemoteAddr().String() != x.listenAddr {
|
||||
t.Fatalf("expected %q, got %q", x.listenAddr, c.RemoteAddr().String())
|
||||
}
|
||||
if c.LocalAddr().String() != "@" {
|
||||
t.Fatalf("expected \"@\", got %q", c.LocalAddr().String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
x := &unixDialTester{
|
||||
listenAddr: addr,
|
||||
}
|
||||
testDial(t, "unix", x.listenAddr, x)
|
||||
}
|
||||
|
|
@ -16,13 +16,17 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func TestPortForward(t *testing.T) {
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
func testPortForward(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
defer conn.Close()
|
||||
|
||||
sshListener, err := conn.Listen("tcp", "localhost:0")
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -41,14 +45,14 @@ func TestPortForward(t *testing.T) {
|
|||
}()
|
||||
|
||||
forwardedAddr := sshListener.Addr().String()
|
||||
tcpConn, err := net.Dial("tcp", forwardedAddr)
|
||||
netConn, err := net.Dial(n, forwardedAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("TCP dial failed: %v", err)
|
||||
t.Fatalf("net dial failed: %v", err)
|
||||
}
|
||||
|
||||
readChan := make(chan []byte)
|
||||
go func() {
|
||||
data, _ := ioutil.ReadAll(tcpConn)
|
||||
data, _ := ioutil.ReadAll(netConn)
|
||||
readChan <- data
|
||||
}()
|
||||
|
||||
|
|
@ -62,14 +66,14 @@ func TestPortForward(t *testing.T) {
|
|||
for len(sent) < 1000*1000 {
|
||||
// Send random sized chunks
|
||||
m := rand.Intn(len(data))
|
||||
n, err := tcpConn.Write(data[:m])
|
||||
n, err := netConn.Write(data[:m])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
sent = append(sent, data[:n]...)
|
||||
}
|
||||
if err := tcpConn.(*net.TCPConn).CloseWrite(); err != nil {
|
||||
t.Errorf("tcpConn.CloseWrite: %v", err)
|
||||
if err := netConn.(closeWriter).CloseWrite(); err != nil {
|
||||
t.Errorf("netConn.CloseWrite: %v", err)
|
||||
}
|
||||
|
||||
read := <-readChan
|
||||
|
|
@ -86,19 +90,29 @@ func TestPortForward(t *testing.T) {
|
|||
}
|
||||
|
||||
// Check that the forward disappeared.
|
||||
tcpConn, err = net.Dial("tcp", forwardedAddr)
|
||||
netConn, err = net.Dial(n, forwardedAddr)
|
||||
if err == nil {
|
||||
tcpConn.Close()
|
||||
netConn.Close()
|
||||
t.Errorf("still listening to %s after closing", forwardedAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptClose(t *testing.T) {
|
||||
func TestPortForwardTCP(t *testing.T) {
|
||||
testPortForward(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestPortForwardUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testPortForward(t, "unix", addr)
|
||||
}
|
||||
|
||||
func testAcceptClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen("tcp", "localhost:0")
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -124,13 +138,23 @@ func TestAcceptClose(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAcceptCloseTCP(t *testing.T) {
|
||||
testAcceptClose(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestAcceptCloseUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testAcceptClose(t, "unix", addr)
|
||||
}
|
||||
|
||||
// Check that listeners exit if the underlying client transport dies.
|
||||
func TestPortForwardConnectionClose(t *testing.T) {
|
||||
func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
conn := server.Dial(clientConfig())
|
||||
|
||||
sshListener, err := conn.Listen("tcp", "localhost:0")
|
||||
sshListener, err := conn.Listen(n, listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -158,3 +182,13 @@ func TestPortForwardConnectionClose(t *testing.T) {
|
|||
t.Logf("quit as expected (error %v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseTCP(t *testing.T) {
|
||||
testPortForwardConnectionClose(t, "tcp", "localhost:0")
|
||||
}
|
||||
|
||||
func TestPortForwardConnectionCloseUnix(t *testing.T) {
|
||||
addr, cleanup := newTempSocket(t)
|
||||
defer cleanup()
|
||||
testPortForwardConnectionClose(t, "unix", addr)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2012 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package test
|
||||
|
||||
// direct-tcpip functional tests
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDial(t *testing.T) {
|
||||
server := newServer(t)
|
||||
defer server.Shutdown()
|
||||
sshConn := server.Dial(clientConfig())
|
||||
defer sshConn.Close()
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("Listen: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
io.WriteString(c, c.RemoteAddr().String())
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := sshConn.Dial("tcp", l.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("Dial: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ Protocol 2
|
|||
HostKey {{.Dir}}/id_rsa
|
||||
HostKey {{.Dir}}/id_dsa
|
||||
HostKey {{.Dir}}/id_ecdsa
|
||||
HostCertificate {{.Dir}}/id_rsa-cert.pub
|
||||
Pidfile {{.Dir}}/sshd.pid
|
||||
#UsePrivilegeSeparation no
|
||||
KeyRegenerationInterval 3600
|
||||
|
|
@ -119,6 +120,11 @@ func clientConfig() *ssh.ClientConfig {
|
|||
ssh.PublicKeys(testSigners["user"]),
|
||||
},
|
||||
HostKeyCallback: hostKeyDB().Check,
|
||||
HostKeyAlgorithms: []string{ // by default, don't allow certs as this affects the hostKeyDB checker
|
||||
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
|
||||
ssh.KeyAlgoRSA, ssh.KeyAlgoDSA,
|
||||
ssh.KeyAlgoED25519,
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
|
@ -154,6 +160,12 @@ func unixConnection() (*net.UnixConn, *net.UnixConn, error) {
|
|||
}
|
||||
|
||||
func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
|
||||
return s.TryDialWithAddr(config, "")
|
||||
}
|
||||
|
||||
// addr is the user specified host:port. While we don't actually dial it,
|
||||
// we need to know this for host key matching
|
||||
func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (*ssh.Client, error) {
|
||||
sshd, err := exec.LookPath("sshd")
|
||||
if err != nil {
|
||||
s.t.Skipf("skipping test: %v", err)
|
||||
|
|
@ -179,7 +191,7 @@ func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
|
|||
s.t.Fatalf("s.cmd.Start: %v", err)
|
||||
}
|
||||
s.clientConn = c1
|
||||
conn, chans, reqs, err := ssh.NewClientConn(c1, "", config)
|
||||
conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -250,6 +262,11 @@ func newServer(t *testing.T) *server {
|
|||
writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
}
|
||||
|
||||
for k, v := range testdata.SSHCertificates {
|
||||
filename := "id_" + k + "-cert.pub"
|
||||
writeFile(filepath.Join(dir, filename), v)
|
||||
}
|
||||
|
||||
var authkeys bytes.Buffer
|
||||
for k, _ := range testdata.PEMBytes {
|
||||
authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
|
||||
|
|
@ -266,3 +283,13 @@ func newServer(t *testing.T) *server {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTempSocket(t *testing.T) (string, func()) {
|
||||
dir, err := ioutil.TempDir("", "socket")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deferFunc := func() { os.RemoveAll(dir) }
|
||||
addr := filepath.Join(dir, "sock")
|
||||
return addr, deferFunc
|
||||
}
|
||||
|
|
|
|||
120
ssh/testdata/keys.go
vendored
120
ssh/testdata/keys.go
vendored
|
|
@ -48,10 +48,130 @@ AAAEAaYmXltfW6nhRo3iWGglRB48lYq0z0Q3I3KyrdutEr6j7d/uFLuDlRbBc4ZVOsx+Gb
|
|||
HKuOrPtLHFvHsjWPwO+/AAAAE2dhcnRvbm1AZ2FydG9ubS14cHMBAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
`),
|
||||
"rsa-openssh-format": []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5l
|
||||
oEuW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lz
|
||||
a+yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAIQWL0H31i9B98AAAAH
|
||||
c3NoLXJzYQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5loE
|
||||
uW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lza+
|
||||
yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAADAQABAAAAgCThyTGsT4
|
||||
IARDxVMhWl6eiB2ZrgFgWSeJm/NOqtppWgOebsIqPMMg4UVuVFsl422/lE3RkPhVkjGXgE
|
||||
pWvZAdCnmLmApK8wK12vF334lZhZT7t3Z9EzJps88PWEHo7kguf285HcnUM7FlFeissJdk
|
||||
kXly34y7/3X/a6Tclm+iABAAAAQE0xR/KxZ39slwfMv64Rz7WKk1PPskaryI29aHE3mKHk
|
||||
pY2QA+P3QlrKxT/VWUMjHUbNNdYfJm48xu0SGNMRdKMAAABBAORh2NP/06JUV3J9W/2Hju
|
||||
X1ViJuqqcQnJPVzpgSL826EC2xwOECTqoY8uvFpUdD7CtpksIxNVqRIhuNOlz0lqEAAABB
|
||||
ANkaHTTaPojClO0dKJ/Zjs7pWOCGliebBYprQ/Y4r9QLBkC/XaWMS26gFIrjgC7D2Rv+rZ
|
||||
wSD0v0RcmkITP1ZR0AAAAYcHF1ZXJuYUBMdWNreUh5ZHJvLmxvY2FsAQID
|
||||
-----END OPENSSH PRIVATE KEY-----`),
|
||||
"user": []byte(`-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49
|
||||
AwEHoUQDQgAEYcO2xNKiRUYOLEHM7VYAp57HNyKbOdYtHD83Z4hzNPVC4tM5mdGD
|
||||
PLL8IEwvYu2wq+lpXfGQnNMbzYf9gspG0w==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`),
|
||||
"ca": []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAvg9dQ9IRG59lYJb+GESfKWTch4yBpr7Ydw1jkK6vvtrx9jLo
|
||||
5hkA8X6+ElRPRqTAZSlN5cBm6YCAcQIOsmXDUn6Oj1lVPQAoOjTBTvsjM3NjGhvv
|
||||
52kHTY0nsMsBeY9q5DTtlzmlYkVUq2a6Htgf2mNi01dIw5fJ7uTTo8EbNf7O0i3u
|
||||
c9a8P19HaZl5NKiWN4EIZkfB2WdXYRJCVBsGgQj3dE/GrEmH9QINq1A+GkNvK96u
|
||||
vZm8H1jjmuqzHplWa7lFeXcx8FTVTbVb/iJrZ2Lc/JvIPitKZWhqbR59yrGjpwEp
|
||||
Id7bo4WhO5L3OB0fSIJYvfu+o4WYnt4f3UzecwIDAQABAoIBABRD9yHgKErVuC2Q
|
||||
bA+SYZY8VvdtF/X7q4EmQFORDNRA7EPgMc03JU6awRGbQ8i4kHs46EFzPoXvWcKz
|
||||
AXYsO6N0Myc900Tp22A5d9NAHATEbPC/wdje7hRq1KyZONMJY9BphFv3nZbY5apR
|
||||
Dc90JBFZP5RhXjTc3n9GjvqLAKfFEKVmPRCvqxCOZunw6XR+SgIQLJo36nsIsbhW
|
||||
QUXIVaCI6cXMN8bRPm8EITdBNZu06Fpu4ZHm6VaxlXN9smERCDkgBSNXNWHKxmmA
|
||||
c3Glo2DByUr2/JFBOrLEe9fkYgr24KNCQkHVcSaFxEcZvTggr7StjKISVHlCNEaB
|
||||
7Q+kPoECgYEA3zE9FmvFGoQCU4g4Nl3dpQHs6kaAW8vJlrmq3xsireIuaJoa2HMe
|
||||
wYdIvgCnK9DIjyxd5OWnE4jXtAEYPsyGD32B5rSLQrRO96lgb3f4bESCLUb3Bsn/
|
||||
sdgeE3p1xZMA0B59htqCrvVgN9k8WxyevBxYl3/gSBm/p8OVH1RTW/ECgYEA2f9Z
|
||||
95OLj0KQHQtxQXf+I3VjhCw3LkLW39QZOXVI0QrCJfqqP7uxsJXH9NYX0l0GFTcR
|
||||
kRrlyoaSU1EGQosZh+n1MvplGBTkTSV47/bPsTzFpgK2NfEZuFm9RoWgltS+nYeH
|
||||
Y2k4mnAN3PhReCMwuprmJz8GRLsO3Cs2s2YylKMCgYEA2UX+uO/q7jgqZ5UJW+ue
|
||||
1H5+W0aMuFA3i7JtZEnvRaUVFqFGlwXin/WJ2+WY1++k/rPrJ+Rk9IBXtBUIvEGw
|
||||
FC5TIfsKQsJyyWgqx/jbbtJ2g4s8+W/1qfTAuqeRNOg5d2DnRDs90wJuS4//0JaY
|
||||
9HkHyVwkQyxFxhSA/AHEMJECgYA2MvyFR1O9bIk0D3I7GsA+xKLXa77Ua53MzIjw
|
||||
9i4CezBGDQpjCiFli/fI8am+jY5DnAtsDknvjoG24UAzLy5L0mk6IXMdB6SzYYut
|
||||
7ak5oahqW+Y9hxIj+XvLmtGQbphtxhJtLu35x75KoBpxSh6FZpmuTEccs31AVCYn
|
||||
eFM/DQKBgQDOPUwbLKqVi6ddFGgrV9MrWw+SWsDa43bPuyvYppMM3oqesvyaX1Dt
|
||||
qDvN7owaNxNM4OnfKcZr91z8YPVCFo4RbBif3DXRzjNNBlxEjHBtuMOikwvsmucN
|
||||
vIrbeEpjTiUMTEAr6PoTiVHjsfS8WAM6MDlF5M+2PNswDsBpa2yLgA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`),
|
||||
}
|
||||
|
||||
var SSHCertificates = map[string][]byte{
|
||||
// The following are corresponding certificates for the private keys above, signed by the CA key
|
||||
// Generated by the following commands:
|
||||
//
|
||||
// 1. Assumes "rsa" key above in file named "rsa", write out the public key to "rsa.pub":
|
||||
// ssh-keygen -y -f rsa > rsa.pu
|
||||
//
|
||||
// 2. Assumes "ca" key above in file named "ca", sign a cert for "rsa.pub":
|
||||
// ssh-keygen -s ca -h -n host.example.com -V +500w -I host.example.com-key rsa.pub
|
||||
"rsa": []byte(`ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLjYqmmuTSEmjVhSfLQphBSTJMLwIZhRgmpn8FHKLiEIAAAADAQABAAAAgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQAAAAAAAAAAAAAAAgAAABRob3N0LmV4YW1wbGUuY29tLWtleQAAABQAAAAQaG9zdC5leGFtcGxlLmNvbQAAAABZHN8UAAAAAGsjIYUAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC+D11D0hEbn2Vglv4YRJ8pZNyHjIGmvth3DWOQrq++2vH2MujmGQDxfr4SVE9GpMBlKU3lwGbpgIBxAg6yZcNSfo6PWVU9ACg6NMFO+yMzc2MaG+/naQdNjSewywF5j2rkNO2XOaViRVSrZroe2B/aY2LTV0jDl8nu5NOjwRs1/s7SLe5z1rw/X0dpmXk0qJY3gQhmR8HZZ1dhEkJUGwaBCPd0T8asSYf1Ag2rUD4aQ28r3q69mbwfWOOa6rMemVZruUV5dzHwVNVNtVv+ImtnYtz8m8g+K0plaGptHn3KsaOnASkh3tujhaE7kvc4HR9Igli9+76jhZie3h/dTN5zAAABDwAAAAdzc2gtcnNhAAABALeDea+60H6xJGhktAyosHaSY7AYzLocaqd8hJQjEIDifBwzoTlnBmcK9CxGhKuaoJFThdCLdaevCeOSuquh8HTkf+2ebZZc/G5T+2thPvPqmcuEcmMosWo+SIjYhbP3S6KD49aLC1X0kz8IBQeauFvURhkZ5ZjhA1L4aQYt9NjL73nqOl8PplRui+Ov5w8b4ldul4zOvYAFrzfcP6wnnXk3c1Zzwwf5wynD5jakO8GpYKBuhM7Z4crzkKSQjU3hla7xqgfomC5Gz4XbR2TNjcQiRrJQ0UlKtX3X3ObRCEhuvG0Kzjklhv+Ddw6txrhKjMjiSi/Yyius/AE8TmC1p4U= host.example.com
|
||||
`),
|
||||
}
|
||||
|
||||
var PEMEncryptedKeys = []struct {
|
||||
Name string
|
||||
EncryptionKey string
|
||||
PEMBytes []byte
|
||||
}{
|
||||
0: {
|
||||
Name: "rsa-encrypted",
|
||||
EncryptionKey: "r54-G0pher_t3st$",
|
||||
PEMBytes: []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,3E1714DE130BC5E81327F36564B05462
|
||||
|
||||
MqW88sud4fnWk/Jk3fkjh7ydu51ZkHLN5qlQgA4SkAXORPPMj2XvqZOv1v2LOgUV
|
||||
dUevUn8PZK7a9zbZg4QShUSzwE5k6wdB7XKPyBgI39mJ79GBd2U4W3h6KT6jIdWA
|
||||
goQpluxkrzr2/X602IaxLEre97FT9mpKC6zxKCLvyFWVIP9n3OSFS47cTTXyFr+l
|
||||
7PdRhe60nn6jSBgUNk/Q1lAvEQ9fufdPwDYY93F1wyJ6lOr0F1+mzRrMbH67NyKs
|
||||
rG8J1Fa7cIIre7ueKIAXTIne7OAWqpU9UDgQatDtZTbvA7ciqGsSFgiwwW13N+Rr
|
||||
hN8MkODKs9cjtONxSKi05s206A3NDU6STtZ3KuPDjFE1gMJODotOuqSM+cxKfyFq
|
||||
wxpk/CHYCDdMAVBSwxb/vraOHamylL4uCHpJdBHypzf2HABt+lS8Su23uAmL87DR
|
||||
yvyCS/lmpuNTndef6qHPRkoW2EV3xqD3ovosGf7kgwGJUk2ZpCLVteqmYehKlZDK
|
||||
r/Jy+J26ooI2jIg9bjvD1PZq+Mv+2dQ1RlDrPG3PB+rEixw6vBaL9x3jatCd4ej7
|
||||
XG7lb3qO9xFpLsx89tkEcvpGR+broSpUJ6Mu5LBCVmrvqHjvnDhrZVz1brMiQtU9
|
||||
iMZbgXqDLXHd6ERWygk7OTU03u+l1gs+KGMfmS0h0ZYw6KGVLgMnsoxqd6cFSKNB
|
||||
8Ohk9ZTZGCiovlXBUepyu8wKat1k8YlHSfIHoRUJRhhcd7DrmojC+bcbMIZBU22T
|
||||
Pl2ftVRGtcQY23lYd0NNKfebF7ncjuLWQGy+vZW+7cgfI6wPIbfYfP6g7QAutk6W
|
||||
KQx0AoX5woZ6cNxtpIrymaVjSMRRBkKQrJKmRp3pC/lul5E5P2cueMs1fj4OHTbJ
|
||||
lAUv88ywr+R+mRgYQlFW/XQ653f6DT4t6+njfO9oBcPrQDASZel3LjXLpjjYG/N5
|
||||
+BWnVexuJX9ika8HJiFl55oqaKb+WknfNhk5cPY+x7SDV9ywQeMiDZpr0ffeYAEP
|
||||
LlwwiWRDYpO+uwXHSFF3+JjWwjhs8m8g99iFb7U93yKgBB12dCEPPa2ZeH9wUHMJ
|
||||
sreYhNuq6f4iWWSXpzN45inQqtTi8jrJhuNLTT543ErW7DtntBO2rWMhff3aiXbn
|
||||
Uy3qzZM1nPbuCGuBmP9L2dJ3Z5ifDWB4JmOyWY4swTZGt9AVmUxMIKdZpRONx8vz
|
||||
I9u9nbVPGZBcou50Pa0qTLbkWsSL94MNXrARBxzhHC9Zs6XNEtwN7mOuii7uMkVc
|
||||
adrxgknBH1J1N+NX/eTKzUwJuPvDtA+Z5ILWNN9wpZT/7ed8zEnKHPNUexyeT5g3
|
||||
uw9z9jH7ffGxFYlx87oiVPHGOrCXYZYW5uoZE31SCBkbtNuffNRJRKIFeipmpJ3P
|
||||
7bpAG+kGHMelQH6b+5K1Qgsv4tpuSyKeTKpPFH9Av5nN4P1ZBm9N80tzbNWqjSJm
|
||||
S7rYdHnuNEVnUGnRmEUMmVuYZnNBEVN/fP2m2SEwXcP3Uh7TiYlcWw10ygaGmOr7
|
||||
MvMLGkYgQ4Utwnd98mtqa0jr0hK2TcOSFir3AqVvXN3XJj4cVULkrXe4Im1laWgp
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`),
|
||||
},
|
||||
|
||||
1: {
|
||||
Name: "dsa-encrypted",
|
||||
EncryptionKey: "qG0pher-dsa_t3st$",
|
||||
PEMBytes: []byte(`-----BEGIN DSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,7CE7A6E4A647DC01AF860210B15ADE3E
|
||||
|
||||
hvnBpI99Hceq/55pYRdOzBLntIEis02JFNXuLEydWL+RJBFDn7tA+vXec0ERJd6J
|
||||
G8JXlSOAhmC2H4uK3q2xR8/Y3yL95n6OIcjvCBiLsV+o3jj1MYJmErxP6zRtq4w3
|
||||
JjIjGHWmaYFSxPKQ6e8fs74HEqaeMV9ONUoTtB+aISmgaBL15Fcoayg245dkBvVl
|
||||
h5Kqspe7yvOBmzA3zjRuxmSCqKJmasXM7mqs3vIrMxZE3XPo1/fWKcPuExgpVQoT
|
||||
HkJZEoIEIIPnPMwT2uYbFJSGgPJVMDT84xz7yvjCdhLmqrsXgs5Qw7Pw0i0c0BUJ
|
||||
b7fDJ2UhdiwSckWGmIhTLlJZzr8K+JpjCDlP+REYBI5meB7kosBnlvCEHdw2EJkH
|
||||
0QDc/2F4xlVrHOLbPRFyu1Oi2Gvbeoo9EsM/DThpd1hKAlb0sF5Y0y0d+owv0PnE
|
||||
R/4X3HWfIdOHsDUvJ8xVWZ4BZk9Zk9qol045DcFCehpr/3hslCrKSZHakLt9GI58
|
||||
vVQJ4L0aYp5nloLfzhViZtKJXRLkySMKdzYkIlNmW1oVGl7tce5UCNI8Nok4j6yn
|
||||
IiHM7GBn+0nJoKTXsOGMIBe3ulKlKVxLjEuk9yivh/8=
|
||||
-----END DSA PRIVATE KEY-----
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@ import (
|
|||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// debugTransport if set, will print packet types as they go over the
|
||||
// wire. No message decoding is done, to minimize the impact on timing.
|
||||
const debugTransport = false
|
||||
|
||||
const (
|
||||
gcmCipherID = "aes128-gcm@openssh.com"
|
||||
aes128cbcID = "aes128-cbc"
|
||||
|
|
@ -22,7 +27,9 @@ type packetConn interface {
|
|||
// Encrypt and send a packet of data to the remote peer.
|
||||
writePacket(packet []byte) error
|
||||
|
||||
// Read a packet from the connection
|
||||
// Read a packet from the connection. The read is blocking,
|
||||
// i.e. if error is nil, then the returned byte slice is
|
||||
// always non-empty.
|
||||
readPacket() ([]byte, error)
|
||||
|
||||
// Close closes the write-side of the connection.
|
||||
|
|
@ -38,7 +45,7 @@ type transport struct {
|
|||
bufReader *bufio.Reader
|
||||
bufWriter *bufio.Writer
|
||||
rand io.Reader
|
||||
|
||||
isClient bool
|
||||
io.Closer
|
||||
}
|
||||
|
||||
|
|
@ -84,9 +91,38 @@ func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *transport) printPacket(p []byte, write bool) {
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
who := "server"
|
||||
if t.isClient {
|
||||
who = "client"
|
||||
}
|
||||
what := "read"
|
||||
if write {
|
||||
what = "write"
|
||||
}
|
||||
|
||||
log.Println(what, who, p[0])
|
||||
}
|
||||
|
||||
// Read and decrypt next packet.
|
||||
func (t *transport) readPacket() ([]byte, error) {
|
||||
return t.reader.readPacket(t.bufReader)
|
||||
func (t *transport) readPacket() (p []byte, err error) {
|
||||
for {
|
||||
p, err = t.reader.readPacket(t.bufReader)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if debugTransport {
|
||||
t.printPacket(p, false)
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
||||
|
|
@ -129,6 +165,9 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
|
|||
}
|
||||
|
||||
func (t *transport) writePacket(packet []byte) error {
|
||||
if debugTransport {
|
||||
t.printPacket(packet, true)
|
||||
}
|
||||
return t.writer.writePacket(t.bufWriter, t.rand, packet)
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +208,8 @@ func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transp
|
|||
},
|
||||
Closer: rwc,
|
||||
}
|
||||
t.isClient = isClient
|
||||
|
||||
if isClient {
|
||||
t.reader.dir = serverKeys
|
||||
t.writer.dir = clientKeys
|
||||
|
|
@ -226,6 +267,7 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac
|
|||
|
||||
c := &streamPacketCipher{
|
||||
mac: macModes[algs.MAC].new(macKey),
|
||||
etm: macModes[algs.MAC].etm,
|
||||
}
|
||||
c.macResult = make([]byte, c.mac.Size())
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
// http://stackoverflow.com/questions/6949667/what-are-the-real-rules-for-linux-usernames-on-centos-6-and-rhel-6
|
||||
// #NAME_REGEX="^[a-z][-a-z0-9_]*\$"
|
||||
usernameRule, _ = regexp.Compile("^[a-z_][a-z0-9_]{0,30}$")
|
||||
// Base username validation on Debians default: https://sources.debian.net/src/adduser/3.113%2Bnmu3/adduser.conf/#L85
|
||||
// -> NAME_REGEX="^[a-z][-a-z0-9_]*\$"
|
||||
// The length is limited to 32 characters. See man 8 useradd: https://linux.die.net/man/8/useradd
|
||||
usernameRule, _ = regexp.Compile("^[a-z_][-a-z0-9_]{0,31}$")
|
||||
}
|
||||
|
||||
func userSpecFile(user, file string) string {
|
||||
|
|
|
|||
2
ver
2
ver
|
|
@ -1 +1 @@
|
|||
v0.2
|
||||
v0.3.dev
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue