safing-portmaster/spn/access/token/scramble.go
Daniel Hååvi 80664d1a27
Restructure modules (#1572)
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

240 lines
5.5 KiB
Go

package token
import (
"fmt"
"sync"
"github.com/mr-tron/base58"
"github.com/safing/jess/lhash"
"github.com/safing/structures/dsd"
)
const (
scrambleSecretSize = 32
)
// ScrambleToken is token based on hashing.
type ScrambleToken struct {
Token []byte
}
// Pack packs the token.
func (pbt *ScrambleToken) Pack() ([]byte, error) {
return pbt.Token, nil
}
// UnpackScrambleToken unpacks the token.
func UnpackScrambleToken(token []byte) (*ScrambleToken, error) {
return &ScrambleToken{Token: token}, nil
}
// ScrambleHandler is a handler for the scramble tokens.
type ScrambleHandler struct {
sync.Mutex
opts *ScrambleOptions
storageLock sync.Mutex
Storage []*ScrambleToken
verifiersLock sync.RWMutex
verifiers map[string]*ScrambleToken
}
// ScrambleOptions are options for the ScrambleHandler.
type ScrambleOptions struct {
Zone string
Algorithm lhash.Algorithm
InitialTokens []string
InitialVerifiers []string
Fallback bool
}
// ScrambleTokenRequest is a token request.
type ScrambleTokenRequest struct{}
// IssuedScrambleTokens are issued scrambled tokens.
type IssuedScrambleTokens struct {
Tokens []*ScrambleToken
}
// NewScrambleHandler creates a new scramble handler.
func NewScrambleHandler(opts ScrambleOptions) (*ScrambleHandler, error) {
sh := &ScrambleHandler{
opts: &opts,
verifiers: make(map[string]*ScrambleToken, len(opts.InitialTokens)+len(opts.InitialVerifiers)),
}
// Add initial tokens.
sh.Storage = make([]*ScrambleToken, len(opts.InitialTokens))
for i, token := range opts.InitialTokens {
// Add to storage.
tokenData, err := base58.Decode(token)
if err != nil {
return nil, fmt.Errorf("failed to decode initial token %q: %w", token, err)
}
sh.Storage[i] = &ScrambleToken{
Token: tokenData,
}
// Add to verifiers.
scrambledToken := lhash.Digest(sh.opts.Algorithm, tokenData).Bytes()
sh.verifiers[string(scrambledToken)] = sh.Storage[i]
}
// Add initial verifiers.
for _, verifier := range opts.InitialVerifiers {
verifierData, err := base58.Decode(verifier)
if err != nil {
return nil, fmt.Errorf("failed to decode verifier %q: %w", verifier, err)
}
sh.verifiers[string(verifierData)] = &ScrambleToken{}
}
return sh, nil
}
// Zone returns the zone name.
func (sh *ScrambleHandler) Zone() string {
return sh.opts.Zone
}
// ShouldRequest returns whether the new tokens should be requested.
func (sh *ScrambleHandler) ShouldRequest() bool {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
return len(sh.Storage) == 0
}
// Amount returns the current amount of tokens in this handler.
func (sh *ScrambleHandler) Amount() int {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
return len(sh.Storage)
}
// IsFallback returns whether this handler should only be used as a fallback.
func (sh *ScrambleHandler) IsFallback() bool {
return sh.opts.Fallback
}
// CreateTokenRequest creates a token request to be sent to the token server.
func (sh *ScrambleHandler) CreateTokenRequest() (request *ScrambleTokenRequest) {
return &ScrambleTokenRequest{}
}
// IssueTokens sign the requested tokens.
func (sh *ScrambleHandler) IssueTokens(request *ScrambleTokenRequest) (response *IssuedScrambleTokens, err error) {
// Copy the storage.
tokens := make([]*ScrambleToken, len(sh.Storage))
copy(tokens, sh.Storage)
return &IssuedScrambleTokens{
Tokens: tokens,
}, nil
}
// ProcessIssuedTokens processes the issued token from the server.
func (sh *ScrambleHandler) ProcessIssuedTokens(issuedTokens *IssuedScrambleTokens) error {
sh.verifiersLock.RLock()
defer sh.verifiersLock.RUnlock()
// Validate tokens.
for i, newToken := range issuedTokens.Tokens {
// Scramle token.
scrambledToken := lhash.Digest(sh.opts.Algorithm, newToken.Token).Bytes()
// Check if token is valid.
_, ok := sh.verifiers[string(scrambledToken)]
if !ok {
return fmt.Errorf("invalid token on #%d", i)
}
}
// Copy to storage.
sh.Storage = issuedTokens.Tokens
return nil
}
// Verify verifies the given token.
func (sh *ScrambleHandler) Verify(token *Token) error {
if token.Zone != sh.opts.Zone {
return ErrZoneMismatch
}
// Hash the data.
scrambledToken := lhash.Digest(sh.opts.Algorithm, token.Data).Bytes()
sh.verifiersLock.RLock()
defer sh.verifiersLock.RUnlock()
// Check if token is valid.
_, ok := sh.verifiers[string(scrambledToken)]
if !ok {
return ErrTokenInvalid
}
return nil
}
// GetToken returns a token.
func (sh *ScrambleHandler) GetToken() (*Token, error) {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
if len(sh.Storage) == 0 {
return nil, ErrEmpty
}
return &Token{
Zone: sh.opts.Zone,
Data: sh.Storage[0].Token,
}, nil
}
// ScrambleStorage is a storage for scramble tokens.
type ScrambleStorage struct {
Storage []*ScrambleToken
}
// Save serializes and returns the current tokens.
func (sh *ScrambleHandler) Save() ([]byte, error) {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
if len(sh.Storage) == 0 {
return nil, ErrEmpty
}
s := &ScrambleStorage{
Storage: sh.Storage,
}
return dsd.Dump(s, dsd.CBOR)
}
// Load loads the given tokens into the handler.
func (sh *ScrambleHandler) Load(data []byte) error {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
s := &ScrambleStorage{}
_, err := dsd.Load(data, s)
if err != nil {
return err
}
sh.Storage = s.Storage
return nil
}
// Clear clears all the tokens in the handler.
func (sh *ScrambleHandler) Clear() {
sh.storageLock.Lock()
defer sh.storageLock.Unlock()
sh.Storage = nil
}