Files
forgetting/internal/iauth/iauth.go
T
maeneko 59e076c1de Start the Go rewrite: jsjson, awg-ctrl port, differential bench
- 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
2026-10-14 13:27:24 +00:00

123 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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())
}