Skip to content

Commit 1ef0194

Browse files
committed
add keygen command
make key generation happen in generation module
1 parent ee93970 commit 1ef0194

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

cmd/generate.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
package cmd
66

77
import (
8+
"encoding/pem"
89
"fmt"
910
"os"
1011

1112
"code.gitea.io/gitea/modules/generate"
13+
"golang.org/x/crypto/ssh"
1214

1315
"github.com/mattn/go-isatty"
1416
"github.com/urfave/cli/v2"
@@ -21,6 +23,7 @@ var (
2123
Usage: "Generate Gitea's secrets/keys/tokens",
2224
Subcommands: []*cli.Command{
2325
subcmdSecret,
26+
subcmdKeygen,
2427
},
2528
}
2629

@@ -33,6 +36,17 @@ var (
3336
microcmdGenerateSecretKey,
3437
},
3538
}
39+
keygenFlags = []cli.Flag{
40+
&cli.StringFlag{Name: "bits", Aliases: []string{"b"}, Usage: "Number of bits in the key, ignored when key is ed25519"},
41+
&cli.StringFlag{Name: "type", Aliases: []string{"t"}, Value: "ed25519", Usage: "Keytype to generate"},
42+
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "Specifies the filename of the key file", Required: true},
43+
}
44+
subcmdKeygen = &cli.Command{
45+
Name: "ssh-keygen",
46+
Usage: "Generate a ssh keypair",
47+
Flags: keygenFlags,
48+
Action: runGenerateKeyPair,
49+
}
3650

3751
microcmdGenerateInternalToken = &cli.Command{
3852
Name: "INTERNAL_TOKEN",
@@ -98,3 +112,34 @@ func runGenerateSecretKey(c *cli.Context) error {
98112

99113
return nil
100114
}
115+
116+
func runGenerateKeyPair(c *cli.Context) error {
117+
keytype := c.String("type")
118+
file := c.String("file")
119+
bits := c.Int("bits")
120+
// provide defaults for bits, ed25519 ignores bit length so it's ommited
121+
if bits == 0 {
122+
if keytype == "rsa" {
123+
bits = 3096
124+
} else {
125+
bits = 256
126+
}
127+
}
128+
129+
pub, priv, err := generate.NewSSHKey(keytype, bits)
130+
if err != nil {
131+
return err
132+
}
133+
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
134+
if err != nil {
135+
return err
136+
}
137+
defer f.Close()
138+
err = pem.Encode(f, priv)
139+
if err != nil {
140+
return err
141+
}
142+
return os.WriteFile(file+".pub", ssh.MarshalAuthorizedKey(pub), 0o644)
143+
144+
return nil
145+
}

modules/generate/generate.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@
55
package generate
66

77
import (
8+
"crypto"
9+
"crypto/ecdsa"
10+
"crypto/ed25519"
11+
"crypto/elliptic"
812
"crypto/rand"
13+
"crypto/rsa"
914
"encoding/base64"
15+
"encoding/pem"
1016
"fmt"
1117
"io"
1218
"time"
1319

1420
"code.gitea.io/gitea/modules/util"
21+
"golang.org/x/crypto/ssh"
1522

1623
"github.com/golang-jwt/jwt/v5"
1724
)
@@ -72,3 +79,59 @@ func NewSecretKey() (string, error) {
7279

7380
return secretKey, nil
7481
}
82+
83+
func NewSSHKey(keytype string, bits int) (ssh.PublicKey, *pem.Block, error) {
84+
pub, priv, err := commonKeyGen(keytype, bits)
85+
if err != nil {
86+
return nil, nil, err
87+
}
88+
pemPriv, err := ssh.MarshalPrivateKey(priv, "")
89+
if err != nil {
90+
return nil, nil, err
91+
}
92+
sshPub, err := ssh.NewPublicKey(pub)
93+
if err != nil {
94+
return nil, nil, err
95+
}
96+
97+
return sshPub, pemPriv, nil
98+
}
99+
100+
// commonKeyGen is an abstraction over rsa, ecdsa and ed25519 generating functions
101+
func commonKeyGen(keytype string, bits int) (publicKey crypto.PublicKey, privateKey crypto.PublicKey, err error) {
102+
switch keytype {
103+
case "rsa":
104+
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
105+
if err != nil {
106+
return nil, nil, err
107+
}
108+
return &privateKey.PublicKey, privateKey, nil
109+
case "ed25519":
110+
return ed25519.GenerateKey(rand.Reader)
111+
case "ecdsa":
112+
curve, err := getElipticCurve(bits)
113+
if err != nil {
114+
return nil, nil, err
115+
}
116+
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
117+
if err != nil {
118+
return nil, nil, err
119+
}
120+
return &privateKey.PublicKey, privateKey, nil
121+
default:
122+
return nil, nil, fmt.Errorf("unknown keytype: %s", keytype)
123+
}
124+
}
125+
126+
func getElipticCurve(bits int) (elliptic.Curve, error) {
127+
switch bits {
128+
case 256:
129+
return elliptic.P256(), nil
130+
case 384:
131+
return elliptic.P384(), nil
132+
case 521:
133+
return elliptic.P521(), nil
134+
default:
135+
return nil, fmt.Errorf("unsupported ECDSA curve bit length: %d", bits)
136+
}
137+
}

0 commit comments

Comments
 (0)