Golang 的 ssh 包 与 Open-SSH 的兼容性

GoSSHD

golang.org/x/crypto/ssh 中的 ServerConfigPublicKeyCallback 字段,用于接受客户端发过来的公钥,做一个验证;

但是参数只有一个 PublicKey 以及网络元信息,好像也只能检测一下 authorized_keys 中有没有该公钥了。

最近发现使用 Open-SSH 客户端来连接自己用 golang 写的 SSH 服务器总是无法调用 PublicKeyCallback;大致看了一下源码,觉得可能原因如下:

公钥身份认证

Open-SSH 客户端通过服务端返回的失败消息中包含的方法列表,来进行下一步处理,如果列表中包含 publickey,那么 Open-SSH 会中断连接;如果列表中包含 password, 就会再次尝试重新输入密码来认证;而方法列表是 ServerConfig 设置的回调函数决定的。

而 Open-SSH 的认证顺序就是 none -> password,至于 publickey 方式在哪里,反正我在debug过程中没看见。。。

一句话就是:Open-SSH 第一次发送的认证方式为 none,如果服务器没通过认证,而且 Open-SSH 收到失败消息中的可用方法列表中没有 password ,那么之后就 Open-SSH 就不再尝试连接了。。。

自始至终 Open-SSH 都不会动用密钥身份验证方式来连接用 golang ssh 包编写的服务器。

难道是 Open-SSH 使用 none 作为密钥身份认证的标识?🤔

过程如下

golang ssh 身份验证逻辑

在 golang golang.org/x/crypto/sshserver.go 中,身份验证放在一个 for 循环中:

如果失败次数达到最大值 MaxAuthTries,会发送一个 “失败太多次的消息” 来告知客户端停止连接并返回:

image-20220430230449204

紧接着是一段 switch 结构通过客户端的身份认证请求方式来执行认证过程,此处我们定义的 callback 函数将被调用:

image-20220430230637553

然后如果失败(对于服务器只设置了 PublickeyCallback 而客户端发送了 none 请求来说必定失败),会将认证方法添加到失败消息中:

image-20220430225145466

关于 userAuthFailureMsg

这个被定义于 RFC 4252 5.1. 中:https://datatracker.ietf.org/doc/html/rfc4252#section-5.1

If the server rejects the authentication request, it MUST respond with the following:

  byte         SSH_MSG_USERAUTH_FAILURE
  name-list    authentications that can continue
  boolean      partial success

It is RECOMMENDED that servers only include those 'method name' values in the name-list that are actually useful. However, it is not illegal to include 'method name' values that cannot be used to authenticate the user.

Already successfully completed authentications SHOULD NOT be included in the name-list, unless they should be performed again for some reason.

The value of 'partial success' MUST be TRUE if the authentication request to which this is a response was successful. It MUST be FALSE if the request was not successfully processed.

大概就是说如果身份验证失败,那么服务端要发送一个包含可用的身份验证方式列表的验证消息至客户端。

在 golang 中定义如下:

type userAuthFailureMsg struct {
   Methods        []string `sshtype:"51"`
   PartialSuccess bool
}

Open-SSH 的连接逻辑

使用 Open-SSH 客户端连接我们写的服务器,可以发现第一次请求总是为 none 请求,

image-20220430231613720

这是 AuthLogCallback 打印的记录:

image-20220430231905920

由于 ServerConfig.NoClientAuthfalse,所以身份验证失败;

紧接着 server.go 会生成一个 failureMsg ,包含可用的认证方法,它是通过回调函数是否为 nil 来判断的:

image-20220430232121221

由于我们的 config.PublickeyCallback 不为 nil,故它将被添加到 failureMsg 的失败消息的方法列表中,并发送至客户端:

image-20220430232109483

但 Open-SSH 客户端收到后就不再尝试了,会中断连接

image-20220430232623980

然后服务端也会因为网络断开产生 io.EOF 并返回:

image-20220430233515787

这也就导致了无法使用 Open-SSH 客户端通过 publickey 的方式连接至 golang.org/x/crypto/ssh 库编写的 SSH 服务器

对于 password 认证

Open-SSH 会

现在为服务器配置设置 PasswordCallback,那么第一次的 none 请求会导致包含 passwordpublickey 方法的认证失败消息被返回:

image-20220430233807683

但是回想我们平时使用 Open-SSH 连接服务器时,如果输错密码,会再次尝试,最多三次;此次并不向 publickey 认证那样直接断开连接,而是重新发送一个 password 方式认证消息:

image-20220430234230774

其实这里可以推测 Open-SSH 客户端就是通过失败消息中包含的 password 来尝试让用户重新输入密码的:

尝试输入密码 123456,发现进入 password 方式认证的代码块,并且 payload 数据长度为 6,之后的就是字符 12、...、6ascii 码:

image-20220430234623180


再插一嘴,我自己用 golang 写的客户端就可以成功调用服务器的 PublickKeyCallback

image-20220501002038803

不会有人用 fmt.Println 来调试吧?。。。那就是我了😅😅😅

第一次验证方式使用的也是 none

image-20220501003919349

第二次就是 publickey 验证了,并且附带了正确的 payload

image-20220501004007800

posted @ 2022-05-01 15:49  NIShoushun  阅读(457)  评论(0编辑  收藏  举报