Compare commits

...
8 Commits
Author SHA1 Message Date
Heschi Kreinick 325b735346 ssh/test: skip TestSSHCLIAuth on Windows
It's failing with a file permissions error:

sign_and_send_pubkey: signing using rsa-sha2-512 SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8\r\nBad permissions. Try removing permissions for user: UNKNOWN\\\\UNKNOWN (S-1-15-2-2) on file C:/b/s/w/ir/x/t/TestSSHCLIAuth1586735692/001/rsa.

For golang/go#64403

Change-Id: Iece8eac4a1ac349f9f7a273ac7389315cb96568e
Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-windows-amd64-longtest,x_crypto-go1.21-windows-amd64-longtest,x_crypto-go1.20-windows-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/545135
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Auto-Submit: Heschi Kreinick <heschi@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2023-11-27 16:29:38 +00:00
Gopher Robot 1eadac50a5 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I7fdfe509173c79a63d006b27d674f869a5baa2af
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/545098
Reviewed-by: Heschi Kreinick <heschi@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2023-11-27 16:00:28 +00:00
Randy Reddig b2d7c26edb ssh: add (*Client).DialContext method
This change adds DialContext to ssh.Client, which opens a TCP-IP
connection tunneled over the SSH connection. This is useful for
proxying network connections, e.g. setting
(net/http.Transport).DialContext.

Fixes golang/go#20288.

Change-Id: I110494c00962424ea803065535ebe2209364ac27
GitHub-Last-Rev: 3176984a71a9a1422702e3a071340ecfff71ff62
GitHub-Pull-Request: golang/crypto#260
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/504735
Run-TryBot: Nicola Murino <nicola.murino@gmail.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Commit-Queue: Nicola Murino <nicola.murino@gmail.com>
2023-11-27 15:35:52 +00:00
Nicola Murino 1c17e20020 ssh: fix certificate authentication with OpenSSH 7.2-7.7
OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512
in the "server-sig-algs" extension but doesn't support these
algorithms for certificate authentication, so if the server rejects
the key try to use the obtained algorithm as if "server-sig-algs" had
not been implemented.

Fixes golang/go#58371

Change-Id: Id49960d3dedd32a21e2c6c2689b1696e05398286
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/510155
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
2023-11-23 17:23:14 +00:00
Sebastiaan van Stijn 270bf2552c curve25519/internal/field/_asm: go mod tidy to fix x/sys version
Relates to CL 540537, which updated the dependency in the main module.

Change-Id: I9a745f4e03b5cf14fa62c4de63363ddf663b19fd
GitHub-Last-Rev: 836c39364e9fe4302bc26efc9dabc47680cb66d2
GitHub-Pull-Request: golang/crypto#277
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/541276
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2023-11-21 20:13:04 +00:00
Matt Dainty 1cf1811d71 ssh: use the correct token from the client
This fixes the case where AcceptSecContext is always called with the
first token sent by the client instead of the most recently sent one.
Previously, despite being being read from the client and unmarshalled,
it was never actually used.

Fixes golang/go#43875

Change-Id: I1967d9a107af03d6778a9437b48e785d61710ee5
GitHub-Last-Rev: 0d58e4d50014fac0a9ea1eef85489172137eb8aa
GitHub-Pull-Request: golang/crypto#176
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/286252
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Run-TryBot: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
2023-11-11 11:26:14 +00:00
Roland Shoemaker a2edfb5072 cryptobyte: fix ReadOptionalASN1Boolean
ReadOptionalASN1Boolean was completely broken, it would only work when
there were two BOOLEAN fields in a row, with the first being OPTIONAL
(which is itself invalid ASN.1 due to the ambiguity). This fixes it
to properly expect a BOOLEAN wrapped in a context-specific tag, as is
the case for all of the other ReadOptionalASN1* methods, and updates
its doc string.

This is a breaking change as it requires adding the tag field to
properly support context-specific tags. Given the method would
previously not work this seems like a reasonable breakage.

Fixes golang/go#43019

Change-Id: I42398256216c59988e249c90bc7aa668f64df945
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/274242
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
2023-11-09 20:53:37 +00:00
Bryan C. Mills ff15cd57d1 ssh: eliminate some goroutine leaks in tests and examples
This should fix the "Log in goroutine" panic seen in
https://build.golang.org/log/e42bf69fc002113dbccfe602a6c67fd52e8f31df,
as well as a few other related leaks. It also helps to verify that
none of the functions under test deadlock unexpectedly.

See https://go.dev/wiki/CodeReviewComments#goroutine-lifetimes.

Updates golang/go#58901.

Change-Id: Ica943444db381ae1accb80b101ea646e28ebf4f9
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/541095
Auto-Submit: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2023-11-09 17:49:15 +00:00
16 changed files with 272 additions and 77 deletions
+7 -6
View File
@@ -733,13 +733,14 @@ func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag
return true return true
} }
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or, // ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue. // explicitly tagged with tag into out and advances. If no element with a
// It reports whether the operation was successful. // matching tag is present, it sets "out" to defaultValue instead. It reports
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool { // whether the read was successful.
func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue bool) bool {
var present bool var present bool
var child String var child String
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) { if !s.ReadOptionalASN1(&child, &present, tag) {
return false return false
} }
@@ -748,7 +749,7 @@ func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
return true return true
} }
return s.ReadASN1Boolean(out) return child.ReadASN1Boolean(out)
} }
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool { func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
+22
View File
@@ -115,6 +115,28 @@ func TestReadASN1OptionalInteger(t *testing.T) {
} }
} }
const defaultBool = false
var optionalBoolTestData = []readASN1Test{
{"empty", []byte{}, 0xa0, true, false},
{"invalid", []byte{0xa1, 0x3, 0x1, 0x2, 0x7f}, 0xa1, false, defaultBool},
{"missing", []byte{0xa1, 0x3, 0x1, 0x1, 0x7f}, 0xa0, true, defaultBool},
{"present", []byte{0xa1, 0x3, 0x1, 0x1, 0xff}, 0xa1, true, true},
}
func TestReadASN1OptionalBoolean(t *testing.T) {
for _, test := range optionalBoolTestData {
t.Run(test.name, func(t *testing.T) {
in := String(test.in)
var out bool
ok := in.ReadOptionalASN1Boolean(&out, test.tag, defaultBool)
if ok != test.ok || ok && out != test.out.(bool) {
t.Errorf("in.ReadOptionalASN1Boolean() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
}
})
}
}
func TestReadASN1IntegerSigned(t *testing.T) { func TestReadASN1IntegerSigned(t *testing.T) {
testData64 := []struct { testData64 := []struct {
in []byte in []byte
+1 -1
View File
@@ -9,7 +9,7 @@ require (
require ( require (
golang.org/x/mod v0.8.0 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.14.0 // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.6.0 // indirect
) )
+4 -4
View File
@@ -26,21 +26,21 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+2 -2
View File
@@ -4,8 +4,8 @@ go 1.18
require ( require (
golang.org/x/net v0.10.0 // tagx:ignore golang.org/x/net v0.10.0 // tagx:ignore
golang.org/x/sys v0.14.0 golang.org/x/sys v0.15.0
golang.org/x/term v0.14.0 golang.org/x/term v0.15.0
) )
require golang.org/x/text v0.14.0 // indirect require golang.org/x/text v0.14.0 // indirect
+4 -4
View File
@@ -1,8 +1,8 @@
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+19 -1
View File
@@ -307,7 +307,10 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
} }
var methods []string var methods []string
var errSigAlgo error var errSigAlgo error
for _, signer := range signers {
origSignersLen := len(signers)
for idx := 0; idx < len(signers); idx++ {
signer := signers[idx]
pub := signer.PublicKey() pub := signer.PublicKey()
as, algo, err := pickSignatureAlgorithm(signer, extensions) as, algo, err := pickSignatureAlgorithm(signer, extensions)
if err != nil && errSigAlgo == nil { if err != nil && errSigAlgo == nil {
@@ -321,6 +324,21 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
if err != nil { if err != nil {
return authFailure, nil, err return authFailure, nil, err
} }
// OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512
// in the "server-sig-algs" extension but doesn't support these
// algorithms for certificate authentication, so if the server rejects
// the key try to use the obtained algorithm as if "server-sig-algs" had
// not been implemented if supported from the algorithm signer.
if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 {
if contains(as.Algorithms(), KeyAlgoRSA) {
// We retry using the compat algorithm after all signers have
// been tried normally.
signers = append(signers, &multiAlgorithmSigner{
AlgorithmSigner: as,
supportedAlgorithms: []string{KeyAlgoRSA},
})
}
}
if !ok { if !ok {
continue continue
} }
+8
View File
@@ -127,6 +127,14 @@ func isRSA(algo string) bool {
return contains(algos, underlyingAlgo(algo)) return contains(algos, underlyingAlgo(algo))
} }
func isRSACert(algo string) bool {
_, ok := certKeyAlgoNames[algo]
if !ok {
return false
}
return isRSA(algo)
}
// supportedPubKeyAuthAlgos specifies the supported client public key // supportedPubKeyAuthAlgos specifies the supported client public key
// authentication algorithms. Note that this doesn't include certificate types // authentication algorithms. Note that this doesn't include certificate types
// since those use the underlying algorithm. This list is sent to the client if // since those use the underlying algorithm. This list is sent to the client if
+16 -2
View File
@@ -16,6 +16,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
@@ -98,8 +99,15 @@ func ExampleNewServerConn() {
} }
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"]) log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
var wg sync.WaitGroup
defer wg.Wait()
// The incoming Request channel must be serviced. // The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs) wg.Add(1)
go func() {
ssh.DiscardRequests(reqs)
wg.Done()
}()
// Service the incoming Channel channel. // Service the incoming Channel channel.
for newChannel := range chans { for newChannel := range chans {
@@ -119,16 +127,22 @@ func ExampleNewServerConn() {
// Sessions have out-of-band requests such as "shell", // Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the // "pty-req" and "env". Here we handle only the
// "shell" request. // "shell" request.
wg.Add(1)
go func(in <-chan *ssh.Request) { go func(in <-chan *ssh.Request) {
for req := range in { for req := range in {
req.Reply(req.Type == "shell", nil) req.Reply(req.Type == "shell", nil)
} }
wg.Done()
}(requests) }(requests)
term := terminal.NewTerminal(channel, "> ") term := terminal.NewTerminal(channel, "> ")
wg.Add(1)
go func() { go func() {
defer channel.Close() defer func() {
channel.Close()
wg.Done()
}()
for { for {
line, err := term.ReadLine() line, err := term.ReadLine()
if err != nil { if err != nil {
+49 -42
View File
@@ -10,7 +10,6 @@ import (
"io" "io"
"sync" "sync"
"testing" "testing"
"time"
) )
func muxPair() (*mux, *mux) { func muxPair() (*mux, *mux) {
@@ -112,7 +111,11 @@ func TestMuxReadWrite(t *testing.T) {
magic := "hello world" magic := "hello world"
magicExt := "hello stderr" magicExt := "hello stderr"
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
_, err := s.Write([]byte(magic)) _, err := s.Write([]byte(magic))
if err != nil { if err != nil {
t.Errorf("Write: %v", err) t.Errorf("Write: %v", err)
@@ -152,13 +155,15 @@ func TestMuxChannelOverflow(t *testing.T) {
defer writer.Close() defer writer.Close()
defer mux.Close() defer mux.Close()
wDone := make(chan int, 1) var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err) t.Errorf("could not fill window: %v", err)
} }
writer.Write(make([]byte, 1)) writer.Write(make([]byte, 1))
wDone <- 1
}() }()
writer.remoteWin.waitWriterBlocked() writer.remoteWin.waitWriterBlocked()
@@ -175,7 +180,6 @@ func TestMuxChannelOverflow(t *testing.T) {
if _, err := reader.SendRequest("hello", true, nil); err == nil { if _, err := reader.SendRequest("hello", true, nil); err == nil {
t.Errorf("SendRequest succeeded.") t.Errorf("SendRequest succeeded.")
} }
<-wDone
} }
func TestMuxChannelCloseWriteUnblock(t *testing.T) { func TestMuxChannelCloseWriteUnblock(t *testing.T) {
@@ -184,20 +188,21 @@ func TestMuxChannelCloseWriteUnblock(t *testing.T) {
defer writer.Close() defer writer.Close()
defer mux.Close() defer mux.Close()
wDone := make(chan int, 1) var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err) t.Errorf("could not fill window: %v", err)
} }
if _, err := writer.Write(make([]byte, 1)); err != io.EOF { if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
t.Errorf("got %v, want EOF for unblock write", err) t.Errorf("got %v, want EOF for unblock write", err)
} }
wDone <- 1
}() }()
writer.remoteWin.waitWriterBlocked() writer.remoteWin.waitWriterBlocked()
reader.Close() reader.Close()
<-wDone
} }
func TestMuxConnectionCloseWriteUnblock(t *testing.T) { func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
@@ -206,20 +211,21 @@ func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
defer writer.Close() defer writer.Close()
defer mux.Close() defer mux.Close()
wDone := make(chan int, 1) var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
t.Errorf("could not fill window: %v", err) t.Errorf("could not fill window: %v", err)
} }
if _, err := writer.Write(make([]byte, 1)); err != io.EOF { if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
t.Errorf("got %v, want EOF for unblock write", err) t.Errorf("got %v, want EOF for unblock write", err)
} }
wDone <- 1
}() }()
writer.remoteWin.waitWriterBlocked() writer.remoteWin.waitWriterBlocked()
mux.Close() mux.Close()
<-wDone
} }
func TestMuxReject(t *testing.T) { func TestMuxReject(t *testing.T) {
@@ -227,7 +233,12 @@ func TestMuxReject(t *testing.T) {
defer server.Close() defer server.Close()
defer client.Close() defer client.Close()
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
ch, ok := <-server.incomingChannels ch, ok := <-server.incomingChannels
if !ok { if !ok {
t.Error("cannot accept channel") t.Error("cannot accept channel")
@@ -267,6 +278,7 @@ func TestMuxChannelRequest(t *testing.T) {
var received int var received int
var wg sync.WaitGroup var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1) wg.Add(1)
go func() { go func() {
for r := range server.incomingRequests { for r := range server.incomingRequests {
@@ -295,7 +307,6 @@ func TestMuxChannelRequest(t *testing.T) {
} }
if ok { if ok {
t.Errorf("SendRequest(no): %v", ok) t.Errorf("SendRequest(no): %v", ok)
} }
client.Close() client.Close()
@@ -389,13 +400,8 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
// Wait for the server to send the keepalive message and receive back a // Wait for the server to send the keepalive message and receive back a
// response. // response.
select { if err := <-kDone; err != nil {
case err := <-kDone: t.Fatal(err)
if err != nil {
t.Fatal(err)
}
case <-time.After(10 * time.Second):
t.Fatalf("server never received ack")
} }
// Confirm client hasn't closed. // Confirm client hasn't closed.
@@ -403,13 +409,9 @@ func TestMuxUnknownChannelRequests(t *testing.T) {
t.Fatalf("failed to send keepalive: %v", err) t.Fatalf("failed to send keepalive: %v", err)
} }
select { // Wait for the server to shut down.
case err := <-kDone: if err := <-kDone; err != nil {
if err != nil { t.Fatal(err)
t.Fatal(err)
}
case <-time.After(10 * time.Second):
t.Fatalf("server never shut down")
} }
} }
@@ -525,11 +527,7 @@ func TestMuxClosedChannel(t *testing.T) {
defer ch.Close() defer ch.Close()
// Wait for the server to close the channel and send the keepalive. // Wait for the server to close the channel and send the keepalive.
select { <-kDone
case <-kDone:
case <-time.After(10 * time.Second):
t.Fatalf("server never received ack")
}
// Make sure the channel closed. // Make sure the channel closed.
if _, ok := <-ch.incomingRequests; ok { if _, ok := <-ch.incomingRequests; ok {
@@ -541,22 +539,29 @@ func TestMuxClosedChannel(t *testing.T) {
t.Fatalf("failed to send keepalive: %v", err) t.Fatalf("failed to send keepalive: %v", err)
} }
select { // Wait for the server to shut down.
case <-kDone: <-kDone
case <-time.After(10 * time.Second):
t.Fatalf("server never shut down")
}
} }
func TestMuxGlobalRequest(t *testing.T) { func TestMuxGlobalRequest(t *testing.T) {
var sawPeek bool
var wg sync.WaitGroup
defer func() {
wg.Wait()
if !sawPeek {
t.Errorf("never saw 'peek' request")
}
}()
clientMux, serverMux := muxPair() clientMux, serverMux := muxPair()
defer serverMux.Close() defer serverMux.Close()
defer clientMux.Close() defer clientMux.Close()
var seen bool wg.Add(1)
go func() { go func() {
defer wg.Done()
for r := range serverMux.incomingRequests { for r := range serverMux.incomingRequests {
seen = seen || r.Type == "peek" sawPeek = sawPeek || r.Type == "peek"
if r.WantReply { if r.WantReply {
err := r.Reply(r.Type == "yes", err := r.Reply(r.Type == "yes",
append([]byte(r.Type), r.Payload...)) append([]byte(r.Type), r.Payload...))
@@ -586,10 +591,6 @@ func TestMuxGlobalRequest(t *testing.T) {
t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v", t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
ok, data, err) ok, data, err)
} }
if !seen {
t.Errorf("never saw 'peek' request")
}
} }
func TestMuxGlobalRequestUnblock(t *testing.T) { func TestMuxGlobalRequestUnblock(t *testing.T) {
@@ -739,7 +740,13 @@ func TestMuxMaxPacketSize(t *testing.T) {
t.Errorf("could not send packet") t.Errorf("could not send packet")
} }
go a.SendRequest("hello", false, nil) var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() {
a.SendRequest("hello", false, nil)
wg.Done()
}()
_, ok := <-b.incomingRequests _, ok := <-b.incomingRequests
if ok { if ok {
+3 -2
View File
@@ -337,7 +337,7 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
} }
func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *connection, func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, token []byte, s *connection,
sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) { sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) {
gssAPIServer := gssapiConfig.Server gssAPIServer := gssapiConfig.Server
defer gssAPIServer.DeleteSecContext() defer gssAPIServer.DeleteSecContext()
@@ -347,7 +347,7 @@ func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *c
outToken []byte outToken []byte
needContinue bool needContinue bool
) )
outToken, srcName, needContinue, err = gssAPIServer.AcceptSecContext(firstToken) outToken, srcName, needContinue, err = gssAPIServer.AcceptSecContext(token)
if err != nil { if err != nil {
return err, nil, nil return err, nil, nil
} }
@@ -369,6 +369,7 @@ func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *c
if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil {
return nil, nil, err return nil, nil, err
} }
token = userAuthGSSAPITokenReq.Token
} }
packet, err := s.transport.readPacket() packet, err := s.transport.readPacket()
if err != nil { if err != nil {
+59 -12
View File
@@ -13,6 +13,7 @@ import (
"io" "io"
"math/rand" "math/rand"
"net" "net"
"sync"
"testing" "testing"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
@@ -27,8 +28,14 @@ func dial(handler serverType, t *testing.T) *Client {
t.Fatalf("netPipe: %v", err) t.Fatalf("netPipe: %v", err)
} }
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer c1.Close() defer func() {
c1.Close()
wg.Done()
}()
conf := ServerConfig{ conf := ServerConfig{
NoClientAuth: true, NoClientAuth: true,
} }
@@ -39,7 +46,11 @@ func dial(handler serverType, t *testing.T) *Client {
t.Errorf("Unable to handshake: %v", err) t.Errorf("Unable to handshake: %v", err)
return return
} }
go DiscardRequests(reqs) wg.Add(1)
go func() {
DiscardRequests(reqs)
wg.Done()
}()
for newCh := range chans { for newCh := range chans {
if newCh.ChannelType() != "session" { if newCh.ChannelType() != "session" {
@@ -52,8 +63,10 @@ func dial(handler serverType, t *testing.T) *Client {
t.Errorf("Accept: %v", err) t.Errorf("Accept: %v", err)
continue continue
} }
wg.Add(1)
go func() { go func() {
handler(ch, inReqs, t) handler(ch, inReqs, t)
wg.Done()
}() }()
} }
if err := conn.Wait(); err != io.EOF { if err := conn.Wait(); err != io.EOF {
@@ -338,8 +351,13 @@ func TestServerWindow(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer session.Close() defer session.Close()
result := make(chan []byte)
serverStdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe failed: %v", err)
}
result := make(chan []byte)
go func() { go func() {
defer close(result) defer close(result)
echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes)) echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
@@ -355,10 +373,6 @@ func TestServerWindow(t *testing.T) {
result <- echoedBuf.Bytes() result <- echoedBuf.Bytes()
}() }()
serverStdin, err := session.StdinPipe()
if err != nil {
t.Fatalf("StdinPipe failed: %v", err)
}
written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes) written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
if err != nil { if err != nil {
t.Errorf("failed to copy origBuf to serverStdin: %v", err) t.Errorf("failed to copy origBuf to serverStdin: %v", err)
@@ -648,29 +662,44 @@ func TestSessionID(t *testing.T) {
User: "user", User: "user",
} }
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
srvErrCh := make(chan error, 1) srvErrCh := make(chan error, 1)
wg.Add(1)
go func() { go func() {
defer wg.Done()
conn, chans, reqs, err := NewServerConn(c1, serverConf) conn, chans, reqs, err := NewServerConn(c1, serverConf)
srvErrCh <- err srvErrCh <- err
if err != nil { if err != nil {
return return
} }
serverID <- conn.SessionID() serverID <- conn.SessionID()
go DiscardRequests(reqs) wg.Add(1)
go func() {
DiscardRequests(reqs)
wg.Done()
}()
for ch := range chans { for ch := range chans {
ch.Reject(Prohibited, "") ch.Reject(Prohibited, "")
} }
}() }()
cliErrCh := make(chan error, 1) cliErrCh := make(chan error, 1)
wg.Add(1)
go func() { go func() {
defer wg.Done()
conn, chans, reqs, err := NewClientConn(c2, "", clientConf) conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
cliErrCh <- err cliErrCh <- err
if err != nil { if err != nil {
return return
} }
clientID <- conn.SessionID() clientID <- conn.SessionID()
go DiscardRequests(reqs) wg.Add(1)
go func() {
DiscardRequests(reqs)
wg.Done()
}()
for ch := range chans { for ch := range chans {
ch.Reject(Prohibited, "") ch.Reject(Prohibited, "")
} }
@@ -738,6 +767,8 @@ func TestHostKeyAlgorithms(t *testing.T) {
serverConf.AddHostKey(testSigners["rsa"]) serverConf.AddHostKey(testSigners["rsa"])
serverConf.AddHostKey(testSigners["ecdsa"]) serverConf.AddHostKey(testSigners["ecdsa"])
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
connect := func(clientConf *ClientConfig, want string) { connect := func(clientConf *ClientConfig, want string) {
var alg string var alg string
clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error { clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
@@ -751,7 +782,11 @@ func TestHostKeyAlgorithms(t *testing.T) {
defer c1.Close() defer c1.Close()
defer c2.Close() defer c2.Close()
go NewServerConn(c1, serverConf) wg.Add(1)
go func() {
NewServerConn(c1, serverConf)
wg.Done()
}()
_, _, _, err = NewClientConn(c2, "", clientConf) _, _, _, err = NewClientConn(c2, "", clientConf)
if err != nil { if err != nil {
t.Fatalf("NewClientConn: %v", err) t.Fatalf("NewClientConn: %v", err)
@@ -785,7 +820,11 @@ func TestHostKeyAlgorithms(t *testing.T) {
defer c1.Close() defer c1.Close()
defer c2.Close() defer c2.Close()
go NewServerConn(c1, serverConf) wg.Add(1)
go func() {
NewServerConn(c1, serverConf)
wg.Done()
}()
clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"} clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
_, _, _, err = NewClientConn(c2, "", clientConf) _, _, _, err = NewClientConn(c2, "", clientConf)
if err == nil { if err == nil {
@@ -818,14 +857,22 @@ func TestServerClientAuthCallback(t *testing.T) {
User: someUsername, User: someUsername,
} }
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
wg.Add(1)
go func() { go func() {
defer wg.Done()
_, chans, reqs, err := NewServerConn(c1, serverConf) _, chans, reqs, err := NewServerConn(c1, serverConf)
if err != nil { if err != nil {
t.Errorf("server handshake: %v", err) t.Errorf("server handshake: %v", err)
userCh <- "error" userCh <- "error"
return return
} }
go DiscardRequests(reqs) wg.Add(1)
go func() {
DiscardRequests(reqs)
wg.Done()
}()
for ch := range chans { for ch := range chans {
ch.Reject(Prohibited, "") ch.Reject(Prohibited, "")
} }
+35
View File
@@ -5,6 +5,7 @@
package ssh package ssh
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -332,6 +333,40 @@ func (l *tcpListener) Addr() net.Addr {
return l.laddr return l.laddr
} }
// DialContext initiates a connection to the addr from the remote host.
//
// The provided Context must be non-nil. If the context expires before the
// connection is complete, an error is returned. Once successfully connected,
// any expiration of the context will not affect the connection.
//
// See func Dial for additional information.
func (c *Client) DialContext(ctx context.Context, n, addr string) (net.Conn, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
type connErr struct {
conn net.Conn
err error
}
ch := make(chan connErr)
go func() {
conn, err := c.Dial(n, addr)
select {
case ch <- connErr{conn, err}:
case <-ctx.Done():
if conn != nil {
conn.Close()
}
}
}()
select {
case res := <-ch:
return res.conn, res.err
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Dial initiates a connection to the addr from the remote host. // Dial initiates a connection to the addr from the remote host.
// The resulting connection has a zero LocalAddr() and RemoteAddr(). // The resulting connection has a zero LocalAddr() and RemoteAddr().
func (c *Client) Dial(n, addr string) (net.Conn, error) { func (c *Client) Dial(n, addr string) (net.Conn, error) {
+33
View File
@@ -5,7 +5,10 @@
package ssh package ssh
import ( import (
"context"
"net"
"testing" "testing"
"time"
) )
func TestAutoPortListenBroken(t *testing.T) { func TestAutoPortListenBroken(t *testing.T) {
@@ -18,3 +21,33 @@ func TestAutoPortListenBroken(t *testing.T) {
t.Errorf("version %q marked as broken", works) t.Errorf("version %q marked as broken", works)
} }
} }
func TestClientImplementsDialContext(t *testing.T) {
type ContextDialer interface {
DialContext(context.Context, string, string) (net.Conn, error)
}
// Belt and suspenders assertion, since package net does not
// declare a ContextDialer type.
var _ ContextDialer = &net.Dialer{}
var _ ContextDialer = &Client{}
}
func TestClientDialContextWithCancel(t *testing.T) {
c := &Client{}
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := c.DialContext(ctx, "tcp", "localhost:1000")
if err != context.Canceled {
t.Errorf("DialContext: got nil error, expected %v", context.Canceled)
}
}
func TestClientDialContextWithDeadline(t *testing.T) {
c := &Client{}
ctx, cancel := context.WithDeadline(context.Background(), time.Now())
defer cancel()
_, err := c.DialContext(ctx, "tcp", "localhost:1000")
if err != context.DeadlineExceeded {
t.Errorf("DialContext: got nil error, expected %v", context.DeadlineExceeded)
}
}
+6 -1
View File
@@ -9,6 +9,7 @@ package test
// direct-tcpip and direct-streamlocal functional tests // direct-tcpip and direct-streamlocal functional tests
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -46,7 +47,11 @@ func testDial(t *testing.T, n, listenAddr string, x dialTester) {
} }
}() }()
conn, err := sshConn.Dial(n, l.Addr().String()) ctx, cancel := context.WithCancel(context.Background())
conn, err := sshConn.DialContext(ctx, n, l.Addr().String())
// Canceling the context after dial should have no effect
// on the opened connection.
cancel()
if err != nil { if err != nil {
t.Fatalf("Dial: %v", err) t.Fatalf("Dial: %v", err)
} }
+4
View File
@@ -10,6 +10,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"golang.org/x/crypto/internal/testenv" "golang.org/x/crypto/internal/testenv"
@@ -34,6 +35,9 @@ func sshClient(t *testing.T) string {
} }
func TestSSHCLIAuth(t *testing.T) { func TestSSHCLIAuth(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skipf("always fails on Windows, see #64403")
}
sshCLI := sshClient(t) sshCLI := sshClient(t)
dir := t.TempDir() dir := t.TempDir()
keyPrivPath := filepath.Join(dir, "rsa") keyPrivPath := filepath.Join(dir, "rsa")