Files
m 20d361a886
build / backend (push) Has been cancelled
build / node-agent (push) Has been cancelled
build / worker (push) Has been cancelled
рабочий вариант, но скороть 10 МБит
2026-05-22 21:46:49 +03:00

370 lines
12 KiB
Go

package authority
import (
"context"
"crypto/ed25519"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
"github.com/example/remote-access-platform/backend/internal/platform/config"
postgresplatform "github.com/example/remote-access-platform/backend/internal/platform/postgres"
)
const (
ModeStrict = "strict"
ModeCompat = "compat"
ActivationSchemaVersion = "rap.installation.activation.v1"
PlatformRoleUser = "user"
PlatformRoleAdmin = "platform_admin"
PlatformRoleRecoveryAdmin = "platform_recovery_admin"
)
var (
ErrInvalidAuthorityMode = errors.New("invalid installation authority mode")
ErrProductRootKeyNeeded = errors.New("product root public key is required")
ErrInvalidActivation = errors.New("invalid installation activation")
ErrInvalidGrant = errors.New("invalid platform role grant")
)
type ActivationPayload struct {
SchemaVersion string `json:"schema_version"`
InstallID string `json:"install_id"`
OwnerEmail string `json:"owner_email"`
PlatformRole string `json:"platform_role"`
IssuedAt time.Time `json:"issued_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Nonce string `json:"nonce,omitempty"`
Environment string `json:"environment,omitempty"`
}
type Verifier struct {
mode string
rootPublicKey ed25519.PublicKey
rootFingerprint string
allowInsecureBootstrap bool
now func() time.Time
}
func NewVerifier(cfg config.InstallationConfig) (*Verifier, error) {
mode := strings.ToLower(strings.TrimSpace(cfg.AuthorityMode))
if mode == "" {
mode = ModeCompat
}
verifier := &Verifier{
mode: mode,
allowInsecureBootstrap: cfg.AllowInsecureBootstrap,
now: time.Now,
}
switch mode {
case ModeCompat:
return verifier, nil
case ModeStrict:
publicKey, err := decodeEd25519PublicKey(cfg.ProductRootPublicKeyBase64)
if err != nil {
return nil, err
}
verifier.rootPublicKey = publicKey
fingerprint := sha256.Sum256(publicKey)
verifier.rootFingerprint = hex.EncodeToString(fingerprint[:])
return verifier, nil
default:
return nil, fmt.Errorf("%w: %s", ErrInvalidAuthorityMode, mode)
}
}
func (v *Verifier) Mode() string {
if v == nil || v.mode == "" {
return ModeCompat
}
return v.mode
}
func (v *Verifier) Strict() bool {
return v != nil && v.mode == ModeStrict
}
func (v *Verifier) AllowInsecureBootstrap() bool {
return v != nil && v.allowInsecureBootstrap
}
func (v *Verifier) RootFingerprint() string {
if v == nil {
return ""
}
return v.rootFingerprint
}
func (v *Verifier) VerifyActivation(payload json.RawMessage, signature string) (ActivationPayload, error) {
if v == nil || !v.Strict() {
return ActivationPayload{}, ErrProductRootKeyNeeded
}
activation, canonical, err := parseActivationPayload(payload)
if err != nil {
return ActivationPayload{}, err
}
if err := activation.validate(v.now().UTC()); err != nil {
return ActivationPayload{}, err
}
if err := v.verifySignature(canonical, signature); err != nil {
return ActivationPayload{}, fmt.Errorf("%w: %v", ErrInvalidActivation, err)
}
return activation, nil
}
func (v *Verifier) VerifyPlatformRoleGrant(payload json.RawMessage, signature, expectedInstallID, expectedEmail, expectedRole string) (ActivationPayload, error) {
activation, err := v.VerifyActivation(payload, signature)
if err != nil {
return ActivationPayload{}, fmt.Errorf("%w: %v", ErrInvalidGrant, err)
}
if activation.InstallID != strings.TrimSpace(expectedInstallID) {
return ActivationPayload{}, fmt.Errorf("%w: install_id mismatch", ErrInvalidGrant)
}
if !strings.EqualFold(activation.OwnerEmail, strings.TrimSpace(expectedEmail)) {
return ActivationPayload{}, fmt.Errorf("%w: owner_email mismatch", ErrInvalidGrant)
}
if activation.PlatformRole != strings.TrimSpace(expectedRole) {
return ActivationPayload{}, fmt.Errorf("%w: platform_role mismatch", ErrInvalidGrant)
}
return activation, nil
}
func CanonicalJSON(raw json.RawMessage) ([]byte, error) {
if len(raw) == 0 {
return nil, fmt.Errorf("%w: empty payload", ErrInvalidActivation)
}
var value any
if err := json.Unmarshal(raw, &value); err != nil {
return nil, fmt.Errorf("%w: invalid json: %v", ErrInvalidActivation, err)
}
canonical, err := json.Marshal(value)
if err != nil {
return nil, fmt.Errorf("%w: canonical json: %v", ErrInvalidActivation, err)
}
return canonical, nil
}
func EffectivePlatformRole(ctx context.Context, db postgresplatform.DBTX, verifier *Verifier, userID string) (string, error) {
userID = strings.TrimSpace(userID)
if userID == "" {
return PlatformRoleUser, nil
}
if verifier == nil || !verifier.Strict() {
return storedPlatformRole(ctx, db, userID)
}
var email string
if err := db.QueryRow(ctx, `SELECT email FROM users WHERE id = $1::uuid`, userID).Scan(&email); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return PlatformRoleUser, nil
}
return "", fmt.Errorf("get user email for platform grant: %w", err)
}
rows, err := db.Query(ctx, `
SELECT prg.role, prg.install_id, prg.grant_payload, prg.grant_signature
FROM platform_role_grants prg
JOIN installation_authority ia
ON ia.id = 1
AND ia.install_id = prg.install_id
AND ia.authority_state = 'active'
WHERE prg.user_id = $1::uuid
AND prg.revoked_at IS NULL
AND (prg.expires_at IS NULL OR prg.expires_at > NOW())
ORDER BY CASE prg.role
WHEN 'platform_recovery_admin' THEN 0
WHEN 'platform_admin' THEN 1
ELSE 2
END, prg.granted_at DESC
`, userID)
if err != nil {
return "", fmt.Errorf("query platform role grants: %w", err)
}
defer rows.Close()
bestRole := PlatformRoleUser
for rows.Next() {
var role, installID, signature string
var payload []byte
if err := rows.Scan(&role, &installID, &payload, &signature); err != nil {
return "", fmt.Errorf("scan platform role grant: %w", err)
}
if _, err := verifier.VerifyPlatformRoleGrant(json.RawMessage(payload), signature, installID, email, role); err != nil {
continue
}
if role == PlatformRoleRecoveryAdmin {
return role, nil
}
if role == PlatformRoleAdmin {
bestRole = role
}
}
if err := rows.Err(); err != nil {
return "", fmt.Errorf("iterate platform role grants: %w", err)
}
if bestRole == PlatformRoleUser {
if role, ok, err := strictBootstrappedOwnerFallback(ctx, db, verifier, userID, email); err != nil {
return "", err
} else if ok {
return role, nil
}
return storedPlatformRole(ctx, db, userID)
}
return bestRole, nil
}
func strictBootstrappedOwnerFallback(ctx context.Context, db postgresplatform.DBTX, verifier *Verifier, userID, email string) (string, bool, error) {
var role string
var bootstrappedOwnerEmail *string
var authorityState string
var rootFingerprint string
err := db.QueryRow(ctx, `
SELECT u.platform_role, ia.bootstrapped_owner_email, ia.authority_state, ia.product_root_key_fingerprint
FROM users u
CROSS JOIN installation_authority ia
WHERE u.id = $1::uuid
AND ia.id = 1
`, userID).Scan(&role, &bootstrappedOwnerEmail, &authorityState, &rootFingerprint)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return PlatformRoleUser, false, nil
}
return "", false, fmt.Errorf("query strict bootstrapped owner fallback: %w", err)
}
if bootstrappedOwnerEmail == nil ||
!strings.EqualFold(*bootstrappedOwnerEmail, email) ||
authorityState != "active" ||
rootFingerprint != verifier.RootFingerprint() {
return PlatformRoleUser, false, nil
}
switch role {
case PlatformRoleAdmin, PlatformRoleRecoveryAdmin:
return role, true, nil
default:
return PlatformRoleUser, false, nil
}
}
func storedPlatformRole(ctx context.Context, db postgresplatform.DBTX, userID string) (string, error) {
var role string
if err := db.QueryRow(ctx, `SELECT platform_role FROM users WHERE id = $1::uuid`, userID).Scan(&role); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return PlatformRoleUser, nil
}
return "", fmt.Errorf("get platform role: %w", err)
}
if role == "" {
return PlatformRoleUser, nil
}
return role, nil
}
func parseActivationPayload(raw json.RawMessage) (ActivationPayload, []byte, error) {
canonical, err := CanonicalJSON(raw)
if err != nil {
return ActivationPayload{}, nil, err
}
var activation ActivationPayload
if err := json.Unmarshal(canonical, &activation); err != nil {
return ActivationPayload{}, nil, fmt.Errorf("%w: decode activation: %v", ErrInvalidActivation, err)
}
activation.SchemaVersion = strings.TrimSpace(activation.SchemaVersion)
activation.InstallID = strings.TrimSpace(activation.InstallID)
activation.OwnerEmail = strings.ToLower(strings.TrimSpace(activation.OwnerEmail))
activation.PlatformRole = strings.TrimSpace(activation.PlatformRole)
activation.Nonce = strings.TrimSpace(activation.Nonce)
activation.Environment = strings.TrimSpace(activation.Environment)
return activation, canonical, nil
}
func (p ActivationPayload) validate(now time.Time) error {
if p.SchemaVersion != ActivationSchemaVersion {
return fmt.Errorf("%w: schema_version must be %s", ErrInvalidActivation, ActivationSchemaVersion)
}
if p.InstallID == "" {
return fmt.Errorf("%w: install_id is required", ErrInvalidActivation)
}
if p.OwnerEmail == "" || !strings.Contains(p.OwnerEmail, "@") {
return fmt.Errorf("%w: owner_email is required", ErrInvalidActivation)
}
switch p.PlatformRole {
case PlatformRoleAdmin, PlatformRoleRecoveryAdmin:
default:
return fmt.Errorf("%w: platform_role must be platform_admin or platform_recovery_admin", ErrInvalidActivation)
}
if p.IssuedAt.IsZero() {
return fmt.Errorf("%w: issued_at is required", ErrInvalidActivation)
}
if p.IssuedAt.After(now.Add(5 * time.Minute)) {
return fmt.Errorf("%w: issued_at is too far in the future", ErrInvalidActivation)
}
if p.ExpiresAt != nil && !p.ExpiresAt.After(now) {
return fmt.Errorf("%w: activation expired", ErrInvalidActivation)
}
return nil
}
func (v *Verifier) verifySignature(payload []byte, signatureText string) error {
signature, err := decodeBase64(strings.TrimSpace(signatureText))
if err != nil {
return fmt.Errorf("signature must be base64 encoded: %w", err)
}
if len(signature) != ed25519.SignatureSize {
return fmt.Errorf("signature must decode to %d bytes", ed25519.SignatureSize)
}
if !ed25519.Verify(v.rootPublicKey, payload, signature) {
return errors.New("signature verification failed")
}
return nil
}
func decodeEd25519PublicKey(value string) (ed25519.PublicKey, error) {
value = strings.TrimSpace(value)
if value == "" {
return nil, ErrProductRootKeyNeeded
}
if block, _ := pem.Decode([]byte(value)); block != nil {
parsed, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse product root public key PEM: %w", err)
}
publicKey, ok := parsed.(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("product root public key PEM must contain an Ed25519 public key")
}
return publicKey, nil
}
decoded, err := decodeBase64(value)
if err != nil {
return nil, fmt.Errorf("product root public key must be base64 encoded: %w", err)
}
if len(decoded) != ed25519.PublicKeySize {
return nil, fmt.Errorf("product root public key must decode to %d bytes", ed25519.PublicKeySize)
}
return ed25519.PublicKey(decoded), nil
}
func decodeBase64(value string) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(value)
if err == nil {
return decoded, nil
}
decoded, rawErr := base64.RawStdEncoding.DecodeString(value)
if rawErr == nil {
return decoded, nil
}
return nil, err
}