You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ran the sample program (see bottom of issue summary) with a host that is healthy and reachable, and to which I can connect via OpenSSH from my terminal, as well as via older versions of the golang.org/x/crypto/ssh package.
What did you expect to see?
I expect to see "connection ok"
What did you see instead?
The program fails to authenticate via public key with
If the test program is used with -keys-sign-only, the connection is established as expected.
Other Observations
I added some print statements inside the ssh package and ran a git bisect and it seems that the "breaking" change was 5d542ad81a58c89581d596f49d0ba5d435481bcf. I'm quoting the word "breaking" since I'm only claiming it breaks from my perspective -- perhaps the current behavior (fail) was always supposed to be the way things work. That being said, the same keys are allowed by OpenSSH, so I suspect that the change really is breaking here.
The Signers returned by ssh/agent (when backed by an agent client)
didn't actually implement AlgorithmSigner but ParameterizedSigner, an
interface defined in an earlier version of CL 123955.
This breaks in cases like ours, when an agent presents a key of type ecdsa-sha2-nistp256-cert-v01@openssh.com, since:
The function pickSignatureAlgorithm receives a signer for which keyFormat is ecdsa-sha2-nistp256-cert-v01@openssh.com. In our client, this assigned value is returned from line 234.
The auth function then invokes agentKeyringSigner.SignWithAlgorithm (via the variable as) on line 278, passing the result of underlyingAlgo as the algorithm parameter. In our case, underlyingAlgo("ecdsa-sha2-nistp256-cert-v01@openssh.com") is ecdsa-sha2-nistp256.
The test program above shows that we can sidestep the issue entirely in our own code by explicitly wrapping the ssh.Signer instances returned by the agentKeyringSigner so that they implement onlySign, and not SignWithAlgorithm. This causes our keys to be wrapped in client_auth.go in values of type algorithmSignerWrapper, which then signs successfully and completes the connection.
Sample Program
Here is my demo I used to repro and hack around:
package main
// This is a demo program of an unexpected handshake issue in// golang.org/x/crypto/sshimport (
"flag""fmt""io""log""net""os""os/user""golang.org/x/crypto/ssh""golang.org/x/crypto/ssh/agent"
)
var (
address=flag.String("address", "", "host:port to which to connect")
keysSignOnly=flag.Bool("keys-sign-only", false, "If set, wrap agent keys so that they implement nothing more than ssh.Signer")
)
funcmain() {
iferr:=runMain(); err!=nil {
log.Fatal(err)
}
}
funcrunMain() error {
flag.Parse()
if*address=="" {
returnfmt.Errorf("need address")
}
agentClient, closeAgentClient, err:=openAgentClient()
iferr!=nil {
returnerr
}
user, err:=user.Current()
iferr!=nil {
returnerr
}
defercloseAgentClient()
clientConfig:= ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
BannerCallback: ssh.BannerDisplayStderr(),
Auth: []ssh.AuthMethod{
ssh.PublicKeysCallback(publicKeysCallback(agentClient.Signers)),
},
}
log.Printf("opening connection")
sshClient, err:=ssh.Dial("tcp", *address, &clientConfig)
iferr!=nil {
returnerr
}
defersshClient.Close()
log.Printf("connection ok")
returnnil
}
funcpublicKeysCallback(getSignersfunc() ([]ssh.Signer, error)) func() ([]ssh.Signer, error) {
if!*keysSignOnly {
returngetSigners
}
returnfunc() ([]ssh.Signer, error) {
signers, err:=getSigners()
iferr!=nil {
returnnil, err
}
varwrapped []ssh.Signerfor_, signer:=rangesigners {
wrapped=append(wrapped, signWrapper{signer: signer})
}
returnwrapped, nil
}
}
// signWrapper is an ssh.Signer but *not* an ssh.AlgorithmSigner, even if the// underlying signer is.typesignWrapperstruct {
signer ssh.Signer
}
func (wsignWrapper) PublicKey() ssh.PublicKey { returnw.signer.PublicKey() }
func (wsignWrapper) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
returnw.signer.Sign(rand, data)
}
funcopenAgentClient() (agent.Agent, func(), error) {
// sample code copied mainly from https://pkg.go.dev/golang.org/x/crypto@v0.0.0-20210817164053-32db794688a5/ssh/agent#Agentsocket:=os.Getenv("SSH_AUTH_SOCK")
conn, err:=net.Dial("unix", socket)
iferr!=nil {
returnnil, nil, err
}
closeFunc:=func() {
iferr:=conn.Close(); err!=nil {
log.Printf("could not close agent socket: %s", err)
}
}
returnagent.NewClient(conn), closeFunc, nil
}
The text was updated successfully, but these errors were encountered:
Actually confirming this is a duplicate of #52185 and is resolved with https://go-review.googlesource.com/c/crypto/+/404614/ . The merge appears to have snuck in Friday 5/13 in the afternoon, after I had drafted this issue, but I didn't think to re-check this morning :)
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes (as of this writing, latest is
golang.org/x/crypto@2cf3adece1227c48e1673f1c37d70357e1a6b9d3
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
Ran the sample program (see bottom of issue summary) with a host that is healthy and reachable, and to which I can connect via OpenSSH from my terminal, as well as via older versions of the
golang.org/x/crypto/ssh
package.What did you expect to see?
I expect to see "connection ok"
What did you see instead?
The program fails to authenticate via public key with
If the test program is used with
-keys-sign-only
, the connection is established as expected.Other Observations
I added some print statements inside the
ssh
package and ran agit bisect
and it seems that the "breaking" change was 5d542ad81a58c89581d596f49d0ba5d435481bcf. I'm quoting the word "breaking" since I'm only claiming it breaks from my perspective -- perhaps the current behavior (fail) was always supposed to be the way things work. That being said, the same keys are allowed by OpenSSH, so I suspect that the change really is breaking here.While most of the work in that change was around supporting rsa-sha2-256/512, it seems from the commit comment and this discussion that the change also made it so that keys returned by the
agentKeyringSigner
implementssh.AlgorithmSigner
. From the commit summary:This breaks in cases like ours, when an agent presents a key of type
ecdsa-sha2-nistp256-cert-v01@openssh.com
, since:pickSignatureAlgorithm
receives asigner
for whichkeyFormat
isecdsa-sha2-nistp256-cert-v01@openssh.com
. In our client, this assigned value is returned from line 234.auth
function then invokesagentKeyringSigner.SignWithAlgorithm
(via the variableas
) on line 278, passing the result ofunderlyingAlgo
as the algorithm parameter. In our case,underlyingAlgo("ecdsa-sha2-nistp256-cert-v01@openssh.com")
isecdsa-sha2-nistp256
.agentKeyringSigner.SignWithAlgorithm
function fails the test on line 774 (sinces.pub.Type() == "ecdsa-sha2-nistp256-cert-v01@openssh.com"
) and subsequently fails on line 785.ssh.Signer
instances returned by theagentKeyringSigner
so that they implement onlySign
, and notSignWithAlgorithm
. This causes our keys to be wrapped inclient_auth.go
in values of typealgorithmSignerWrapper
, which then signs successfully and completes the connection.Sample Program
Here is my demo I used to repro and hack around:
The text was updated successfully, but these errors were encountered: