mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package generate
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/hanzoai/git/modules/consts"
|
|
"github.com/hanzoai/git/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.
|
|
func NewInternalToken() (string, error) {
|
|
secretBytes := make([]byte, 32)
|
|
_, err := io.ReadFull(rand.Reader, secretBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
secretKey := base64.RawURLEncoding.EncodeToString(secretBytes)
|
|
|
|
now := time.Now()
|
|
|
|
var internalToken string
|
|
internalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"nbf": now.Unix(),
|
|
}).SignedString([]byte(secretKey))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return internalToken, nil
|
|
}
|
|
|
|
const defaultJwtSecretLen = 32
|
|
|
|
// DecodeJwtSecretBase64 decodes a base64 encoded jwt secret into bytes, and check its length
|
|
func DecodeJwtSecretBase64(src string) ([]byte, error) {
|
|
encoding := base64.RawURLEncoding
|
|
decoded := make([]byte, encoding.DecodedLen(len(src))+3)
|
|
if n, err := encoding.Decode(decoded, []byte(src)); err != nil {
|
|
return nil, err
|
|
} else if n != defaultJwtSecretLen {
|
|
return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, defaultJwtSecretLen)
|
|
}
|
|
return decoded[:defaultJwtSecretLen], nil
|
|
}
|
|
|
|
// NewJwtSecretWithBase64 generates a jwt secret with its base64 encoded value intended to be used for saving into config file
|
|
func NewJwtSecretWithBase64() ([]byte, string) {
|
|
bytes := make([]byte, defaultJwtSecretLen)
|
|
_, err := rand.Read(bytes)
|
|
if err != nil {
|
|
panic(err) // rand.Read never fails
|
|
}
|
|
return bytes, base64.RawURLEncoding.EncodeToString(bytes)
|
|
}
|
|
|
|
// NewSecretKey generate a new value intended to be used by SECRET_KEY.
|
|
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)
|
|
}
|
|
}
|