- Notifications
You must be signed in to change notification settings - Fork1k
fix(agent/agentssh): use deterministic host key for SSH server#16626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
ThomasK33 merged 1 commit intomainfromthomask33/02-19-fix_agentssh_pin_random_seed_for_rsa_key_generationFeb 21, 2025
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
44 changes: 41 additions & 3 deletionsagent/agent.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
122 changes: 108 additions & 14 deletionsagent/agentssh/agentssh.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -3,11 +3,12 @@ package agentssh | ||
import ( | ||
"bufio" | ||
"context" | ||
"crypto/rsa" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math/big" | ||
"math/rand" | ||
"net" | ||
"os" | ||
"os/exec" | ||
@@ -128,17 +129,6 @@ type Server struct { | ||
} | ||
func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prometheus.Registry, fs afero.Fs, execer agentexec.Execer, config *Config) (*Server, error) { | ||
if config == nil { | ||
config = &Config{} | ||
} | ||
@@ -205,8 +195,10 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom | ||
slog.F("local_addr", conn.LocalAddr()), | ||
slog.Error(err)) | ||
}, | ||
Handler: s.sessionHandler, | ||
// HostSigners are intentionally empty, as the host key will | ||
// be set before we start listening. | ||
HostSigners: []ssh.Signer{}, | ||
ThomasK33 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
LocalPortForwardingCallback: func(ctx ssh.Context, destinationHost string, destinationPort uint32) bool { | ||
// Allow local port forwarding all! | ||
s.logger.Debug(ctx, "local port forward", | ||
@@ -844,7 +836,13 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string, | ||
return cmd, nil | ||
} | ||
// Serve starts the server to handle incoming connections on the provided listener. | ||
// It returns an error if no host keys are set or if there is an issue accepting connections. | ||
func (s *Server) Serve(l net.Listener) (retErr error) { | ||
if len(s.srv.HostSigners) == 0 { | ||
return xerrors.New("no host keys set") | ||
} | ||
s.logger.Info(context.Background(), "started serving listener", slog.F("listen_addr", l.Addr())) | ||
defer func() { | ||
s.logger.Info(context.Background(), "stopped serving listener", | ||
@@ -1099,3 +1097,99 @@ func userHomeDir() (string, error) { | ||
} | ||
return u.HomeDir, nil | ||
} | ||
// UpdateHostSigner updates the host signer with a new key generated from the provided seed. | ||
// If an existing host key exists with the same algorithm, it is overwritten | ||
func (s *Server) UpdateHostSigner(seed int64) error { | ||
key, err := CoderSigner(seed) | ||
if err != nil { | ||
return err | ||
} | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
s.srv.AddHostKey(key) | ||
return nil | ||
} | ||
// CoderSigner generates a deterministic SSH signer based on the provided seed. | ||
// It uses RSA with a key size of 2048 bits. | ||
func CoderSigner(seed int64) (gossh.Signer, error) { | ||
// Clients should ignore the host key when connecting. | ||
// The agent needs to authenticate with coderd to SSH, | ||
// so SSH authentication doesn't improve security. | ||
// Since the standard lib purposefully does not generate | ||
// deterministic rsa keys, we need to do it ourselves. | ||
coderHostKey := func() *rsa.PrivateKey { | ||
// Create deterministic random source | ||
// nolint: gosec | ||
deterministicRand := rand.New(rand.NewSource(seed)) | ||
// Use fixed values for p and q based on the seed | ||
p := big.NewInt(0) | ||
q := big.NewInt(0) | ||
e := big.NewInt(65537) // Standard RSA public exponent | ||
// Generate deterministic primes using the seeded random | ||
// Each prime should be ~1024 bits to get a 2048-bit key | ||
for { | ||
p.SetBit(p, 1024, 1) // Ensure it's large enough | ||
for i := 0; i < 1024; i++ { | ||
if deterministicRand.Int63()%2 == 1 { | ||
p.SetBit(p, i, 1) | ||
} else { | ||
p.SetBit(p, i, 0) | ||
} | ||
} | ||
if p.ProbablyPrime(20) { | ||
break | ||
} | ||
} | ||
for { | ||
q.SetBit(q, 1024, 1) // Ensure it's large enough | ||
for i := 0; i < 1024; i++ { | ||
if deterministicRand.Int63()%2 == 1 { | ||
q.SetBit(q, i, 1) | ||
} else { | ||
q.SetBit(q, i, 0) | ||
} | ||
} | ||
if q.ProbablyPrime(20) && p.Cmp(q) != 0 { | ||
break | ||
} | ||
} | ||
// Calculate n = p * q | ||
n := new(big.Int).Mul(p, q) | ||
// Calculate phi = (p-1) * (q-1) | ||
p1 := new(big.Int).Sub(p, big.NewInt(1)) | ||
q1 := new(big.Int).Sub(q, big.NewInt(1)) | ||
phi := new(big.Int).Mul(p1, q1) | ||
// Calculate private exponent d | ||
d := new(big.Int).ModInverse(e, phi) | ||
// Create the private key | ||
privateKey := &rsa.PrivateKey{ | ||
PublicKey: rsa.PublicKey{ | ||
N: n, | ||
E: int(e.Int64()), | ||
}, | ||
D: d, | ||
Primes: []*big.Int{p, q}, | ||
} | ||
// Compute precomputed values | ||
privateKey.Precompute() | ||
return privateKey | ||
}() | ||
coderSigner, err := gossh.NewSignerFromKey(coderHostKey) | ||
return coderSigner, err | ||
} |
2 changes: 2 additions & 0 deletionsagent/agentssh/agentssh_internal_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletionsagent/agentssh/agentssh_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletionsagent/agentssh/x11_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletionscli/ssh_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.