ssh: allow to configure public key auth algorithms

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2023-11-09 20:03:04 +01:00
parent f83600225b
commit c5c5860012
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
15 changed files with 160 additions and 46 deletions

View file

@ -147,6 +147,7 @@ The configuration file contains the following sections:
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values are: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`, `diffie-hellman-group16-sha512`, `diffie-hellman-group14-sha1`, `diffie-hellman-group1-sha1`. Default values: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`. SHA512 based KEXs are disabled by default because they are slow. If you set one or more moduli files, `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1` will be available.
- `ciphers`, list of strings. Allowed ciphers in preference order. Leave empty to use default values. The supported values are: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`, `aes128-cbc`, `aes192-cbc`, `aes256-cbc`, `3des-cbc`, `arcfour256`, `arcfour128`, `arcfour`. Default values: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`. Please note that the ciphers disabled by default are insecure, you should expect that an active attacker can recover plaintext if you enable them.
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values are: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`, `hmac-sha2-512-etm@openssh.com`, `hmac-sha2-512`, `hmac-sha1`, `hmac-sha1-96`. Default values: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`.
- `public_key_algorithms`, list of strings. Public key algorithms that the server will accept for client authentication. The supported values are: `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-rsa`, `ssh-dss`, `ssh-ed25519`, `sk-ssh-ed25519@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`. Default values: `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-ed25519`, `sk-ssh-ed25519@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`.
- `trusted_user_ca_keys`, list of public keys paths of certificate authorities that are trusted to sign user certificates for authentication. The paths can be absolute or relative to the configuration directory.
- `revoked_user_certs_file`, path to a file containing the revoked user certificates. The path can be absolute or relative to the configuration directory. It must contain a JSON list with the public key fingerprints of the revoked certificates. Example content: `["SHA256:bsBRHC/xgiqBJdSuvSTNpJNLTISP/G356jNMCRYC5Es","SHA256:119+8cL/HH+NLMawRsJx6CzPF1I3xC+jpM60bQHXGE8"]`. The revocation list can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. Default: "".
- `login_banner_file`, path to the login banner file. The contents of the specified file, if any, are sent to the remote user before authentication is allowed. It can be a path relative to the config dir or an absolute one. Leave empty to disable login banner.

2
go.mod
View file

@ -177,5 +177,5 @@ replace (
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20230820193955-e7243edeb89b
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20231109082937-60ac5813bca0
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20231109180513-aa0daef37eeb
)

4
go.sum
View file

@ -153,8 +153,8 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0 h1:EW9gIJRmt9lzk66Fhh4S8VEtURA6QHZqGeSRE9Nb2/U=
github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/drakkan/crypto v0.0.0-20231109082937-60ac5813bca0 h1:5UwG68raSmbWbZdCDOxxaQpzfRS/2/4XLjP+o5lOvt0=
github.com/drakkan/crypto v0.0.0-20231109082937-60ac5813bca0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
github.com/drakkan/crypto v0.0.0-20231109180513-aa0daef37eeb h1:2CkHnBtgdS29SoGR4SI9wkE711HRkC9983PNYi+vtKQ=
github.com/drakkan/crypto v0.0.0-20231109180513-aa0daef37eeb/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/drakkan/ftpserverlib v0.0.0-20230820193955-e7243edeb89b h1:sCtiYerLxfOQrSludkwGwwXLlSVHxpvfmyOxjCOf0ec=

View file

@ -263,6 +263,7 @@ func Init() {
KexAlgorithms: []string{},
Ciphers: []string{},
MACs: []string{},
PublicKeyAlgorithms: []string{},
TrustedUserCAKeys: []string{},
RevokedUserCertsFile: "",
LoginBannerFile: "",
@ -2020,6 +2021,7 @@ func setViperDefaults() {
viper.SetDefault("sftpd.kex_algorithms", globalConf.SFTPD.KexAlgorithms)
viper.SetDefault("sftpd.ciphers", globalConf.SFTPD.Ciphers)
viper.SetDefault("sftpd.macs", globalConf.SFTPD.MACs)
viper.SetDefault("sftpd.public_key_algorithms", globalConf.SFTPD.PublicKeyAlgorithms)
viper.SetDefault("sftpd.trusted_user_ca_keys", globalConf.SFTPD.TrustedUserCAKeys)
viper.SetDefault("sftpd.revoked_user_certs_file", globalConf.SFTPD.RevokedUserCertsFile)
viper.SetDefault("sftpd.login_banner_file", globalConf.SFTPD.LoginBannerFile)

View file

@ -28,8 +28,9 @@ import (
// Supported values for host keys, KEXs, ciphers, MACs
var (
supportedHostKeyAlgos = []string{ssh.KeyAlgoRSA}
supportedKexAlgos = []string{
supportedHostKeyAlgos = []string{ssh.KeyAlgoRSA}
supportedPublicKeyAlgos = []string{ssh.KeyAlgoRSA, ssh.KeyAlgoDSA}
supportedKexAlgos = []string{
"diffie-hellman-group16-sha512", "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha256", "diffie-hellman-group-exchange-sha1",
}
@ -45,17 +46,21 @@ var (
// SFTPDConfigs defines configurations for SFTPD
type SFTPDConfigs struct {
HostKeyAlgos []string `json:"host_key_algos,omitempty"`
Moduli []string `json:"moduli,omitempty"`
KexAlgorithms []string `json:"kex_algorithms,omitempty"`
Ciphers []string `json:"ciphers,omitempty"`
MACs []string `json:"macs,omitempty"`
HostKeyAlgos []string `json:"host_key_algos,omitempty"`
PublicKeyAlgos []string `json:"public_key_algos,omitempty"`
Moduli []string `json:"moduli,omitempty"`
KexAlgorithms []string `json:"kex_algorithms,omitempty"`
Ciphers []string `json:"ciphers,omitempty"`
MACs []string `json:"macs,omitempty"`
}
func (c *SFTPDConfigs) isEmpty() bool {
if len(c.HostKeyAlgos) > 0 {
return false
}
if len(c.PublicKeyAlgos) > 0 {
return false
}
if len(c.Moduli) > 0 {
return false
}
@ -76,6 +81,11 @@ func (*SFTPDConfigs) GetSupportedHostKeyAlgos() []string {
return supportedHostKeyAlgos
}
// GetSupportedPublicKeyAlgos returns the supported legacy public key algos
func (*SFTPDConfigs) GetSupportedPublicKeyAlgos() []string {
return supportedPublicKeyAlgos
}
// GetSupportedKEXAlgos returns the supported KEX algos
func (*SFTPDConfigs) GetSupportedKEXAlgos() []string {
return supportedKexAlgos
@ -129,12 +139,19 @@ func (c *SFTPDConfigs) validate() error {
return util.NewValidationError(fmt.Sprintf("unsupported MAC algorithm %q", mac))
}
}
for _, algo := range c.PublicKeyAlgos {
if !util.Contains(supportedPublicKeyAlgos, algo) {
return util.NewValidationError(fmt.Sprintf("unsupported public key algorithm %q", algo))
}
}
return nil
}
func (c *SFTPDConfigs) getACopy() *SFTPDConfigs {
hostKeys := make([]string, len(c.HostKeyAlgos))
copy(hostKeys, c.HostKeyAlgos)
publicKeys := make([]string, len(c.PublicKeyAlgos))
copy(publicKeys, c.PublicKeyAlgos)
moduli := make([]string, len(c.Moduli))
copy(moduli, c.Moduli)
kexs := make([]string, len(c.KexAlgorithms))
@ -145,11 +162,12 @@ func (c *SFTPDConfigs) getACopy() *SFTPDConfigs {
copy(macs, c.MACs)
return &SFTPDConfigs{
HostKeyAlgos: hostKeys,
Moduli: moduli,
KexAlgorithms: kexs,
Ciphers: ciphers,
MACs: macs,
HostKeyAlgos: hostKeys,
PublicKeyAlgos: publicKeys,
Moduli: moduli,
KexAlgorithms: kexs,
Ciphers: ciphers,
MACs: macs,
}
}

View file

@ -7823,7 +7823,8 @@ func TestLoaddata(t *testing.T) {
}
configs := dataprovider.Configs{
SFTPD: &dataprovider.SFTPDConfigs{
HostKeyAlgos: []string{ssh.KeyAlgoRSA, ssh.CertAlgoRSAv01},
HostKeyAlgos: []string{ssh.KeyAlgoRSA, ssh.CertAlgoRSAv01},
PublicKeyAlgos: []string{ssh.KeyAlgoDSA},
},
SMTP: &dataprovider.SMTPConfigs{
Host: "mail.example.com",
@ -7890,6 +7891,7 @@ func TestLoaddata(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, configs.SMTP, configsGet.SMTP)
assert.Equal(t, []string{ssh.KeyAlgoRSA}, configsGet.SFTPD.HostKeyAlgos)
assert.Equal(t, []string{ssh.KeyAlgoDSA}, configsGet.SFTPD.PublicKeyAlgos)
assert.Len(t, configsGet.SFTPD.Moduli, 0)
assert.Len(t, configsGet.SFTPD.KexAlgorithms, 0)
assert.Len(t, configsGet.SFTPD.Ciphers, 0)
@ -12722,6 +12724,7 @@ func TestWebConfigsMock(t *testing.T) {
// save SFTP configs
form.Set("sftp_host_key_algos", ssh.KeyAlgoRSA)
form.Add("sftp_host_key_algos", ssh.CertAlgoDSAv01)
form.Set("sftp_pub_key_algos", ssh.KeyAlgoDSA)
form.Set("sftp_moduli", "path 1 , path 2")
form.Set("form_action", "sftp_submit")
req, err = http.NewRequest(http.MethodPost, webConfigsPath, bytes.NewBuffer([]byte(form.Encode())))
@ -12733,6 +12736,7 @@ func TestWebConfigsMock(t *testing.T) {
assert.Contains(t, rr.Body.String(), ssh.CertAlgoDSAv01) // invalid algo
form.Set("sftp_host_key_algos", ssh.KeyAlgoRSA)
form.Add("sftp_host_key_algos", ssh.CertAlgoRSAv01)
form.Set("sftp_pub_key_algos", ssh.KeyAlgoDSA)
form.Set("sftp_kex_algos", "diffie-hellman-group18-sha512")
form.Add("sftp_kex_algos", "diffie-hellman-group16-sha512")
req, err = http.NewRequest(http.MethodPost, webConfigsPath, bytes.NewBuffer([]byte(form.Encode())))
@ -12747,6 +12751,8 @@ func TestWebConfigsMock(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, configs.SFTPD.HostKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.HostKeyAlgos, ssh.KeyAlgoRSA)
assert.Len(t, configs.SFTPD.PublicKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.PublicKeyAlgos, ssh.KeyAlgoDSA)
assert.Len(t, configs.SFTPD.Moduli, 2)
assert.Contains(t, configs.SFTPD.Moduli, "path 1")
assert.Contains(t, configs.SFTPD.Moduli, "path 2")
@ -12795,6 +12801,8 @@ func TestWebConfigsMock(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, configs.SFTPD.HostKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.HostKeyAlgos, ssh.KeyAlgoRSA)
assert.Len(t, configs.SFTPD.PublicKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.PublicKeyAlgos, ssh.KeyAlgoDSA)
assert.Len(t, configs.SFTPD.Moduli, 2)
assert.Equal(t, "mail.example.net", configs.SMTP.Host)
assert.Equal(t, 587, configs.SMTP.Port)
@ -12865,6 +12873,8 @@ func TestWebConfigsMock(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, configs.SFTPD.HostKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.HostKeyAlgos, ssh.KeyAlgoRSA)
assert.Len(t, configs.SFTPD.PublicKeyAlgos, 1)
assert.Contains(t, configs.SFTPD.PublicKeyAlgos, ssh.KeyAlgoDSA)
assert.Len(t, configs.SFTPD.Moduli, 2)
assert.Equal(t, 80, configs.ACME.HTTP01Challenge.Port)
assert.Equal(t, 7, configs.ACME.Protocols)
@ -12896,6 +12906,7 @@ func TestWebConfigsMock(t *testing.T) {
configs, err = dataprovider.GetConfigs()
assert.NoError(t, err)
assert.Len(t, configs.SFTPD.HostKeyAlgos, 1)
assert.Len(t, configs.SFTPD.PublicKeyAlgos, 1)
assert.Equal(t, 402, configs.ACME.HTTP01Challenge.Port)
assert.Equal(t, 1, configs.ACME.Protocols)
assert.Equal(t, domain, configs.ACME.Domain)

View file

@ -2553,11 +2553,12 @@ func getIPListEntryFromPostFields(r *http.Request, listType dataprovider.IPListT
func getSFTPConfigsFromPostFields(r *http.Request) *dataprovider.SFTPDConfigs {
return &dataprovider.SFTPDConfigs{
HostKeyAlgos: r.Form["sftp_host_key_algos"],
Moduli: getSliceFromDelimitedValues(r.Form.Get("sftp_moduli"), ","),
KexAlgorithms: r.Form["sftp_kex_algos"],
Ciphers: r.Form["sftp_ciphers"],
MACs: r.Form["sftp_macs"],
HostKeyAlgos: r.Form["sftp_host_key_algos"],
PublicKeyAlgos: r.Form["sftp_pub_key_algos"],
Moduli: getSliceFromDelimitedValues(r.Form.Get("sftp_moduli"), ","),
KexAlgorithms: r.Form["sftp_kex_algos"],
Ciphers: r.Form["sftp_ciphers"],
MACs: r.Form["sftp_macs"],
}
}

View file

@ -1879,13 +1879,15 @@ func TestConfigsFromProvider(t *testing.T) {
assert.Len(t, c.KexAlgorithms, 0)
assert.Len(t, c.Ciphers, 0)
assert.Len(t, c.MACs, 0)
assert.Len(t, c.PublicKeyAlgorithms, 0)
configs := dataprovider.Configs{
SFTPD: &dataprovider.SFTPDConfigs{
HostKeyAlgos: []string{ssh.KeyAlgoRSA},
Moduli: []string{"/etc/ssh/moduli"},
KexAlgorithms: []string{kexDHGroupExchangeSHA256},
Ciphers: []string{"aes128-cbc", "aes192-cbc", "aes256-cbc"},
MACs: []string{"hmac-sha2-512-etm@openssh.com"},
HostKeyAlgos: []string{ssh.KeyAlgoRSA},
Moduli: []string{"/etc/ssh/moduli"},
KexAlgorithms: []string{kexDHGroupExchangeSHA256},
Ciphers: []string{"aes128-cbc", "aes192-cbc", "aes256-cbc"},
MACs: []string{"hmac-sha2-512-etm@openssh.com"},
PublicKeyAlgos: []string{ssh.KeyAlgoDSA},
},
}
err = dataprovider.UpdateConfigs(&configs, "", "", "")
@ -1896,11 +1898,13 @@ func TestConfigsFromProvider(t *testing.T) {
expectedKEXs := append(preferredKexAlgos, configs.SFTPD.KexAlgorithms...)
expectedCiphers := append(preferredCiphers, configs.SFTPD.Ciphers...)
expectedMACs := append(preferredMACs, configs.SFTPD.MACs...)
expectedPublicKeyAlgos := append(preferredPublicKeyAlgos, configs.SFTPD.PublicKeyAlgos...)
assert.Equal(t, expectedHostKeyAlgos, c.HostKeyAlgorithms)
assert.Equal(t, expectedKEXs, c.KexAlgorithms)
assert.Equal(t, expectedCiphers, c.Ciphers)
assert.Equal(t, expectedMACs, c.MACs)
assert.Equal(t, configs.SFTPD.Moduli, c.Moduli)
assert.Equal(t, expectedPublicKeyAlgos, c.PublicKeyAlgorithms)
err = dataprovider.UpdateConfigs(nil, "", "", "")
assert.NoError(t, err)

View file

@ -69,6 +69,19 @@ var (
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
ssh.KeyAlgoED25519,
}
supportedPublicKeyAlgos = []string{
ssh.KeyAlgoED25519,
ssh.KeyAlgoSKED25519, ssh.KeyAlgoSKECDSA256,
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSA,
ssh.KeyAlgoDSA,
}
preferredPublicKeyAlgos = []string{
ssh.KeyAlgoED25519,
ssh.KeyAlgoSKED25519, ssh.KeyAlgoSKECDSA256,
ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512,
}
supportedKexAlgos = []string{
"curve25519-sha256", "curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
@ -171,6 +184,8 @@ type Configuration struct {
// MACs Specifies the available MAC (message authentication code) algorithms
// in preference order
MACs []string `json:"macs" mapstructure:"macs"`
// PublicKeyAlgorithms lists the supported public key algorithms for client authentication.
PublicKeyAlgorithms []string `json:"public_key_algorithms" mapstructure:"public_key_algorithms"`
// TrustedUserCAKeys specifies a list of public keys paths of certificate authorities
// that are trusted to sign user certificates for authentication.
// The paths can be absolute or relative to the configuration directory
@ -318,6 +333,12 @@ func (c *Configuration) loadFromProvider() error {
}
c.HostKeyAlgorithms = append(c.HostKeyAlgorithms, configs.SFTPD.HostKeyAlgos...)
}
if len(configs.SFTPD.PublicKeyAlgos) > 0 {
if len(c.PublicKeyAlgorithms) == 0 {
c.PublicKeyAlgorithms = preferredPublicKeyAlgos
}
c.PublicKeyAlgorithms = append(c.PublicKeyAlgorithms, configs.SFTPD.PublicKeyAlgos...)
}
c.Moduli = append(c.Moduli, configs.SFTPD.Moduli...)
if len(configs.SFTPD.KexAlgorithms) > 0 {
if len(c.KexAlgorithms) == 0 {
@ -441,7 +462,7 @@ func (c *Configuration) serve(listener net.Listener, serverConfig *ssh.ServerCon
}
}
func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) error {
func (c *Configuration) configureKeyAlgos(serverConfig *ssh.ServerConfig) error {
if len(c.HostKeyAlgorithms) == 0 {
c.HostKeyAlgorithms = preferredHostKeyAlgos
} else {
@ -453,6 +474,27 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
}
}
if len(c.PublicKeyAlgorithms) > 0 {
c.PublicKeyAlgorithms = util.RemoveDuplicates(c.PublicKeyAlgorithms, true)
for _, algo := range c.PublicKeyAlgorithms {
if !util.Contains(supportedPublicKeyAlgos, algo) {
return fmt.Errorf("unsupported public key authentication algorithm %q", algo)
}
}
} else {
c.PublicKeyAlgorithms = preferredPublicKeyAlgos
}
serverConfig.PublicKeyAuthAlgorithms = c.PublicKeyAlgorithms
serviceStatus.PublicKeyAlgorithms = c.PublicKeyAlgorithms
return nil
}
func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) error {
if err := c.configureKeyAlgos(serverConfig); err != nil {
return err
}
if len(c.KexAlgorithms) > 0 {
hasDHGroupKEX := util.Contains(supportedKexAlgos, kexDHGroupExchangeSHA256)
if !hasDHGroupKEX {
@ -468,11 +510,12 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
return fmt.Errorf("unsupported key-exchange algorithm %q", kex)
}
}
serverConfig.KeyExchanges = c.KexAlgorithms
serviceStatus.KexAlgorithms = c.KexAlgorithms
} else {
serviceStatus.KexAlgorithms = preferredKexAlgos
c.KexAlgorithms = preferredKexAlgos
}
serverConfig.KeyExchanges = c.KexAlgorithms
serviceStatus.KexAlgorithms = c.KexAlgorithms
if len(c.Ciphers) > 0 {
c.Ciphers = util.RemoveDuplicates(c.Ciphers, true)
for _, cipher := range c.Ciphers {
@ -480,11 +523,12 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
return fmt.Errorf("unsupported cipher %q", cipher)
}
}
serverConfig.Ciphers = c.Ciphers
serviceStatus.Ciphers = c.Ciphers
} else {
serviceStatus.Ciphers = preferredCiphers
c.Ciphers = preferredCiphers
}
serverConfig.Ciphers = c.Ciphers
serviceStatus.Ciphers = c.Ciphers
if len(c.MACs) > 0 {
c.MACs = util.RemoveDuplicates(c.MACs, true)
for _, mac := range c.MACs {
@ -492,11 +536,12 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
return fmt.Errorf("unsupported MAC algorithm %q", mac)
}
}
serverConfig.MACs = c.MACs
serviceStatus.MACs = c.MACs
} else {
serviceStatus.MACs = preferredMACs
c.MACs = preferredMACs
}
serverConfig.MACs = c.MACs
serviceStatus.MACs = c.MACs
return nil
}

View file

@ -77,14 +77,15 @@ func (h *HostKey) GetAlgosAsString() string {
// ServiceStatus defines the service status
type ServiceStatus struct {
IsActive bool `json:"is_active"`
Bindings []Binding `json:"bindings"`
SSHCommands []string `json:"ssh_commands"`
HostKeys []HostKey `json:"host_keys"`
Authentications []string `json:"authentications"`
MACs []string `json:"macs"`
KexAlgorithms []string `json:"kex_algorithms"`
Ciphers []string `json:"ciphers"`
IsActive bool `json:"is_active"`
Bindings []Binding `json:"bindings"`
SSHCommands []string `json:"ssh_commands"`
HostKeys []HostKey `json:"host_keys"`
Authentications []string `json:"authentications"`
MACs []string `json:"macs"`
KexAlgorithms []string `json:"kex_algorithms"`
Ciphers []string `json:"ciphers"`
PublicKeyAlgorithms []string `json:"public_key_algorithms"`
}
// GetSSHCommandsAsString returns enabled SSH commands as comma separated string
@ -112,6 +113,12 @@ func (s *ServiceStatus) GetCiphersAsString() string {
return strings.Join(s.Ciphers, ", ")
}
// GetPublicKeysAlgosAsString returns enabled public key authentication
// algorithms as comma separated string
func (s *ServiceStatus) GetPublicKeysAlgosAsString() string {
return strings.Join(s.PublicKeyAlgorithms, ", ")
}
// GetStatus returns the server status
func GetStatus() ServiceStatus {
return serviceStatus

View file

@ -438,6 +438,12 @@ func TestInitialization(t *testing.T) {
assert.Contains(t, err.Error(), "unsupported key-exchange algorithm")
}
sftpdConf.KexAlgorithms = nil
sftpdConf.PublicKeyAlgorithms = []string{"not a pub key algo"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported public key authentication algorithm")
}
sftpdConf.PublicKeyAlgorithms = nil
sftpdConf.HostKeyAlgorithms = []string{"not a host key algo"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
@ -581,6 +587,7 @@ func TestBasicSFTPHandling(t *testing.T) {
assert.NotEmpty(t, status.GetMACsAsString())
assert.NotEmpty(t, status.GetKEXsAsString())
assert.NotEmpty(t, status.GetCiphersAsString())
assert.NotEmpty(t, status.GetPublicKeysAlgosAsString())
}
func TestBasicSFTPFsHandling(t *testing.T) {

View file

@ -6505,6 +6505,10 @@ components:
type: array
items:
$ref: '#/components/schemas/SSHAuthentications'
public_key_algorithms:
type: array
items:
type: string
macs:
type: array
items:

View file

@ -91,6 +91,7 @@
"kex_algorithms": [],
"ciphers": [],
"macs": [],
"public_key_algorithms": [],
"trusted_user_ca_keys": [],
"revoked_user_certs_file": "",
"login_banner_file": "",

View file

@ -65,6 +65,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
</div>
<div class="form-group row">
<label for="idPubKeyAlgos" class="col-sm-2 col-form-label">Public Key Algos</label>
<div class="col-sm-10">
<select class="form-control selectpicker" id="idPubKeyAlgos" name="sftp_pub_key_algos" multiple>
{{range $val := .Configs.SFTPD.GetSupportedPublicKeyAlgos}}
<option value="{{$val}}" {{range $algo :=$.Configs.SFTPD.PublicKeyAlgos }}{{if eq $algo $val}}selected{{end}}{{end}}>{{$val}}</option>
{{end}}
</select>
</div>
</div>
<div class="form-group row">
<label for="idModuli" class="col-sm-2 col-form-label">Moduli</label>
<div class="col-sm-10">

View file

@ -50,9 +50,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<br>
{{end}}
<br>
MAC algorithms: "{{.Status.SSH.GetMACsAsString}}"
Public key authentication algorithms: "{{.Status.SSH.GetPublicKeysAlgosAsString}}"
<br><br>
KEX algorithms: "{{.Status.SSH.GetKEXsAsString}}"
Message authentication algorithms: "{{.Status.SSH.GetMACsAsString}}"
<br><br>
Key exchange algorithms: "{{.Status.SSH.GetKEXsAsString}}"
<br><br>
Ciphers: "{{.Status.SSH.GetCiphersAsString}}"
<br>