main
go 96 lines 3.05 KB
Raw
1 package auth
2
3 import (
4 "errors"
5 "fmt"
6 "strings"
7 "time"
8
9 "github.com/gosuda/portal-tunnel/v2/portal/identity"
10 "github.com/gosuda/portal-tunnel/v2/types"
11 "github.com/gosuda/portal-tunnel/v2/utils"
12 )
13
14 var ErrHopRouteSignatureInvalid = errors.New("hop route signature is invalid")
15
16 func normalizeHopRoute(route *types.HopRoute, requireOwner bool) error {
17 ownerPublicKey := strings.ToLower(utils.TrimHexPrefix(strings.TrimSpace(route.OwnerPublicKey)))
18 if ownerPublicKey != "" {
19 if _, err := identity.ParseSecp256k1PublicKeyHex(ownerPublicKey); err != nil {
20 return fmt.Errorf("hop route owner public key: %w", err)
21 }
22 } else if requireOwner {
23 return errors.New("hop route owner public key is required")
24 }
25
26 relayURL, err := utils.NormalizeRelayURL(route.RelayURL)
27 if err != nil {
28 return fmt.Errorf("hop relay url: %w", err)
29 }
30
31 route.OwnerPublicKey = ownerPublicKey
32 route.RelayURL = relayURL
33 route.PublicHostname = utils.NormalizeHostname(route.PublicHostname)
34 route.RouteHostname = utils.NormalizeHostname(route.RouteHostname)
35 route.HostnameHash = strings.TrimSpace(route.HostnameHash)
36 route.MatchToken = strings.TrimSpace(route.MatchToken)
37 route.Metadata = route.Metadata.Copy()
38 route.ForwardToken = strings.TrimSpace(route.ForwardToken)
39 route.ExpiresAt = route.ExpiresAt.UTC()
40 route.Signature = strings.TrimSpace(route.Signature)
41 return nil
42 }
43
44 func SignHopRoute(method string, route types.HopRoute, authority identity.Authority, expiresAt time.Time) (types.HopRoute, error) {
45 if authority == nil {
46 return types.HopRoute{}, errors.New("hop route signing authority is required")
47 }
48 route.ExpiresAt = expiresAt.UTC()
49 route.Signature = ""
50 route.OwnerPublicKey = ""
51
52 if err := normalizeHopRoute(&route, false); err != nil {
53 return types.HopRoute{}, err
54 }
55 ownerIdentity := authority.Identity()
56 ownerPublicKey := strings.ToLower(utils.TrimHexPrefix(strings.TrimSpace(ownerIdentity.PublicKey)))
57 if ownerPublicKey == "" {
58 return types.HopRoute{}, errors.New("hop route owner identity is required")
59 }
60 if _, err := identity.ParseSecp256k1PublicKeyHex(ownerPublicKey); err != nil {
61 return types.HopRoute{}, fmt.Errorf("hop route owner public key: %w", err)
62 }
63
64 route.OwnerPublicKey = ownerPublicKey
65 payload, err := types.HopRouteBytes(method, route)
66 if err != nil {
67 return types.HopRoute{}, err
68 }
69 signature, err := authority.SignSHA256Secp256k1(payload)
70 if err != nil {
71 return types.HopRoute{}, err
72 }
73 route.Signature, err = signature.DERHex()
74 if err != nil {
75 return types.HopRoute{}, err
76 }
77 return route, nil
78 }
79
80 func VerifyHopRoute(method string, route types.HopRoute) (types.HopRoute, error) {
81 signature := strings.TrimSpace(route.Signature)
82 route.Signature = ""
83
84 if err := normalizeHopRoute(&route, true); err != nil {
85 return types.HopRoute{}, err
86 }
87 payload, err := types.HopRouteBytes(method, route)
88 if err != nil {
89 return types.HopRoute{}, err
90 }
91 if err := identity.VerifySHA256Secp256k1DER(payload, route.OwnerPublicKey, signature); err != nil {
92 return types.HopRoute{}, ErrHopRouteSignatureInvalid
93 }
94 route.Signature = signature
95 return route, nil
96 }