ssh 协议

介绍#

ssh 协议是一个远程控制的协议,具备加密性。

实现#

dropbear 是一个轻量级的 ssh 服务端,可以参考它的代码

编码格式#

ssh 对字符串,数字,布尔值等有自己的编码方式

数字#

4 byte 大端

字符串#

4 byte 的数字表示字符串的长度,之后接字符串的内容,不带 \0

布尔值#

1 byte

字符#

1 byte

大整数#

4 byte 大整数的 byte 数
如果大整数为负数,那么此处插入 1 byte 0x00
大整数 []byte

字符串序列 name-list#

把字符串用 , 隔开,然后合成一个大字符串,以上述字符串方法编码

包的组成#

ssh 协议的报文以一个包为单位
4 byte 数字表示接下来的内容大小
1 byte 数字表示 padding 大小
动态长度的正文区
padding 区域
所以正文大小等于 package 大小 -1- padding 大小

常量#

以下常量来自 dropbear 的 ssh.h 文件

/* message numbers */
#define SSH_MSG_DISCONNECT             1
#define SSH_MSG_IGNORE                 2
#define SSH_MSG_UNIMPLEMENTED          3
#define SSH_MSG_DEBUG                  4
#define SSH_MSG_SERVICE_REQUEST        5
#define SSH_MSG_SERVICE_ACCEPT         6
#define SSH_MSG_EXT_INFO               7
#define SSH_MSG_KEXINIT                20
#define SSH_MSG_NEWKEYS                21
#define SSH_MSG_KEXDH_INIT             30
#define SSH_MSG_KEXDH_REPLY            31

/* userauth message numbers */
#define SSH_MSG_USERAUTH_REQUEST            50
#define SSH_MSG_USERAUTH_FAILURE            51
#define SSH_MSG_USERAUTH_SUCCESS            52
#define SSH_MSG_USERAUTH_BANNER             53

/* packets 60-79 are method-specific, aren't one-one mapping */
#define SSH_MSG_USERAUTH_SPECIFIC_60   60

#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ   60

#define SSH_MSG_USERAUTH_PK_OK				60

/* keyboard interactive auth */
#define SSH_MSG_USERAUTH_INFO_REQUEST           60
#define SSH_MSG_USERAUTH_INFO_RESPONSE          61


/* If adding numbers here, check MAX_UNAUTH_PACKET_TYPE in process-packet.c
 * is still valid */

/* connect message numbers */
#define SSH_MSG_GLOBAL_REQUEST                  80
#define SSH_MSG_REQUEST_SUCCESS                 81
#define SSH_MSG_REQUEST_FAILURE                 82
#define SSH_MSG_CHANNEL_OPEN                    90
#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION       91 
#define SSH_MSG_CHANNEL_OPEN_FAILURE            92
#define SSH_MSG_CHANNEL_WINDOW_ADJUST           93
#define SSH_MSG_CHANNEL_DATA                    94
#define SSH_MSG_CHANNEL_EXTENDED_DATA           95
#define SSH_MSG_CHANNEL_EOF                     96
#define SSH_MSG_CHANNEL_CLOSE                   97
#define SSH_MSG_CHANNEL_REQUEST                 98
#define SSH_MSG_CHANNEL_SUCCESS                 99
#define SSH_MSG_CHANNEL_FAILURE                 100

/* extended data types */
#define SSH_EXTENDED_DATA_STDERR	1

/* disconnect codes */
#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT      1
#define SSH_DISCONNECT_PROTOCOL_ERROR                   2
#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED              3
#define SSH_DISCONNECT_RESERVED                         4
#define SSH_DISCONNECT_MAC_ERROR                        5
#define SSH_DISCONNECT_COMPRESSION_ERROR                6
#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE            7
#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED   8
#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE          9
#define SSH_DISCONNECT_CONNECTION_LOST                 10
#define SSH_DISCONNECT_BY_APPLICATION                  11
#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS            12
#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER          13
#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE  14
#define SSH_DISCONNECT_ILLEGAL_USER_NAME               15

/* rfc8308 */
#define SSH_EXT_INFO_S "ext-info-s"
#define SSH_EXT_INFO_C "ext-info-c"
#define SSH_SERVER_SIG_ALGS "server-sig-algs"

/* OpenSSH strict KEX feature */
#define SSH_STRICT_KEX_S "[email protected]"
#define SSH_STRICT_KEX_C "[email protected]"

/* service types */
#define SSH_SERVICE_USERAUTH "ssh-userauth"
#define SSH_SERVICE_USERAUTH_LEN 12
#define SSH_SERVICE_CONNECTION "ssh-connection"
#define SSH_SERVICE_CONNECTION_LEN 14

/* public/signature key types */
#define SSH_SIGNKEY_DSS "ssh-dss"
#define SSH_SIGNKEY_DSS_LEN 7
#define SSH_SIGNKEY_RSA "ssh-rsa"
#define SSH_SIGNKEY_RSA_LEN 7
#define SSH_SIGNKEY_ED25519 "ssh-ed25519"
#define SSH_SIGNKEY_ED25519_LEN 11
/* signature type */
#define SSH_SIGNATURE_RSA_SHA256 "rsa-sha2-256"

/* Agent commands. These aren't part of the spec, and are defined
 * only on the openssh implementation. */
#define SSH_AGENT_FAILURE			5
#define SSH_AGENT_SUCCESS			6
#define SSH2_AGENTC_REQUEST_IDENTITIES		11
#define SSH2_AGENT_IDENTITIES_ANSWER		12
#define SSH2_AGENTC_SIGN_REQUEST		13
#define SSH2_AGENT_SIGN_RESPONSE		14

#define SSH2_AGENT_FAILURE			30

/* Flags defined by OpenSSH U2F key/signature format */
#define SSH_SK_USER_PRESENCE_REQD       0x01
#define SSH_SK_USER_VERIFICATION_REQD   0x04
#define SSH_SK_RESIDENT_KEY             0x20

报文解析#

首先,客户端向服务端建立连接,服务端会向客户端发送如下数据,不经过上述 ssh 编码
serverBanner\r\n
比如
SSH-2.0-dropbear_2025.89\r\n
此时客户端应该向服务端发送 clientBanner\r\n ,比如
SSH-2.0-OpenSSH_for_Windows_9.5\r\n
之后服务端会发送 SSH_MSG_KEXINIT 消息,报文正文内容是
1 byte SSH_MSG_KEXINIT
16 byte Cookie
name-list 密钥协商算法
name-list 主机公钥算法
name-list 客户端向服务端的加密算法
name-list 服务端向客户端的加密算法
name-list 客户端向服务端的 mac 算法
name-list 服务端向客户端的 mac 算法
name-list 客户端向服务端的压缩算法
name-list 服务端向客户端的压缩算法
name-list 客户端向服务端的语言
name-list 服务端向客户端的语言
1 byte FirstKexPacketFollows
4 byte 保留字段
FirstKexPacketFollows 如果为 true 的话,发送端会向接受端发送 SSH_MSG_KEXINIT 包之后发送下一个包
之后客户端会发送自己的 SSH_MSG_KEXINIT 包
那么使用的对应算法就是客户端的 SSH_MSG_KEXINIT 第一个服务端也同时包含的项
假设选择了 DH 协商算法,那么客户端会发送一个 SSH_MSG_KEXDH_INIT 包,正文内容如下
1 byte SSH_MSG_KEXDH_INIT
大整数 e
e 是自己的 DH 公钥
之后服务端会发送 SSH_MSG_KEXDH_REPLY 包,正文内容如下
1 byte SSH_MSG_KEXDH_REPLY
字符串 公钥 []byte
大整数 服务端 DH 公钥
字符串 签名 []byte
公钥字符序列和签名字符串序列都是如下编码
字符串 名字
字符串 内容
比如公钥算法可能是 ssh-ed25519 ,签名算法可能是 ssh-ed25519
客户端需要计算一个 hash 值,然后判断公钥是否可以验证这个 hash 值,如果可以的话,那么可以证明这个消息是由主机发出的
hash 组成
clientBanner + serverBanner + 客户端 SSH_MSG_KEXINIT 包 + 服务端 SSH_MSG_KEXINIT 包 + 服务端公钥 + 客户端 DH 公钥 + 服务端 DH 公钥 + DH 协商密钥
如果验证成功,那么客户端发送一个 SSH_MSG_NEWKEYS 包,包正文内容只有一个字节的 SSH_MSG_NEWKEYS ,服务端也会发送一个 SSH_MSG_NEWKEYS 包,之后双方使用加密的消息