25 Commits
Author SHA1 Message Date
Nicola Murino bb41b3dbe6 ssh: improve DH GEX group selection using PreferredBits
Previously, the server selected the Diffie-Hellman group based solely on
the MaxBits value provided by the client. This resulted in suboptimal
modulus selection, often ignoring the client's PreferredBits or selecting
a larger-than-necessary group.

This change implements a "best fit" selection algorithm similar to
OpenSSH's choose_dh logic. It attempts to find the smallest available
group larger than or equal to the client's PreferredBits, falling back to
the largest available group within the accepted range if no group above
the preference is available.

Additionally, this commit caches the parsed Oakley groups using
sync.OnceValue, avoiding repeated big.Int parsing on every handshake
while keeping the cost out of package initialization.

This issue was found during a security audit by NCC Group Cryptography
Services, sponsored by Teleport, and was assessed and is being fixed as
a non-security bug.

Change-Id: Idfa81bbcf354a7fb7b541cb4bbeb6e4a0181398a
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/782424
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Mark Freeman <markfreeman@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
2026-06-02 00:23:03 -07:00
Nicola Murino 66c3d8ce71 ssh: add support for FIPS mode
Unsupported algoritms are silently ignored and not negotiated, or
rejected

Fixes golang/go#75061

Change-Id: I08d50d10a97c08e78aedead89ca61beceff88918
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/698795
Reviewed-by: Mio Mio <miomio0086@gmail.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-09-27 12:36:52 -07:00
Nicola Murino 8f580defa0 ssh: remove Go 1.24 build tag for ML-KEM kex
Change-Id: Ia77ad1b6fef9919ab100fb10c42231725eb81c12
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/698775
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2025-08-26 00:42:33 -07:00
Filippo Valsorda f5a2eabcab ssh: use curve25519.X25519 instead of curve25519.ScalarMult
This lets us surface an error message instead of panicking if running
in fips140=only mode, where ECDH on X25519 returns an error.

Updates golang/go#75061

Change-Id: I6a6a6964c0591f3dca2dc946c99d44364314a3ab
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/696995
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
2025-08-19 10:38:43 -07:00
Nicola Murino ebc8e46315 ssh: add server side support for Diffie Hellman Group Exchange
We add this support for the following reasons:

- We are planning to expose recommended (secure) vs. supported (works,
  not necessarily recommended) algorithms. The DHGEX kex is currently
  only exposed as a client-side kex. To simplify the calling convention
  for this follow-on, we expose the server side too.
- Some clients are quite inflexible with reference to kex algorithms
  choice, for example they offer:

  diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1,
  diffie-hellman-group14-sha1, diffie-hellman-group1-sha1

  therefore DHGEX helps interoperability.

We do not recommend the DHGEX kex as a whole:

- the negotiation requires an extra round trip
- the server must generate parameters (slow) or hardcode them, which
  defeats the security benefit over traditional DH.

In this implementation we hardcode sending Oakley Group 14, Oakley
Group 15 or Oakley Group 16 based on the requested max size.

Users that are concerned with security of classical DH kex should
migrate to kex based on EC or Ed25519.

Fixes golang/go#54743

Change-Id: I127822e90efc36821af4aca679931f40a2023021
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/532415
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-15 10:47:05 -07:00
Nicola Murino ac58737d25 ssh: export supported algorithms
Fixes golang/go#61537

Change-Id: If3478121e3ae445391e3faeceeb889d75e9e3214
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/531935
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2025-05-15 10:43:30 -07:00
Nicola Murino eab9315960 ssh: add diffie-hellman-group16-sha512 kex
This group is disabled by default because it is a bit slower than
the others.
The group18-sha512 variant is too slow to include.

Benchstat results including diffie-hellman-group18-sha512:

name                                           time/op
Kexes/diffie-hellman-group-exchange-sha256-12  22.6ms ± 9%
Kexes/diffie-hellman-group18-sha512-12          1.15s ±11%
Kexes/ecdh-sha2-nistp384-12                    3.91ms ± 6%
Kexes/ecdh-sha2-nistp256-12                     304µs ± 5%
Kexes/curve25519-sha256@libssh.org-12           413µs ± 7%
Kexes/ecdh-sha2-nistp521-12                    11.6ms ±13%
Kexes/curve25519-sha256-12                      361µs ± 5%
Kexes/diffie-hellman-group-exchange-sha1-12    22.9ms ± 9%
Kexes/diffie-hellman-group1-sha1-12            3.59ms ± 6%
Kexes/diffie-hellman-group14-sha1-12           22.1ms ±11%
Kexes/diffie-hellman-group14-sha256-12         21.6ms ± 8%
Kexes/diffie-hellman-group16-sha512-12          138ms ± 9%

name                                           alloc/op
Kexes/diffie-hellman-group-exchange-sha256-12  67.8kB ± 1%
Kexes/diffie-hellman-group18-sha512-12          243kB ± 9%
Kexes/ecdh-sha2-nistp384-12                    13.9kB ± 0%
Kexes/ecdh-sha2-nistp256-12                    12.1kB ± 0%
Kexes/curve25519-sha256@libssh.org-12          8.22kB ± 0%
Kexes/ecdh-sha2-nistp521-12                    16.5kB ± 0%
Kexes/curve25519-sha256-12                     8.22kB ± 0%
Kexes/diffie-hellman-group-exchange-sha1-12    67.5kB ± 0%
Kexes/diffie-hellman-group1-sha1-12            34.9kB ± 0%
Kexes/diffie-hellman-group14-sha1-12           61.9kB ± 0%
Kexes/diffie-hellman-group14-sha256-12         62.0kB ± 0%
Kexes/diffie-hellman-group16-sha512-12          117kB ± 0%

name                                           allocs/op
Kexes/diffie-hellman-group-exchange-sha256-12     314 ± 0%
Kexes/diffie-hellman-group18-sha512-12            271 ± 4%
Kexes/ecdh-sha2-nistp384-12                       243 ± 0%
Kexes/ecdh-sha2-nistp256-12                       213 ± 0%
Kexes/curve25519-sha256@libssh.org-12             168 ± 0%
Kexes/ecdh-sha2-nistp521-12                       245 ± 0%
Kexes/curve25519-sha256-12                        168 ± 0%
Kexes/diffie-hellman-group-exchange-sha1-12       314 ± 0%
Kexes/diffie-hellman-group1-sha1-12               255 ± 0%
Kexes/diffie-hellman-group14-sha1-12              255 ± 0%
Kexes/diffie-hellman-group14-sha256-12            255 ± 0%
Kexes/diffie-hellman-group16-sha512-12            256 ± 0%

Change-Id: Id119401fda7e417675325f37e3d442e70585206c
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/506839
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2023-07-31 17:46:52 +00:00
Filippo Valsorda 1baeb1ce4c ssh: don't advertise rsa-sha2 algorithms if we can't use them
The server implementation looks at the HostKeys to advertise and
negotiate host key signature algorithms. A fundamental issue of the
Signer and AlgorithmSigner interfaces is that they don't expose the
supported signature algorithms, so really the server has to guess.

Currently, it would guess exclusively based on the PublicKey.Type,
regardless of whether the host key implemented AlgorithmSigner. This
means that a legacy Signer that only supports ssh-rsa still led the
server to negotiate rsa-sha2 algorithms. The server would then fail to
find a suitable host key to make the signature and crash.

This won't happen if only Signers from this package are used, but if a
custom Signer that doesn't support SignWithAlgorithm() but returns
"ssh-rsa" from PublicKey().Type() is used as a HostKey, the server is
vulnerable to DoS.

The only workable rules to determine what to advertise seems to be:

   1. a pure Signer will always Sign with the PublicKey.Type

   2. an AlgorithmSigner supports all algorithms associated with the
      PublicKey.Type

Rule number two means that we can't add new supported algorithms in the
future, which is not great, but it's too late to fix that.

rsaSigner was breaking rule number one, and although it would have been
fine where it's used, I didn't want to break our own interface contract.

It's unclear why we had separate test key entries for rsa-sha2
algorithms, since we can use the ssh-rsa key for those. The only test
that used them, TestCertTypes, seemed broken: the init was actually
failing at making the corresponding signers rsaSigners, and indeed the
test for the SHA-256 signer expected and checked a SHA-512 signature.

Pending CVE
For golang/go#49952

Change-Id: Ie658eefcadd87906e63fc7faae8249376aa96c79
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392355
Trust: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-14 23:46:59 +00:00
Filippo Valsorda 4d399097f0 ssh: fix diffie-hellman-group-exchange g and K bounds checks
The previous code

	if gex.g.Cmp(one) != 1 && gex.g.Cmp(pMinusOne) != -1 {

deobfuscates to the classic mistake

    if g <= 1 && g >= p - 1 {

which is never true.

What the code actually intended to do is

	if gex.g.Cmp(one) != 1 || gex.g.Cmp(pMinusOne) != -1 {

or more readably and consistently with the diffieHellman method

	if gex.g.Cmp(one) <= 0 || gex.g.Cmp(pMinusOne) >= 0 {

Now, is this a security issue? The incorrect checks apply to g and k,
but not what we call Y and the spec calls f. RFC 4419 says:

   Either side MUST NOT send or accept e or f values that are not in the
   range [1, p-1].  If this condition is violated, the key exchange
   fails.  To prevent confinement attacks, they MUST accept the shared
   secret K only if 1 < K < p - 1.

Note that RFC 8268, Section 4 updates the equivalent RFC 4253 statement
(although not the RFC 4419 one) about e and f, but we are already doing
the correct full check there.

      DH Public Key values MUST be checked and both conditions:

         1 < e < p-1

         1 < f < p-1

      MUST be true.  Values not within these bounds MUST NOT be sent or
      accepted by either side.  If either one of these conditions is
      violated, then the key exchange fails.

   This simple check ensures that:

   o  The remote peer behaves properly.

   o  The local system is not forced into the two-element subgroup.

The check on K seems like a proxy for checking a number of ways to fix
the DH output (for example by manipulating g) to one of 0, 1, or -1.
This should not be meaningful to security for two reasons:

   - all parameters end up in the "transcript" hash that will get signed
     by the server's host key, and if the attacker controls the host
     key's signature, they have the ability to MitM without resorting to
     confinement attacks

   - the client secret is ephemeral, so leaking bits of it by forcing it
     into small sub-groups does not gain the attacker anything, as the
     secret does not get reused

Indeed, this is the same explanation of why it's ok not to check that p
is indeed a (safe) prime, which even OpenSSH omits. Building an
equivalent attack by manipulating p instead of g is left as an exercise
to the reader.

For the future, this is a case study in why we should not add complexity
even when it looks easy enough to do. CL 174257 added the
diffie-hellman-group-exchange kex. That introduced a data race (arguably
a security issue), which was fixed in CL 222078. Then it was too slow,
which led to CL 252337 that removed the primalty check, which required a
full analysis of whether it's safe to skip it, and checking against
other implementations. Now we find there's a bug and we have to do
another security analysis that not even the RFC bothered to do in order
to decide if it's a security issue. My decision in
https://github.com/golang/go/issues/17230#issuecomment-489163656
does not look like the right one in hindsight.

While at it, clean up the code some

   - drop useless bit size bounds logic in the server stub that get
     ignored by the rest of the function

   - make p and g local variables instead of method fields, since they
     are not persistent state (this was originally a data race which was
     fixed in CL 222078 by making Client not a pointer receiver)

Updates golang/go#17230

Change-Id: I4b1c68537109f627ccd75ec381dcfab57ce1768c
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392015
Trust: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-14 23:46:39 +00:00
Filippo ValsordaandNicola Murino e4b3678e5f ssh: add diffie-hellman-group14-sha256 kex
RFC 9142 made diffie-hellman-group14-sha256 from RFC 8268 a MUST, and
it's strictly better than diffie-hellman-group14-sha1, which we already
have, and trivial to add.

> The method of key exchange used for the name "diffie-hellman-
> group14-sha256" is the same as that for "diffie-hellman-group14-sha1"
> except that the SHA256 hash algorithm is used.

Ignore the bigger groups which have a meaningful performance cost, and
don't share the same interoperability benefit.

Adapted from CL 387994.

Fixes golang/go#31731

Co-authored-by: Nicola Murino <nicola.murino@gmail.com>
Change-Id: Id4ce345a2065840f193986739ea890f105a1e929
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/392014
Trust: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2022-03-14 23:46:33 +00:00
Михаил ПатинandFilippo Valsorda 9b076918e3 ssh: support new curve25519-sha256 kex name
RFC 8731 standardized curve25519-sha256@libssh.org as curve25519-sha256,
and some systems only advertise support for the new name.

Fixes golang/go#48756

Change-Id: Ice35874cd8c07ad48752686ac368bf11ab793f77
Co-authored-by: Filippo Valsorda <filippo@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/385394
Trust: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Trust: Matt Layher <mdlayher@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2022-03-14 23:46:23 +00:00
Daniel Cohen afb6bcd081 ssh: remove slow unnecessary diffie-hellman-group-exchange primality check
The existing implementation validates that the prime returned by the server is, in fact, prime, which is extremely slow, especially for large key sizes.

As other implementations, including OpenSSH, do not verify the primality of the provided parameters, this change removes that check.

Fixes golang/go#41151

Change-Id: I7539714c690f08b5792a0c540cbf46c3e81f13ba
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/252337
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Roland Shoemaker <roland@golang.org>
2020-09-30 16:06:38 +00:00
Lucas Bremgartner 1b76d66859 ssh: fix data race in dh group exchange sha256
Fixes golang/go#37607

Change-Id: Iedf6522ec9b9a676ac51c054407a6aef894885f5
GitHub-Last-Rev: 8cb2460c59d2e32bc3f0480bcd7867a113361c67
GitHub-Pull-Request: golang/crypto#126
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/222078
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-03-17 14:21:12 +00:00
Lars Lehtonen 6e5058baad ssh: fix dropped error
This fixes an error variable that was being dropped
prior to the return of dhGroup.Server().

Change-Id: I8ddef1832cd7a62a69b61bb1c98bc1ceb561f985
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/205420
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
2019-11-08 23:12:17 +00:00
Lucas Bremgartner 57b3e21c3d ssh: add diffie-hellman-group-exchange-sha256
Add the diffie-hellman-group-exchange-sha256 defined in RFC 4419 to
the list of supported key exchange algorithms for ssh.
The server half is only a minimal implementation to satisfy the automated tests.

Fixes golang/go#17230

Change-Id: I25880a564347fd9b4738dd2ed1e347cd5d2e21bb
GitHub-Last-Rev: 9f0b8d02c0c96e9baf00cdf1cf063ff834245443
GitHub-Pull-Request: golang/crypto#87
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/174257
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
2019-06-17 13:33:40 +00:00
Kevin Burke e8f229864d all: fix errors reported by vet, golint
None are "wrong" per se, but there are a lot of good suggestions and
in one case a docstring that was not present in godoc due to the
presence of an extra newline.

Changed "Id" in struct properties to "ID" in some non-exported
structs. Removed a trailing period from some error messages; I believe
the exact contents of error strings are not covered by the Go
compatibility promise.

Change-Id: I7c620582dc247396f72c52d38c909ccc0ec87b83
Reviewed-on: https://go-review.googlesource.com/80145
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-11-28 17:48:09 +00:00
Dave Cheney adbae1b6b6 all: gofmt ./...
Change-Id: I8ffee4dc712091e424b83a9f5a3cc2a6724abefc
Reviewed-on: https://go-review.googlesource.com/46050
Reviewed-by: Matt Layher <mdlayher@gmail.com>
2017-06-19 20:42:22 +00:00
Adam Langley dc7f3af03e 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>
2016-10-10 21:14:46 +00:00
Han-Wen Nienhuys d68c3ecb62 x/crypto/ssh: make sure the initial key exchange happens once.
This is done by running the key exchange and setting the session ID
under mutex. If the first exchange encounters an already set session
ID, then do nothing.

This fixes a race condition:

On setting up the connection, both sides sent a kexInit to initiate
the first (mandatory) key exchange.  If one side was faster, the
faster side might have completed the key exchange, before the slow
side had a chance to send a kexInit.  The slow side would send a
kexInit which would trigger a second key exchange. The resulting
confirmation message (msgNewKeys) would confuse the authentication
loop.

This fix removes sessionID from the transport struct.

This fix also deletes the unused interface rekeyingTransport.

Fixes #15066

Change-Id: I7f303bce5d3214c9bdd58f52d21178a185871d90
Reviewed-on: https://go-review.googlesource.com/21606
Reviewed-by: Adam Langley <agl@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
2016-04-12 17:14:56 +00:00
Carl Jackson 7b85b097bf x/crypto/ssh: run go fmt
Change-Id: Ibb071aa550ca7392543c2d1eba8f8a69ba1d86fa
Reviewed-on: https://go-review.googlesource.com/17270
Reviewed-by: Dave Cheney <dave@cheney.net>
2015-12-01 00:25:08 +00:00
hanwen aca188fab6 x/crypto/ssh: implement curve25519-sha256@libssh.org key agreement.
Fixes golang/go#11004.

Change-Id: Ic37cf9d620e3397b7ad769ae16abdaee63a7733b
Reviewed-on: https://go-review.googlesource.com/13592
Reviewed-by: Adam Langley <agl@golang.org>
2015-08-17 22:16:34 +00:00
Adam Langley fa50e7408b go.crypto/ssh: import gosshnew.
See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ

R=hanwen
CC=golang-codereviews
https://golang.org/cl/86190043
2014-04-09 13:57:52 -07:00
Jonathan Pittman 44256fa2df go.crypto/ssh: cosmetic only spelling fixes
R=agl, hanwen
CC=dave, golang-dev, jpsugar
https://golang.org/cl/14430055
2013-10-10 11:35:15 -04:00
Han-Wen Nienhuys 4147256c9c go.crypto/ssh: move interpretation of msgNewKeys into
transport.

Sending the msgNewKeys packet and setting up the key material
now happen under a lock, preventing races with concurrent
writers.

R=kardianos, agl, jpsugar, hanwenn
CC=golang-dev
https://golang.org/cl/14476043
2013-10-08 15:47:04 -04:00
Han-Wen Nienhuys 73e99326fb go.crypto/ssh: separate kex algorithms into kexAlgorithm class.
Adds readPacket() to conn, and renames conn to packetConn.
Key exchanges operate on packetConn, so they can be
unittested.

R=agl, jpsugar, dave
CC=golang-dev
https://golang.org/cl/13352055
2013-09-24 12:26:29 -04:00