feat(ssh): auto generate additional ssh keys (#33974)

adds capabilities for gitea to generate ecdsa and ed25519 keys by
default
adds cli for built-in ssh key generation helpers


closes: https://github.com/go-gitea/gitea/issues/33783

---------

Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
TheFox0x7
2026-06-08 18:18:58 +00:00
committed by GitHub
co-authored by Nicolas wxiaoguang Giteabot
parent ade76fe838
commit d76a974b24
9 changed files with 350 additions and 73 deletions
+80
View File
@@ -5,15 +5,23 @@
package generate
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/pem"
"fmt"
"io"
"time"
"gitea.dev/modules/consts"
"gitea.dev/modules/util"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/ssh"
)
// NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN.
@@ -67,3 +75,75 @@ func NewJwtSecretWithBase64() ([]byte, string) {
func NewSecretKey() (string, error) {
return util.CryptoRandomString(64), nil
}
type SSHKeyType string
const (
SSHKeyRSA SSHKeyType = "rsa"
SSHKeyECDSA SSHKeyType = "ecdsa"
SSHKeyED25519 SSHKeyType = "ed25519"
)
func NewSSHKey(keyType SSHKeyType, bits int) (ssh.PublicKey, *pem.Block, error) {
pub, priv, err := commonKeyGen(keyType, bits)
if err != nil {
return nil, nil, err
}
pemPriv, err := ssh.MarshalPrivateKey(priv, "")
if err != nil {
return nil, nil, err
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return nil, nil, err
}
return sshPub, pemPriv, nil
}
// commonKeyGen is an abstraction over rsa, ecdsa, and ed25519 generating functions
func commonKeyGen(keyType SSHKeyType, bits int) (crypto.PublicKey, crypto.PrivateKey, error) {
switch keyType {
case SSHKeyRSA:
bits = util.IfZero(bits, consts.AsymKeyDefaultBitsRsa)
if bits < consts.AsymKeyMinBitsRsa {
return nil, nil, util.NewInvalidArgumentErrorf("invalid rsa bits: %d", bits)
}
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
}
return &privateKey.PublicKey, privateKey, nil
case SSHKeyED25519:
return ed25519.GenerateKey(rand.Reader)
case SSHKeyECDSA:
bits = util.IfZero(bits, consts.AsymKeyDefaultBitsEcdsa)
if bits < consts.AsymKeyMinBitsEC {
return nil, nil, util.NewInvalidArgumentErrorf("invalid elliptic-curve bits: %d", bits)
}
curve, err := getEllipticCurve(bits)
if err != nil {
return nil, nil, err
}
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
return &privateKey.PublicKey, privateKey, nil
default:
return nil, nil, util.NewInvalidArgumentErrorf("unknown key type: %s", keyType)
}
}
func getEllipticCurve(bits int) (elliptic.Curve, error) {
switch bits {
case 256:
return elliptic.P256(), nil
case 384:
return elliptic.P384(), nil
case 521:
return elliptic.P521(), nil
default:
return nil, util.NewInvalidArgumentErrorf("unsupported elliptic-curve bits: %d", bits)
}
}