serve sftp: generate an ECDSA server key as well as RSA

Before this fix, rclone only generated an RSA server key when the user
didn't supply a key.

However the RSA server key is being deprecated as it is now insecure.

This patch generates an ECDSA server key too which will be used in
preference over the RSA key, but the RSA key will carry on working.

Fixes #5671
This commit is contained in:
Nick Craig-Wood 2021-10-08 17:14:35 +01:00
parent 0dfffc0ed4
commit df0b7d8eab
1 changed files with 51 additions and 7 deletions

View File

@ -6,6 +6,8 @@ package sftp
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/subtle"
@ -221,7 +223,10 @@ func (s *server) serve() (err error) {
keyPaths := s.opt.HostKeys
cachePath := filepath.Join(config.CacheDir, "serve-sftp")
if len(keyPaths) == 0 {
keyPaths = []string{filepath.Join(cachePath, "id_rsa")}
keyPaths = []string{
filepath.Join(cachePath, "id_rsa"),
filepath.Join(cachePath, "id_ecdsa"),
}
}
for _, keyPath := range keyPaths {
private, err := loadPrivateKey(keyPath)
@ -232,13 +237,20 @@ func (s *server) serve() (err error) {
if err != nil {
return errors.Wrap(err, "failed to create cache path")
}
const bits = 2048
fs.Logf(nil, "Generating %d bit key pair at %q", bits, keyPath)
err = makeSSHKeyPair(bits, keyPath+".pub", keyPath)
if strings.HasSuffix(keyPath, "/id_rsa") {
const bits = 2048
fs.Logf(nil, "Generating %d bit key pair at %q", bits, keyPath)
err = makeRSASSHKeyPair(bits, keyPath+".pub", keyPath)
} else if strings.HasSuffix(keyPath, "/id_ecdsa") {
fs.Logf(nil, "Generating ECDSA p256 key pair at %q", keyPath)
err = makeECDSASSHKeyPair(keyPath+".pub", keyPath)
} else {
return errors.Errorf("don't know how to generate key pair %q", keyPath)
}
if err != nil {
return errors.Wrap(err, "failed to create SSH key pair")
}
// reload the new keys
// reload the new key
private, err = loadPrivateKey(keyPath)
}
if err != nil {
@ -325,12 +337,12 @@ func loadAuthorizedKeys(authorizedKeysPath string) (authorizedKeysMap map[string
return authorizedKeysMap, nil
}
// makeSSHKeyPair make a pair of public and private keys for SSH access.
// makeRSASSHKeyPair make a pair of public and private keys for SSH access.
// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
// Private Key generated is PEM encoded
//
// Originally from: https://stackoverflow.com/a/34347463/164234
func makeSSHKeyPair(bits int, pubKeyPath, privateKeyPath string) (err error) {
func makeRSASSHKeyPair(bits int, pubKeyPath, privateKeyPath string) (err error) {
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
@ -354,3 +366,35 @@ func makeSSHKeyPair(bits int, pubKeyPath, privateKeyPath string) (err error) {
}
return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
}
// makeECDSASSHKeyPair make a pair of public and private keys for ECDSA SSH access.
// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
// Private Key generated is PEM encoded
func makeECDSASSHKeyPair(pubKeyPath, privateKeyPath string) (err error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
// generate and write private key as PEM
privateKeyFile, err := os.OpenFile(privateKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer fs.CheckClose(privateKeyFile, &err)
buf, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return err
}
privateKeyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: buf}
if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
return err
}
// generate and write public key
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return err
}
return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
}