mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
257 lines
7.2 KiB
Go
257 lines
7.2 KiB
Go
package access
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/tevino/abool"
|
|
|
|
"github.com/safing/jess/lhash"
|
|
"github.com/safing/portbase/log"
|
|
"github.com/safing/portmaster/spn/access/token"
|
|
"github.com/safing/portmaster/spn/conf"
|
|
"github.com/safing/portmaster/spn/terminal"
|
|
)
|
|
|
|
var (
|
|
// ExpandAndConnectZones are the zones that grant access to the expand and
|
|
// connect operations.
|
|
ExpandAndConnectZones = []string{"pblind1", "alpha2", "fallback1"}
|
|
|
|
zonePermissions = map[string]terminal.Permission{
|
|
"pblind1": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
|
|
"alpha2": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
|
|
"fallback1": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
|
|
}
|
|
persistentZones = ExpandAndConnectZones
|
|
|
|
enableTestMode = abool.New()
|
|
)
|
|
|
|
// EnableTestMode enables the test mode, leading the access module to only
|
|
// register a test zone.
|
|
// This should not be used to test the access module itself.
|
|
func EnableTestMode() {
|
|
enableTestMode.Set()
|
|
}
|
|
|
|
// InitializeZones initialized the permission zones.
|
|
// It initializes the test zones, if EnableTestMode was called before.
|
|
// Must only be called once.
|
|
func InitializeZones() error {
|
|
// Check if we are testing.
|
|
if enableTestMode.IsSet() {
|
|
return initializeTestZone()
|
|
}
|
|
|
|
// Special client zone config.
|
|
var requestSignalHandler func(token.Handler)
|
|
if conf.Client() {
|
|
requestSignalHandler = shouldRequestTokensHandler
|
|
}
|
|
|
|
// Register pblind1 as the first primary zone.
|
|
ph, err := token.NewPBlindHandler(token.PBlindOptions{
|
|
Zone: "pblind1",
|
|
CurveName: "P-256",
|
|
PublicKey: "eXoJXzXbM66UEsM2eVi9HwyBPLMfVnNrC7gNrsfMUJDs",
|
|
UseSerials: true,
|
|
BatchSize: 1000,
|
|
RandomizeOrder: true,
|
|
SignalShouldRequest: requestSignalHandler,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pblind1 token handler: %w", err)
|
|
}
|
|
err = token.RegisterPBlindHandler(ph)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to register pblind1 token handler: %w", err)
|
|
}
|
|
|
|
// Register fallback1 zone as fallback when the issuer is not available.
|
|
sh, err := token.NewScrambleHandler(token.ScrambleOptions{
|
|
Zone: "fallback1",
|
|
Algorithm: lhash.BLAKE2b_256,
|
|
InitialVerifiers: []string{"ZwkQoaAttVBMURzeLzNXokFBMAMUUwECfM1iHojcVKBmjk"},
|
|
Fallback: true,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create fallback1 token handler: %w", err)
|
|
}
|
|
err = token.RegisterScrambleHandler(sh)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to register fallback1 token handler: %w", err)
|
|
}
|
|
|
|
// Register alpha2 zone for transition phase.
|
|
sh, err = token.NewScrambleHandler(token.ScrambleOptions{
|
|
Zone: "alpha2",
|
|
Algorithm: lhash.BLAKE2b_256,
|
|
InitialVerifiers: []string{"ZwojEvXZmAv7SZdNe7m94Xzu7F9J8vULqKf7QYtoTpN2tH"},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create alpha2 token handler: %w", err)
|
|
}
|
|
err = token.RegisterScrambleHandler(sh)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to register alpha2 token handler: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func initializeTestZone() error {
|
|
// Safeguard checks if we should really enable the test zone.
|
|
if !strings.HasSuffix(os.Args[0], ".test") {
|
|
return errors.New("tried to enable test mode, but no test binary was detected")
|
|
}
|
|
if token.RegistrySize() > 0 {
|
|
return fmt.Errorf("tried to enable test zone, but %d handlers are already registered", token.RegistrySize())
|
|
}
|
|
|
|
// Reset zones.
|
|
token.ResetRegistry()
|
|
|
|
// Set eligible zones.
|
|
ExpandAndConnectZones = []string{"unittest"}
|
|
zonePermissions = map[string]terminal.Permission{
|
|
"unittest": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
|
|
}
|
|
|
|
// Register unittest zone as for testing.
|
|
sh, err := token.NewScrambleHandler(token.ScrambleOptions{
|
|
Zone: "unittest",
|
|
Algorithm: lhash.BLAKE2b_256,
|
|
InitialTokens: []string{"6jFqLA93uSLL52utGKrvctG3ZfopSQ8WFqjsRK1c2Svt"},
|
|
InitialVerifiers: []string{"ZwoEoL59sr81s7WnF2vydGzjeejE3u8CqVafig1NTQzUr7"},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create unittest token handler: %w", err)
|
|
}
|
|
err = token.RegisterScrambleHandler(sh)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to register unittest token handler: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func shouldRequestTokensHandler(_ token.Handler) {
|
|
// accountUpdateTask is always set in client mode and when the module is online.
|
|
// Check if it's set in case this gets executed in other circumstances.
|
|
if accountUpdateTask == nil {
|
|
log.Warningf("spn/access: trying to trigger account update, but the task is not available")
|
|
return
|
|
}
|
|
|
|
accountUpdateTask.StartASAP()
|
|
}
|
|
|
|
// GetTokenAmount returns the amount of tokens for the given zones.
|
|
func GetTokenAmount(zones []string) (regular, fallback int) {
|
|
handlerLoop:
|
|
for _, zone := range zones {
|
|
// Get handler and check if it should be used.
|
|
handler, ok := token.GetHandler(zone)
|
|
if !ok {
|
|
log.Warningf("spn/access: use of non-registered zone %q", zone)
|
|
continue handlerLoop
|
|
}
|
|
|
|
if handler.IsFallback() {
|
|
fallback += handler.Amount()
|
|
} else {
|
|
regular += handler.Amount()
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ShouldRequest returns whether tokens should be requested for the given zones.
|
|
func ShouldRequest(zones []string) (shouldRequest bool) {
|
|
handlerLoop:
|
|
for _, zone := range zones {
|
|
// Get handler and check if it should be used.
|
|
handler, ok := token.GetHandler(zone)
|
|
if !ok {
|
|
log.Warningf("spn/access: use of non-registered zone %q", zone)
|
|
continue handlerLoop
|
|
}
|
|
|
|
// Go through all handlers every time as this will be the case anyway most
|
|
// of the time and will help us better catch zone misconfiguration.
|
|
if handler.ShouldRequest() {
|
|
shouldRequest = true
|
|
}
|
|
}
|
|
|
|
return shouldRequest
|
|
}
|
|
|
|
// GetToken returns a token of one of the given zones.
|
|
func GetToken(zones []string) (t *token.Token, err error) {
|
|
handlerSelection:
|
|
for _, zone := range zones {
|
|
// Get handler and check if it should be used.
|
|
handler, ok := token.GetHandler(zone)
|
|
switch {
|
|
case !ok:
|
|
log.Warningf("spn/access: use of non-registered zone %q", zone)
|
|
continue handlerSelection
|
|
case handler.IsFallback() && !TokenIssuerIsFailing():
|
|
// Skip fallback zone if everything works.
|
|
continue handlerSelection
|
|
}
|
|
|
|
// Get token from handler.
|
|
t, err = token.GetToken(zone)
|
|
if err == nil {
|
|
return t, nil
|
|
}
|
|
}
|
|
|
|
// Return existing error, if exists.
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, token.ErrEmpty
|
|
}
|
|
|
|
// VerifyRawToken verifies a raw token.
|
|
func VerifyRawToken(data []byte) (granted terminal.Permission, err error) {
|
|
t, err := token.ParseRawToken(data)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to parse token: %w", err)
|
|
}
|
|
|
|
return VerifyToken(t)
|
|
}
|
|
|
|
// VerifyToken verifies a token.
|
|
func VerifyToken(t *token.Token) (granted terminal.Permission, err error) {
|
|
handler, ok := token.GetHandler(t.Zone)
|
|
if !ok {
|
|
return terminal.NoPermission, token.ErrZoneUnknown
|
|
}
|
|
|
|
// Check if the token is a fallback token.
|
|
if handler.IsFallback() && !healthCheck() {
|
|
return terminal.NoPermission, ErrFallbackNotAvailable
|
|
}
|
|
|
|
// Verify token.
|
|
err = handler.Verify(t)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to verify token: %w", err)
|
|
}
|
|
|
|
// Return permission of zone.
|
|
granted, ok = zonePermissions[t.Zone]
|
|
if !ok {
|
|
return terminal.NoPermission, nil
|
|
}
|
|
return granted, nil
|
|
}
|