mirror of
https://github.com/wavetermdev/waveterm
synced 2026-04-21 22:47:16 +00:00
Lots of updates across all parts of the system to get this working. Big changes to routing, streaming, connection management, etc. * Persistent sessions behind a metadata flag for now * New backlog queue in the router to prevent hanging * Fix connection Close() issues that caused hangs when network was down * Fix issue with random routeids (need to be generated fresh each time the JWT is used and not fixed) so you can run multiple-wsh commands at once * Fix issue with domain sockets changing names across wave restarts (added a symlink mechanism to resolve new names) * ClientId caching in main server * Quick reorder queue for input to prevent out of order delivery across multiple hops * Fix out-of-order event delivery in router (remove unnecessary go routine creation) * Environment testing and fix environment variables for remote jobs (get from connserver, add to remote job starts) * Add new ConnServerInit() remote method to call before marking connection up * TODO -- remote file transfer needs to be fixed to not create OOM issues when transferring large files or directories
143 lines
3.2 KiB
Go
143 lines
3.2 KiB
Go
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package wavejwt
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
const (
|
|
IssuerWaveTerm = "waveterm"
|
|
)
|
|
|
|
var (
|
|
globalLock sync.Mutex
|
|
publicKey ed25519.PublicKey
|
|
privateKey ed25519.PrivateKey
|
|
)
|
|
|
|
type WaveJwtClaims struct {
|
|
jwt.RegisteredClaims
|
|
MainServer bool `json:"mainserver,omitempty"`
|
|
Sock string `json:"sock,omitempty"`
|
|
RouteId string `json:"routeid,omitempty"`
|
|
ProcRoute bool `json:"procroute,omitempty"`
|
|
BlockId string `json:"blockid,omitempty"`
|
|
JobId string `json:"jobid,omitempty"`
|
|
Conn string `json:"conn,omitempty"`
|
|
Router bool `json:"router,omitempty"`
|
|
}
|
|
|
|
type KeyPair struct {
|
|
PublicKey []byte
|
|
PrivateKey []byte
|
|
}
|
|
|
|
func GenerateKeyPair() (*KeyPair, error) {
|
|
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate key pair: %w", err)
|
|
}
|
|
|
|
return &KeyPair{
|
|
PublicKey: pubKey,
|
|
PrivateKey: privKey,
|
|
}, nil
|
|
}
|
|
|
|
func SetPublicKey(keyData []byte) error {
|
|
if len(keyData) != ed25519.PublicKeySize {
|
|
return fmt.Errorf("invalid public key size: expected %d, got %d", ed25519.PublicKeySize, len(keyData))
|
|
}
|
|
globalLock.Lock()
|
|
defer globalLock.Unlock()
|
|
publicKey = ed25519.PublicKey(keyData)
|
|
return nil
|
|
}
|
|
|
|
func GetPublicKey() []byte {
|
|
globalLock.Lock()
|
|
defer globalLock.Unlock()
|
|
return publicKey
|
|
}
|
|
|
|
func GetPublicKeyBase64() string {
|
|
pubKey := GetPublicKey()
|
|
if len(pubKey) == 0 {
|
|
return ""
|
|
}
|
|
return base64.StdEncoding.EncodeToString(pubKey)
|
|
}
|
|
|
|
func SetPrivateKey(keyData []byte) error {
|
|
if len(keyData) != ed25519.PrivateKeySize {
|
|
return fmt.Errorf("invalid private key size: expected %d, got %d", ed25519.PrivateKeySize, len(keyData))
|
|
}
|
|
globalLock.Lock()
|
|
defer globalLock.Unlock()
|
|
privateKey = ed25519.PrivateKey(keyData)
|
|
return nil
|
|
}
|
|
|
|
func ValidateAndExtract(tokenStr string) (*WaveJwtClaims, error) {
|
|
globalLock.Lock()
|
|
pubKey := publicKey
|
|
globalLock.Unlock()
|
|
|
|
if pubKey == nil {
|
|
return nil, fmt.Errorf("public key not set")
|
|
}
|
|
|
|
token, err := jwt.ParseWithClaims(tokenStr, &WaveJwtClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return pubKey, nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse token: %w", err)
|
|
}
|
|
|
|
claims, ok := token.Claims.(*WaveJwtClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, fmt.Errorf("invalid token")
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
func Sign(claims *WaveJwtClaims) (string, error) {
|
|
globalLock.Lock()
|
|
privKey := privateKey
|
|
globalLock.Unlock()
|
|
|
|
if privKey == nil {
|
|
return "", fmt.Errorf("private key not set")
|
|
}
|
|
|
|
if claims.IssuedAt == nil {
|
|
claims.IssuedAt = jwt.NewNumericDate(time.Now())
|
|
}
|
|
if claims.Issuer == "" {
|
|
claims.Issuer = IssuerWaveTerm
|
|
}
|
|
if claims.ExpiresAt == nil {
|
|
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 365))
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
|
|
tokenStr, err := token.SignedString(privKey)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error signing token: %w", err)
|
|
}
|
|
|
|
return tokenStr, nil
|
|
}
|