@@ -3,11 +3,12 @@ package agentssh
3
3
import (
4
4
"bufio"
5
5
"context"
6
- "crypto/rand"
7
6
"crypto/rsa"
8
7
"errors"
9
8
"fmt"
10
9
"io"
10
+ "math/big"
11
+ "math/rand"
11
12
"net"
12
13
"os"
13
14
"os/exec"
@@ -128,17 +129,6 @@ type Server struct {
128
129
}
129
130
130
131
func NewServer (ctx context.Context ,logger slog.Logger ,prometheusRegistry * prometheus.Registry ,fs afero.Fs ,execer agentexec.Execer ,config * Config ) (* Server ,error ) {
131
- // Clients' should ignore the host key when connecting.
132
- // The agent needs to authenticate with coderd to SSH,
133
- // so SSH authentication doesn't improve security.
134
- randomHostKey ,err := rsa .GenerateKey (rand .Reader ,2048 )
135
- if err != nil {
136
- return nil ,err
137
- }
138
- randomSigner ,err := gossh .NewSignerFromKey (randomHostKey )
139
- if err != nil {
140
- return nil ,err
141
- }
142
132
if config == nil {
143
133
config = & Config {}
144
134
}
@@ -205,8 +195,10 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
205
195
slog .F ("local_addr" ,conn .LocalAddr ()),
206
196
slog .Error (err ))
207
197
},
208
- Handler :s .sessionHandler ,
209
- HostSigners : []ssh.Signer {randomSigner },
198
+ Handler :s .sessionHandler ,
199
+ // HostSigners are intentionally empty, as the host key will
200
+ // be set before we start listening.
201
+ HostSigners : []ssh.Signer {},
210
202
LocalPortForwardingCallback :func (ctx ssh.Context ,destinationHost string ,destinationPort uint32 )bool {
211
203
// Allow local port forwarding all!
212
204
s .logger .Debug (ctx ,"local port forward" ,
@@ -844,7 +836,13 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
844
836
return cmd ,nil
845
837
}
846
838
839
+ // Serve starts the server to handle incoming connections on the provided listener.
840
+ // It returns an error if no host keys are set or if there is an issue accepting connections.
847
841
func (s * Server )Serve (l net.Listener ) (retErr error ) {
842
+ if len (s .srv .HostSigners )== 0 {
843
+ return xerrors .New ("no host keys set" )
844
+ }
845
+
848
846
s .logger .Info (context .Background (),"started serving listener" ,slog .F ("listen_addr" ,l .Addr ()))
849
847
defer func () {
850
848
s .logger .Info (context .Background (),"stopped serving listener" ,
@@ -1099,3 +1097,99 @@ func userHomeDir() (string, error) {
1099
1097
}
1100
1098
return u .HomeDir ,nil
1101
1099
}
1100
+
1101
+ // UpdateHostSigner updates the host signer with a new key generated from the provided seed.
1102
+ // If an existing host key exists with the same algorithm, it is overwritten
1103
+ func (s * Server )UpdateHostSigner (seed int64 )error {
1104
+ key ,err := CoderSigner (seed )
1105
+ if err != nil {
1106
+ return err
1107
+ }
1108
+
1109
+ s .mu .Lock ()
1110
+ defer s .mu .Unlock ()
1111
+
1112
+ s .srv .AddHostKey (key )
1113
+
1114
+ return nil
1115
+ }
1116
+
1117
+ // CoderSigner generates a deterministic SSH signer based on the provided seed.
1118
+ // It uses RSA with a key size of 2048 bits.
1119
+ func CoderSigner (seed int64 ) (gossh.Signer ,error ) {
1120
+ // Clients should ignore the host key when connecting.
1121
+ // The agent needs to authenticate with coderd to SSH,
1122
+ // so SSH authentication doesn't improve security.
1123
+
1124
+ // Since the standard lib purposefully does not generate
1125
+ // deterministic rsa keys, we need to do it ourselves.
1126
+ coderHostKey := func ()* rsa.PrivateKey {
1127
+ // Create deterministic random source
1128
+ // nolint: gosec
1129
+ deterministicRand := rand .New (rand .NewSource (seed ))
1130
+
1131
+ // Use fixed values for p and q based on the seed
1132
+ p := big .NewInt (0 )
1133
+ q := big .NewInt (0 )
1134
+ e := big .NewInt (65537 )// Standard RSA public exponent
1135
+
1136
+ // Generate deterministic primes using the seeded random
1137
+ // Each prime should be ~1024 bits to get a 2048-bit key
1138
+ for {
1139
+ p .SetBit (p ,1024 ,1 )// Ensure it's large enough
1140
+ for i := 0 ;i < 1024 ;i ++ {
1141
+ if deterministicRand .Int63 ()% 2 == 1 {
1142
+ p .SetBit (p ,i ,1 )
1143
+ }else {
1144
+ p .SetBit (p ,i ,0 )
1145
+ }
1146
+ }
1147
+ if p .ProbablyPrime (20 ) {
1148
+ break
1149
+ }
1150
+ }
1151
+
1152
+ for {
1153
+ q .SetBit (q ,1024 ,1 )// Ensure it's large enough
1154
+ for i := 0 ;i < 1024 ;i ++ {
1155
+ if deterministicRand .Int63 ()% 2 == 1 {
1156
+ q .SetBit (q ,i ,1 )
1157
+ }else {
1158
+ q .SetBit (q ,i ,0 )
1159
+ }
1160
+ }
1161
+ if q .ProbablyPrime (20 )&& p .Cmp (q )!= 0 {
1162
+ break
1163
+ }
1164
+ }
1165
+
1166
+ // Calculate n = p * q
1167
+ n := new (big.Int ).Mul (p ,q )
1168
+
1169
+ // Calculate phi = (p-1) * (q-1)
1170
+ p1 := new (big.Int ).Sub (p ,big .NewInt (1 ))
1171
+ q1 := new (big.Int ).Sub (q ,big .NewInt (1 ))
1172
+ phi := new (big.Int ).Mul (p1 ,q1 )
1173
+
1174
+ // Calculate private exponent d
1175
+ d := new (big.Int ).ModInverse (e ,phi )
1176
+
1177
+ // Create the private key
1178
+ privateKey := & rsa.PrivateKey {
1179
+ PublicKey : rsa.PublicKey {
1180
+ N :n ,
1181
+ E :int (e .Int64 ()),
1182
+ },
1183
+ D :d ,
1184
+ Primes : []* big.Int {p ,q },
1185
+ }
1186
+
1187
+ // Compute precomputed values
1188
+ privateKey .Precompute ()
1189
+
1190
+ return privateKey
1191
+ }()
1192
+
1193
+ coderSigner ,err := gossh .NewSignerFromKey (coderHostKey )
1194
+ return coderSigner ,err
1195
+ }