dockerimg: update SSHTheater to use Ed25519 keys
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sa8eb476bc38e0479k
diff --git a/dockerimg/ssh_theater.go b/dockerimg/ssh_theater.go
index 1b060cc..91d3c21 100644
--- a/dockerimg/ssh_theater.go
+++ b/dockerimg/ssh_theater.go
@@ -3,9 +3,8 @@
import (
"bufio"
"bytes"
+ "crypto/ed25519"
"crypto/rand"
- "crypto/rsa"
- "crypto/x509"
"encoding/pem"
"fmt"
"io/fs"
@@ -19,7 +18,7 @@
"golang.org/x/crypto/ssh/knownhosts"
)
-const keyBitSize = 2048
+// Ed25519 has a fixed key size, no bit size constant needed
// SSHTheater does the necessary key pair generation, known_hosts updates, ssh_config file updates etc steps
// so that ssh can connect to a locally running sketch container to other local processes like vscode without
@@ -33,6 +32,8 @@
// Include $HOME/.config/sketch/ssh_config
//
// where $HOME is your home directory.
+//
+// SSHTheater uses Ed25519 keys for improved security and performance.
type SSHTheater struct {
cntrName string
sshHost string
@@ -58,6 +59,7 @@
// these files do not already exist. These key pair files are not deleted by #Cleanup,
// so they can be re-used across invocations of sketch. This means every sketch container
// that runs on this host will use the same ssh server identity.
+// The system uses Ed25519 keys for better security and performance.
//
// If this doesn't return an error, you should be able to run "ssh <cntrName>"
// in a terminal on your host machine to open a shell into the container without having
@@ -224,15 +226,25 @@
return hosts
}
-func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {
+func encodePrivateKeyToPEM(privateKey ed25519.PrivateKey) []byte {
pemBlock := &pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
+ Type: "OPENSSH PRIVATE KEY",
+ Bytes: MarshalED25519PrivateKey(privateKey),
}
pemBytes := pem.EncodeToMemory(pemBlock)
return pemBytes
}
+// MarshalED25519PrivateKey encodes an Ed25519 private key in the OpenSSH private key format
+func MarshalED25519PrivateKey(key ed25519.PrivateKey) []byte {
+ // Marshal the private key using the SSH library
+ pkBytes, err := ssh.MarshalPrivateKey(key, "")
+ if err != nil {
+ panic(fmt.Sprintf("failed to marshal private key: %v", err))
+ }
+ return pem.EncodeToMemory(pkBytes)
+}
+
func (c *SSHTheater) writeKeyToFile(keyBytes []byte, filename string) error {
err := c.fs.WriteFile(filename, keyBytes, 0o600)
return err
@@ -243,14 +255,14 @@
return nil, nil
}
- privateKey, err := c.kg.GeneratePrivateKey(keyBitSize)
+ privateKey, publicKey, err := c.kg.GenerateKeyPair()
if err != nil {
- return nil, fmt.Errorf("error generating private key: %w", err)
+ return nil, fmt.Errorf("error generating key pair: %w", err)
}
- publicRsaKey, err := c.kg.GeneratePublicKey(&privateKey.PublicKey)
+ sshPublicKey, err := c.kg.ConvertToSSHPublicKey(publicKey)
if err != nil {
- return nil, fmt.Errorf("error generating public key: %w", err)
+ return nil, fmt.Errorf("error converting to SSH public key: %w", err)
}
privateKeyPEM := encodePrivateKeyToPEM(privateKey)
@@ -259,13 +271,13 @@
if err != nil {
return nil, fmt.Errorf("error writing private key to file %w", err)
}
- pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)
+ pubKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
err = c.writeKeyToFile([]byte(pubKeyBytes), idPath+".pub")
if err != nil {
return nil, fmt.Errorf("error writing public key to file %w", err)
}
- return publicRsaKey, nil
+ return sshPublicKey, nil
}
func (c *SSHTheater) addSketchHostMatchIfMissing(cfg *ssh_config.Config) error {
@@ -558,19 +570,20 @@
// KeyGenerator represents an interface for generating SSH keys for testability
type KeyGenerator interface {
- GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error)
- GeneratePublicKey(privateKey *rsa.PublicKey) (ssh.PublicKey, error)
+ GenerateKeyPair() (ed25519.PrivateKey, ed25519.PublicKey, error)
+ ConvertToSSHPublicKey(publicKey ed25519.PublicKey) (ssh.PublicKey, error)
}
// RealKeyGenerator is the default implementation of KeyGenerator
type RealKeyGenerator struct{}
-func (kg *RealKeyGenerator) GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
- return rsa.GenerateKey(rand.Reader, bitSize)
+func (kg *RealKeyGenerator) GenerateKeyPair() (ed25519.PrivateKey, ed25519.PublicKey, error) {
+ publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
+ return privateKey, publicKey, err
}
-func (kg *RealKeyGenerator) GeneratePublicKey(privateKey *rsa.PublicKey) (ssh.PublicKey, error) {
- return ssh.NewPublicKey(privateKey)
+func (kg *RealKeyGenerator) ConvertToSSHPublicKey(publicKey ed25519.PublicKey) (ssh.PublicKey, error) {
+ return ssh.NewPublicKey(publicKey)
}
// CheckSSHReachability checks if the user's SSH config includes the Sketch SSH config file