- internal/jsjson: JSON.stringify-compatible encoder and order-preserving decoder, tested against fixtures produced by Node itself - internal/ctrl: awg-ctrl in Go (params, vpn:// keys, users, peers, sync, 3.1 upgrade, CAKE shaping, metrics, monitor job, Express-compatible HTTP layer) - internal/iauth, internal/logx: internal Ed25519 tokens and logging - tools/diffbench: Docker bench with stubbed awg/ip/tc/nft that runs one scenario against the TS and Go implementations for comparison
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
// Package iauth — внутренние токены между awg-ui / awg-agent / CLI и awg-ctrl.
|
||
//
|
||
// Формат — «JWT» с EdDSA: header {"alg":"EdDSA","typ":"JWT"}, payload {iss,iat,exp},
|
||
// подпись Ed25519 над ASCII "h.p". Ключи — PEM от openssl: PKCS#8 (закрытый) и SPKI
|
||
// (открытый). Проверка намеренно такая же нестрогая, как у Node-версии: только подпись
|
||
// и exp (alg/iss/лишние сегменты не смотрим) — ужесточение отдельным шагом (zero trust).
|
||
package iauth
|
||
|
||
import (
|
||
"crypto/ed25519"
|
||
"crypto/x509"
|
||
"encoding/base64"
|
||
"encoding/pem"
|
||
"errors"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/maeneko/forgetting/internal/jsjson"
|
||
)
|
||
|
||
func decodePEM(file string) ([]byte, error) {
|
||
raw, err := os.ReadFile(file)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
blk, _ := pem.Decode(raw)
|
||
if blk == nil {
|
||
return nil, errors.New("iauth: not a PEM file: " + file)
|
||
}
|
||
return blk.Bytes, nil
|
||
}
|
||
|
||
// LoadPublic читает открытый ключ (SPKI PEM).
|
||
func LoadPublic(file string) (ed25519.PublicKey, error) {
|
||
der, err := decodePEM(file)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := x509.ParsePKIXPublicKey(der)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pub, ok := k.(ed25519.PublicKey)
|
||
if !ok {
|
||
return nil, errors.New("iauth: not an Ed25519 public key")
|
||
}
|
||
return pub, nil
|
||
}
|
||
|
||
// LoadPrivate читает закрытый ключ (PKCS#8 PEM).
|
||
func LoadPrivate(file string) (ed25519.PrivateKey, error) {
|
||
der, err := decodePEM(file)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := x509.ParsePKCS8PrivateKey(der)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
priv, ok := k.(ed25519.PrivateKey)
|
||
if !ok {
|
||
return nil, errors.New("iauth: not an Ed25519 private key")
|
||
}
|
||
return priv, nil
|
||
}
|
||
|
||
var b64 = base64.RawURLEncoding
|
||
|
||
// Mint выпускает токен на 60 с.
|
||
func Mint(priv ed25519.PrivateKey, iss string, now time.Time) string {
|
||
h := b64.EncodeToString([]byte(`{"alg":"EdDSA","typ":"JWT"}`))
|
||
t := now.Unix()
|
||
p := b64.EncodeToString(jsjson.MustMarshal(jsjson.Object{{Key: "iss", Val: iss}, {Key: "iat", Val: t}, {Key: "exp", Val: t + 60}}))
|
||
sig := ed25519.Sign(priv, []byte(h+"."+p))
|
||
return h + "." + p + "." + b64.EncodeToString(sig)
|
||
}
|
||
|
||
// lenient повторяет Buffer.from(s, "base64url") в Node: терпит паддинг, «+», «/».
|
||
func lenient(s string) ([]byte, error) {
|
||
s = strings.NewReplacer("+", "-", "/", "_").Replace(strings.TrimRight(s, "="))
|
||
return b64.DecodeString(s)
|
||
}
|
||
|
||
// Verify — подпись верна и exp в будущем.
|
||
func Verify(pub ed25519.PublicKey, token string, now time.Time) bool {
|
||
parts := strings.Split(token, ".")
|
||
if len(parts) < 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" {
|
||
return false
|
||
}
|
||
sig, err := lenient(parts[2])
|
||
if err != nil || !ed25519.Verify(pub, []byte(parts[0]+"."+parts[1]), sig) {
|
||
return false
|
||
}
|
||
raw, err := lenient(parts[1])
|
||
if err != nil {
|
||
return false
|
||
}
|
||
v, err := jsjson.Decode(raw)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
obj, ok := v.(jsjson.Object)
|
||
if !ok {
|
||
return false
|
||
}
|
||
var exp float64
|
||
switch e := obj.Get2("exp").(type) {
|
||
case float64:
|
||
exp = e
|
||
case string: // JS сравнил бы числовую строку как число
|
||
f, err := strconv.ParseFloat(strings.TrimSpace(e), 64)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
exp = f
|
||
default:
|
||
return false
|
||
}
|
||
return exp > float64(now.Unix())
|
||
}
|