sftpd: auto generate an ed25519 host key too
This commit is contained in:
parent
d12819932a
commit
b51d795e04
4 changed files with 95 additions and 33 deletions
|
@ -35,7 +35,7 @@ The `serve` command supports the following flags:
|
|||
|
||||
Log file can be rotated on demand sending a `SIGUSR1` signal on Unix based systems and using the command `sftpgo service rotatelogs` on Windows.
|
||||
|
||||
If you don't configure any private host key, the daemon will use `id_rsa` and `id_ecdsa` in the configuration directory. If these files don't exist, the daemon will attempt to autogenerate them (if the user that executes SFTPGo has write access to the `config-dir`). The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L33).
|
||||
If you don't configure any private host key, the daemon will use `id_rsa`, `id_ecdsa` and `id_ed25519` in the configuration directory. If these files don't exist, the daemon will attempt to autogenerate them (if the user that executes SFTPGo has write access to the `config-dir`). The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L33).
|
||||
|
||||
The `gen` command allows to generate completion scripts for your shell and man pages. Currently the man pages visual representation is wrong, take a look at this upstream [bug](https://github.com/spf13/cobra/issues/1049) for more details.
|
||||
|
||||
|
@ -68,7 +68,7 @@ The configuration file contains the following sections:
|
|||
- `actions`, struct. Deprecated, please use the same key in `common` section.
|
||||
- `keys`, struct array. Deprecated, please use `host_keys`.
|
||||
- `private_key`, path to the private key file. It can be a path relative to the config dir or an absolute one.
|
||||
- `host_keys`, list of strings. It contains the daemon's private host keys. Each host key can be defined as a path relative to the configuration directory or an absolute one. If empty, the daemon will search or try to generate `id_rsa` and `id_ecdsa` keys inside the configuration directory. If you configure absolute paths to files named `id_rsa` and/or `id_ecdsa` then SFTPGo will try to generate these keys using the default settings.
|
||||
- `host_keys`, list of strings. It contains the daemon's private host keys. Each host key can be defined as a path relative to the configuration directory or an absolute one. If empty, the daemon will search or try to generate `id_rsa`, `id_ecdsa` and `id_ed25519` keys inside the configuration directory. If you configure absolute paths to files named `id_rsa`, `id_ecdsa` and/or `id_ed25519` then SFTPGo will try to generate these keys using the default settings.
|
||||
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L46 "Supported kex algos")
|
||||
- `ciphers`, list of strings. Allowed ciphers. Leave empty to use default values. The supported values can be found here: [crypto/ssh](https://github.com/golang/crypto/blob/master/ssh/common.go#L28 "Supported ciphers")
|
||||
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [crypto/ssh](https://github.com/golang/crypto/blob/master/ssh/common.go#L84 "Supported MACs")
|
||||
|
@ -172,16 +172,17 @@ If you want to use a private host key that uses an algorithm/setting different f
|
|||
|
||||
where `id_rsa`, `id_ecdsa` and `id_ed25519`, in this example, are files containing your generated keys. You can use absolute paths or paths relative to the configuration directory.
|
||||
|
||||
If you want the default host keys generation in a directory different from the config dir, please specify absolute paths to files named `id_rsa` or `id_ecdsa` like this:
|
||||
If you want the default host keys generation in a directory different from the config dir, please specify absolute paths to files named `id_rsa`, `id_ecdsa` or `id_ed25519` like this:
|
||||
|
||||
```json
|
||||
"host_keys": [
|
||||
"/etc/sftpgo/keys/id_rsa",
|
||||
"/etc/sftpgo/keys/id_ecdsa"
|
||||
"/etc/sftpgo/keys/id_ecdsa",
|
||||
"/etc/sftpgo/keys/id_ed25519"
|
||||
]
|
||||
```
|
||||
|
||||
then SFTPGo will try to create `id_rsa` and `id_ecdsa`, if they are missing, inside the existing directory `/etc/sftpgo/keys`.
|
||||
then SFTPGo will try to create `id_rsa`, `id_ecdsa` and `id_ed25519`, if they are missing, inside the existing directory `/etc/sftpgo/keys`.
|
||||
|
||||
The configuration can be read from JSON, TOML, YAML, HCL, envfile and Java properties config files. If your `config-file` flag is set to `sftpgo` (default value), you need to create a configuration file called `sftpgo.json` or `sftpgo.yaml` and so on inside `config-dir`.
|
||||
|
||||
|
|
|
@ -1741,17 +1741,21 @@ func TestLoadHostKeys(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
rsaKeyName := filepath.Join(keysDir, defaultPrivateRSAKeyName)
|
||||
ecdsaKeyName := filepath.Join(keysDir, defaultPrivateECDSAKeyName)
|
||||
ed25519KeyName := filepath.Join(keysDir, defaultPrivateEd25519KeyName)
|
||||
nonDefaultKeyName := filepath.Join(keysDir, "akey")
|
||||
c.HostKeys = []string{nonDefaultKeyName, rsaKeyName, ecdsaKeyName}
|
||||
c.HostKeys = []string{nonDefaultKeyName, rsaKeyName, ecdsaKeyName, ed25519KeyName}
|
||||
err = c.checkAndLoadHostKeys(configDir, serverConfig)
|
||||
assert.Error(t, err)
|
||||
assert.FileExists(t, rsaKeyName)
|
||||
assert.FileExists(t, ecdsaKeyName)
|
||||
assert.FileExists(t, ed25519KeyName)
|
||||
assert.NoFileExists(t, nonDefaultKeyName)
|
||||
err = os.Remove(rsaKeyName)
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(ecdsaKeyName)
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(ed25519KeyName)
|
||||
assert.NoError(t, err)
|
||||
if runtime.GOOS != osWindows {
|
||||
err = os.Chmod(keysDir, 0551)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1764,6 +1768,9 @@ func TestLoadHostKeys(t *testing.T) {
|
|||
c.HostKeys = []string{ecdsaKeyName, rsaKeyName}
|
||||
err = c.checkAndLoadHostKeys(configDir, serverConfig)
|
||||
assert.Error(t, err)
|
||||
c.HostKeys = []string{ed25519KeyName}
|
||||
err = c.checkAndLoadHostKeys(configDir, serverConfig)
|
||||
assert.Error(t, err)
|
||||
err = os.Chmod(keysDir, 0755)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -25,9 +25,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultPrivateRSAKeyName = "id_rsa"
|
||||
defaultPrivateECDSAKeyName = "id_ecdsa"
|
||||
sourceAddressCriticalOption = "source-address"
|
||||
defaultPrivateRSAKeyName = "id_rsa"
|
||||
defaultPrivateECDSAKeyName = "id_ecdsa"
|
||||
defaultPrivateEd25519KeyName = "id_ed25519"
|
||||
sourceAddressCriticalOption = "source-address"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -471,6 +472,33 @@ func (c *Configuration) checkSSHCommands() {
|
|||
c.EnabledSSHCommands = sshCommands
|
||||
}
|
||||
|
||||
func (c *Configuration) generateDefaultHostKeys(configDir string) error {
|
||||
var err error
|
||||
defaultHostKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName, defaultPrivateEd25519KeyName}
|
||||
for _, k := range defaultHostKeys {
|
||||
autoFile := filepath.Join(configDir, k)
|
||||
if _, err = os.Stat(autoFile); os.IsNotExist(err) {
|
||||
logger.Info(logSender, "", "No host keys configured and %#v does not exist; try to create a new host key", autoFile)
|
||||
logger.InfoToConsole("No host keys configured and %#v does not exist; try to create a new host key", autoFile)
|
||||
if k == defaultPrivateRSAKeyName {
|
||||
err = utils.GenerateRSAKeys(autoFile)
|
||||
} else if k == defaultPrivateECDSAKeyName {
|
||||
err = utils.GenerateECDSAKeys(autoFile)
|
||||
} else {
|
||||
err = utils.GenerateEd25519Keys(autoFile)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error creating host key %#v: %v", autoFile, err)
|
||||
logger.WarnToConsole("error creating host key %#v: %v", autoFile, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.HostKeys = append(c.HostKeys, k)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
|
||||
for _, k := range c.HostKeys {
|
||||
if filepath.IsAbs(k) {
|
||||
|
@ -495,6 +523,15 @@ func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
|
|||
logger.WarnToConsole("error creating host key %#v: %v", k, err)
|
||||
return err
|
||||
}
|
||||
case defaultPrivateEd25519KeyName:
|
||||
logger.Info(logSender, "", "try to create non-existent host key %#v", k)
|
||||
logger.InfoToConsole("try to create non-existent host key %#v", k)
|
||||
err = utils.GenerateEd25519Keys(k)
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error creating host key %#v: %v", k, err)
|
||||
logger.WarnToConsole("error creating host key %#v: %v", k, err)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
logger.Warn(logSender, "", "non-existent host key %#v will not be created", k)
|
||||
logger.WarnToConsole("non-existent host key %#v will not be created", k)
|
||||
|
@ -503,24 +540,8 @@ func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
|
|||
}
|
||||
}
|
||||
if len(c.HostKeys) == 0 {
|
||||
defaultKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName}
|
||||
for _, k := range defaultKeys {
|
||||
autoFile := filepath.Join(configDir, k)
|
||||
if _, err := os.Stat(autoFile); os.IsNotExist(err) {
|
||||
logger.Info(logSender, "", "No host keys configured and %#v does not exist; try to create a new host key", autoFile)
|
||||
logger.InfoToConsole("No host keys configured and %#v does not exist; try to create a new host key", autoFile)
|
||||
if k == defaultPrivateRSAKeyName {
|
||||
err = utils.GenerateRSAKeys(autoFile)
|
||||
} else {
|
||||
err = utils.GenerateECDSAKeys(autoFile)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error creating host key %#v: %v", autoFile, err)
|
||||
logger.WarnToConsole("error creating host key %#v: %v", autoFile, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.HostKeys = append(c.HostKeys, k)
|
||||
if err := c.generateDefaultHostKeys(configDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
|
@ -230,12 +231,6 @@ func GenerateECDSAKeys(file string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
o, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
keyBytes, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -245,6 +240,12 @@ func GenerateECDSAKeys(file string) error {
|
|||
Bytes: keyBytes,
|
||||
}
|
||||
|
||||
o, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
if err := pem.Encode(o, priv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -256,6 +257,38 @@ func GenerateECDSAKeys(file string) error {
|
|||
return ioutil.WriteFile(file+".pub", ssh.MarshalAuthorizedKey(pub), 0600)
|
||||
}
|
||||
|
||||
// GenerateEd25519Keys generate ed25519 private and public keys and write the
|
||||
// private key to specified file and the public key to the specified
|
||||
// file adding the .pub suffix
|
||||
func GenerateEd25519Keys(file string) error {
|
||||
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyBytes, err := x509.MarshalPKCS8PrivateKey(privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
priv := &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
o, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer o.Close()
|
||||
|
||||
if err := pem.Encode(o, priv); err != nil {
|
||||
return err
|
||||
}
|
||||
pub, err := ssh.NewPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(file+".pub", ssh.MarshalAuthorizedKey(pub), 0600)
|
||||
}
|
||||
|
||||
// GetDirsForSFTPPath returns all the directory for the given path in reverse order
|
||||
// for example if the path is: /1/2/3/4 it returns:
|
||||
// [ "/1/2/3/4", "/1/2/3", "/1/2", "/1", "/" ]
|
||||
|
|
Loading…
Reference in a new issue