Skip to content

Commit 8cd766e

Browse files
committed
add keyinit to better mimick behavior of ssh-keygen
1 parent 61f4962 commit 8cd766e

File tree

2 files changed

+101
-29
lines changed

2 files changed

+101
-29
lines changed

modules/ssh/ssh.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -373,17 +373,16 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) {
373373
}
374374

375375
if len(keys) == 0 {
376-
for i := range 3 {
377-
filename := setting.SSH.ServerHostKeys[i]
378-
filePath := filepath.Dir(filename)
379-
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
380-
log.Error("Failed to create dir %s: %v", filePath, err)
381-
}
382-
err := GenKeyPair(filename)
383-
if err != nil {
384-
log.Fatal("Failed to generate private key: %v", err)
385-
}
386-
log.Trace("New private key is generated: %s", filename)
376+
filePath := filepath.Dir(setting.SSH.ServerHostKeys[0])
377+
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
378+
log.Error("Failed to create dir %s: %v", filePath, err)
379+
}
380+
err := initDefaultKeys(filePath)
381+
if err != nil {
382+
log.Fatal("Failed to generate private key: %v", err)
383+
}
384+
for _, keytype := range []string{"rsa", "ecdsa", "ed25519"} {
385+
filename := filePath + "/gitea." + keytype
387386
keys = append(keys, filename)
388387
}
389388
}
@@ -405,17 +404,12 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) {
405404
// GenKeyPair make a pair of public and private keys for SSH access.
406405
// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
407406
// Private Key generated is PEM encoded
408-
func GenKeyPair(keyPath string) error {
407+
func GenKeyPair(keyPath, keytype string) error {
409408
bits := 4096
410-
keytype := filepath.Ext(keyPath)
411-
if keytype == ".ed25519" {
412-
keytype = "ed25519"
413-
} else if keytype == ".ecdsa" {
409+
if keytype == "ecdsa" {
414410
bits = 256
415-
keytype = "ecdsa"
416-
} else {
417-
keytype = "rsa"
418411
}
412+
419413
publicKey, privateKeyPEM, err := generate.NewSSHKey(keytype, bits)
420414
if err != nil {
421415
return err
@@ -448,3 +442,19 @@ func GenKeyPair(keyPath string) error {
448442
_, err = p.Write(public)
449443
return err
450444
}
445+
446+
// initDefaultKeys mirrors how ssh-keygen -A operates
447+
// it runs checks if public and private keys are already defined and creates new ones if not present
448+
// key naming does not follow the openssh convention due to existing settings being gitea.{keytype} so generation follows gitea convention
449+
func initDefaultKeys(path string) error {
450+
var errs []error
451+
keytypes := []string{"rsa", "ecdsa", "ed25519"}
452+
for _, keytype := range keytypes {
453+
privExists, _ := util.IsExist(path + "/gitea." + keytype)
454+
pubExists, _ := util.IsExist(path + "/gitea." + keytype + ".pub")
455+
if !privExists || !pubExists {
456+
errs = append(errs, GenKeyPair(path+"/gitea."+keytype, keytype))
457+
}
458+
}
459+
return errors.Join(errs...)
460+
}

modules/ssh/ssh_test.go

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2025 The Gitea Authors. All rights reserved.
22
// SPDX-License-Identifier: MIT
33

4-
package ssh_test
4+
package ssh
55

66
import (
77
"crypto/ecdsa"
@@ -12,35 +12,33 @@ import (
1212
"path/filepath"
1313
"testing"
1414

15-
"code.gitea.io/gitea/modules/ssh"
16-
1715
"github.com/stretchr/testify/assert"
1816
"github.com/stretchr/testify/require"
1917
gossh "golang.org/x/crypto/ssh"
2018
)
2119

2220
func TestGenKeyPair(t *testing.T) {
2321
testCases := []struct {
24-
keyPath string
22+
keyType string
2523
expectedType any
2624
}{
2725
{
28-
keyPath: "/gitea.rsa",
26+
keyType: "rsa",
2927
expectedType: &rsa.PrivateKey{},
3028
},
3129
{
32-
keyPath: "/gitea.ed25519",
30+
keyType: "ed25519",
3331
expectedType: &ed25519.PrivateKey{},
3432
},
3533
{
36-
keyPath: "/gitea.ecdsa",
34+
keyType: "ecdsa",
3735
expectedType: &ecdsa.PrivateKey{},
3836
},
3937
}
4038
for _, tC := range testCases {
41-
t.Run("Generate "+filepath.Ext(tC.keyPath), func(t *testing.T) {
42-
path := t.TempDir() + tC.keyPath
43-
require.NoError(t, ssh.GenKeyPair(path))
39+
t.Run("Generate "+filepath.Ext(tC.keyType), func(t *testing.T) {
40+
path := t.TempDir() + "gitea." + tC.keyType
41+
require.NoError(t, GenKeyPair(path, tC.keyType))
4442

4543
file, err := os.Open(path)
4644
require.NoError(t, err)
@@ -53,4 +51,68 @@ func TestGenKeyPair(t *testing.T) {
5351
assert.IsType(t, tC.expectedType, privateKey)
5452
})
5553
}
54+
t.Run("Generate unknown keytype", func(t *testing.T) {
55+
path := t.TempDir() + "gitea.badkey"
56+
57+
err := GenKeyPair(path, "badkey")
58+
require.Error(t, err)
59+
})
60+
}
61+
62+
func TestInitKeys(t *testing.T) {
63+
tempDir := t.TempDir()
64+
65+
keytypes := []string{"rsa", "ecdsa", "ed25519"}
66+
for _, keytype := range keytypes {
67+
privKeyPath := filepath.Join(tempDir, "gitea."+keytype)
68+
pubKeyPath := filepath.Join(tempDir, "gitea."+keytype+".pub")
69+
assert.NoFileExists(t, privKeyPath)
70+
assert.NoFileExists(t, pubKeyPath)
71+
}
72+
73+
// Test basic creation
74+
err := initDefaultKeys(tempDir)
75+
require.NoError(t, err)
76+
77+
metadata := map[string]os.FileInfo{}
78+
for _, keytype := range keytypes {
79+
privKeyPath := filepath.Join(tempDir, "gitea."+keytype)
80+
pubKeyPath := filepath.Join(tempDir, "gitea."+keytype+".pub")
81+
assert.FileExists(t, privKeyPath)
82+
assert.FileExists(t, pubKeyPath)
83+
84+
info, err := os.Stat(privKeyPath)
85+
require.NoError(t, err)
86+
metadata[privKeyPath] = info
87+
88+
info, err = os.Stat(pubKeyPath)
89+
require.NoError(t, err)
90+
metadata[pubKeyPath] = info
91+
}
92+
93+
// Test recreation on missing public or private key
94+
require.NoError(t, os.Remove(filepath.Join(tempDir, "gitea.ecdsa.pub")))
95+
require.NoError(t, os.Remove(filepath.Join(tempDir, "gitea.ed25519")))
96+
97+
err = initDefaultKeys(tempDir)
98+
require.NoError(t, err)
99+
100+
for _, keytype := range keytypes {
101+
privKeyPath := filepath.Join(tempDir, "gitea."+keytype)
102+
pubKeyPath := filepath.Join(tempDir, "gitea."+keytype+".pub")
103+
assert.FileExists(t, privKeyPath)
104+
assert.FileExists(t, pubKeyPath)
105+
106+
infoPriv, err := os.Stat(privKeyPath)
107+
require.NoError(t, err)
108+
infoPub, err := os.Stat(pubKeyPath)
109+
require.NoError(t, err)
110+
if keytype == "rsa" {
111+
assert.Equal(t, metadata[privKeyPath], infoPriv)
112+
assert.Equal(t, metadata[pubKeyPath], infoPub)
113+
} else {
114+
assert.NotEqual(t, metadata[privKeyPath], infoPriv)
115+
assert.NotEqual(t, metadata[pubKeyPath], infoPub)
116+
}
117+
}
56118
}

0 commit comments

Comments
 (0)