sftpd: autogenerate ecdsa key
With default configuration we now generate RSA and ECDSA server keys.
This commit is contained in:
parent
79c8b6cbc2
commit
c8cc81cf4a
3 changed files with 106 additions and 44 deletions
15
README.md
15
README.md
|
@ -125,7 +125,7 @@ The `serve` command supports the following flags:
|
|||
- `--log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10 or the value of `SFTPGO_LOG_MAX_SIZE` environment variable. It is unused if `log-file-path` is empty.
|
||||
- `--log-verbose` boolean. Enable verbose logs. Default `true` or the value of `SFTPGO_LOG_VERBOSE` environment variable (1 or `true`, 0 or `false`).
|
||||
|
||||
If you don't configure any private host keys, the daemon will use `id_rsa` in the configuration directory. If that file doesn't exist, the daemon will attempt to autogenerate it (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#L32).
|
||||
If you don't configure any private host keys, 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#L32).
|
||||
|
||||
The `sftpgo` configuration file contains the following sections:
|
||||
|
||||
|
@ -141,7 +141,7 @@ The `sftpgo` configuration file contains the following sections:
|
|||
- `execute_on`, list of strings. Valid values are `download`, `upload`, `delete`, `rename`, `ssh_cmd`. Leave empty to disable actions.
|
||||
- `command`, string. Absolute path to the command to execute. Leave empty to disable.
|
||||
- `http_notification_url`, a valid URL. An HTTP GET request will be executed to this URL. Leave empty to disable.
|
||||
- `keys`, struct array. It contains the daemon's private keys. If empty or missing the daemon will search or try to generate `id_rsa` in the configuration directory.
|
||||
- `keys`, struct array. It contains the daemon's private keys. If empty or missing the daemon will search or try to generate `id_rsa` and `id_ecdsa` keys in the configuration directory.
|
||||
- `private_key`, path to the private key file. It can be a path relative to the config dir or an absolute one.
|
||||
- `enable_scp`, boolean. Default disabled. Set to `true` to enable the experimental SCP support. This setting is deprecated and will be removed in future versions, please add `scp` to the `enabled_ssh_commands` list to enable it
|
||||
- `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")
|
||||
|
@ -253,7 +253,7 @@ Here is a full example showing the default config in JSON format:
|
|||
}
|
||||
```
|
||||
|
||||
If you want to use a private key that use an algorithm different from RSA or more than one private key then replace the empty `keys` array with something like this:
|
||||
If you want to use a private key that use an algorithm different from RSA or ECDSA or more private keys then generate your own keys and replace the empty `keys` array with something like this:
|
||||
|
||||
```json
|
||||
"keys": [
|
||||
|
@ -262,10 +262,15 @@ If you want to use a private key that use an algorithm different from RSA or mor
|
|||
},
|
||||
{
|
||||
"private_key": "id_ecdsa"
|
||||
},
|
||||
{
|
||||
"private_key": "id_ed25519"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
where `id_rsa`, `id_ecdsa` and `id_ed25519` are your generated keys. You can use absolute paths or paths relative to the configuration directory.
|
||||
|
||||
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`.
|
||||
|
||||
You can also override all the available configuration options using environment variables, sftpgo will check for environment variables with a name matching the key uppercased and prefixed with the `SFTPGO_`. You need to use `__` to traverse a struct.
|
||||
|
@ -410,7 +415,7 @@ The program must write the questions on its standard output, in a single line, u
|
|||
- `instruction`, string. A short description to show to the user that is trying to authenticate. Can be empty or omitted
|
||||
- `questions`, list of questions to be asked to the user
|
||||
- `echos` list of boolean flags corresponding to the questions (so the lengths of both lists must be the same) and indicating whether user's reply for a particular question should be echoed on the screen while they are typing: true if it should be echoed, or false if it should be hidden.
|
||||
- `check_password` optional integer. Ask exactly one question and set this field to 1 if the expected answer is the user password and you want that SFTPGo check it for you. If the password is correct the returned response to the program is `OK`. If the password is wrong the program will be terminated and an authentication error will be returned to the user
|
||||
- `check_password` optional integer. Ask exactly one question and set this field to 1 if the expected answer is the user password and you want that SFTPGo checks it for you. If the password is correct the returned response to the program is `OK`. If the password is wrong the program will be terminated and an authentication error will be returned to the user that is trying to authenticate
|
||||
- `auth_result`, integer. Set this field to 1 to indicate successful authentication, 0 is ignored, any other value means authentication error. If this fields is found and it is different from 0 then SFTPGo does not read any other questions from the external program and finalize the authentication.
|
||||
|
||||
SFTPGo writes the user answers to the program standard input, one per line, in the same order of the questions.
|
||||
|
@ -440,7 +445,7 @@ else
|
|||
fi
|
||||
```
|
||||
|
||||
and here is an example where SFTPGo check the user password for you:
|
||||
and here is an example where SFTPGo checks the user password for you:
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package sftpd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -24,7 +20,10 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const defaultPrivateKeyName = "id_rsa"
|
||||
const (
|
||||
defaultPrivateRSAKeyName = "id_rsa"
|
||||
defaultPrivateECDSAKeyName = "id_ecdsa"
|
||||
)
|
||||
|
||||
var sftpExtensions = []string{"posix-rename@openssh.com"}
|
||||
|
||||
|
@ -445,18 +444,26 @@ func (c *Configuration) checkSSHCommands() {
|
|||
|
||||
// If no host keys are defined we try to use or generate the default one.
|
||||
func (c *Configuration) checkHostKeys(configDir string) error {
|
||||
var err error
|
||||
if len(c.Keys) == 0 {
|
||||
autoFile := filepath.Join(configDir, defaultPrivateKeyName)
|
||||
if _, err = os.Stat(autoFile); os.IsNotExist(err) {
|
||||
logger.Info(logSender, "", "No host keys configured and %#v does not exist; creating new private key for server", autoFile)
|
||||
logger.InfoToConsole("No host keys configured and %#v does not exist; creating new private key for server", autoFile)
|
||||
err = c.generatePrivateKey(autoFile)
|
||||
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; creating new key for server", autoFile)
|
||||
logger.InfoToConsole("No host keys configured and %#v does not exist; creating new key for server", autoFile)
|
||||
if k == defaultPrivateRSAKeyName {
|
||||
err = utils.GenerateRSAKeys(autoFile)
|
||||
} else {
|
||||
err = utils.GenerateECDSAKeys(autoFile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.Keys = append(c.Keys, Key{PrivateKey: k})
|
||||
}
|
||||
|
||||
c.Keys = append(c.Keys, Key{PrivateKey: defaultPrivateKeyName})
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKey string) (*ssh.Permissions, error) {
|
||||
|
@ -507,28 +514,3 @@ func (c Configuration) validateKeyboardInteractiveCredentials(conn ssh.ConnMetad
|
|||
metrics.AddLoginResult(method, err)
|
||||
return sshPerm, err
|
||||
}
|
||||
|
||||
// Generates a private key that will be used by the SFTP server.
|
||||
func (c Configuration) generatePrivateKey(file string) error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
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()
|
||||
|
||||
pkey := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}
|
||||
|
||||
if err := pem.Encode(o, pkey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,14 +4,23 @@ package utils
|
|||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const logSender = "utils"
|
||||
|
@ -173,3 +182,69 @@ func DecryptData(data string) (string, error) {
|
|||
}
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
// GenerateRSAKeys generate rsa 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 GenerateRSAKeys(file string) error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
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()
|
||||
|
||||
priv := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}
|
||||
|
||||
if err := pem.Encode(o, priv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pub, err := ssh.NewPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(file+".pub", ssh.MarshalAuthorizedKey(pub), 0600)
|
||||
}
|
||||
|
||||
// GenerateECDSAKeys generate ecdsa 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 GenerateECDSAKeys(file string) error {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
priv := &pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
}
|
||||
|
||||
if err := pem.Encode(o, priv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pub, err := ssh.NewPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(file+".pub", ssh.MarshalAuthorizedKey(pub), 0600)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue