feat: refactor DeriveToken to accept multiple parts and update related usages
Kim committed
Apr 17, 2026 at 11:47 UTC
4ddb79daf560b9c58c4d7a926c07f86dedffd756
4 files changed
+26
-24
portal/auth/hop_route.go
+6
-15
@@ -3,7 +3,6 @@ package auth
3
import (
4
"crypto/sha256"
5
"encoding/hex"
6
- "encoding/json"
6
"errors"
7
"fmt"
8
"strings"
@@ -24,20 +23,12 @@ func SignHopRoute(method string, route types.HopRoute, identity types.Identity,
23
if err != nil {
24
return types.HopRoute{}, err
25
}
27
- ownerScope := struct {
28
- RelayURL string `json:"relay_url"`
29
- MatchHostname string `json:"match_hostname"`
30
- MatchToken string `json:"match_token"`
31
- }{
32
- RelayURL: route.RelayURL,
33
- MatchHostname: route.MatchHostname,
34
- MatchToken: route.MatchToken,
35
- }
36
- encodedOwnerScope, err := json.Marshal(ownerScope)
37
- if err != nil {
38
- return types.HopRoute{}, err
39
- }
40
- ownerToken, err := identity.DeriveToken("hop-owner:" + string(encodedOwnerScope) + ":0")
26
+ ownerToken, err := identity.DeriveToken(
27
+ "hop-route-owner",
28
+ route.RelayURL,
29
+ route.MatchHostname,
30
+ route.MatchToken,
31
+ )
32
if err != nil {
33
return types.HopRoute{}, err
34
}
sdk/api_client.go
+8
-1
@@ -8,6 +8,7 @@ import (
8
"net/http"
9
"net/url"
10
"slices"
11
+ "strconv"
12
"strings"
13
"time"
14
@@ -127,7 +128,13 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
128
129
tokens := make([]string, len(hopPath)-1)
130
for i := range tokens {
130
- token, err := l.identity.DeriveToken(fmt.Sprintf("hop:%s:%d:%s:%s", publicHostname, i, hopPath[i].APIHTTPSAddr, hopPath[i+1].APIHTTPSAddr))
131
+ token, err := l.identity.DeriveToken(
132
+ "hop-token",
133
+ publicHostname,
134
+ strconv.Itoa(i),
135
+ hopPath[i].APIHTTPSAddr,
136
+ hopPath[i+1].APIHTTPSAddr,
137
+ )
138
if err != nil {
139
return types.RegisterResponse{}, nil, err
140
}
types/identity.go
+11
-7
@@ -6,6 +6,7 @@ import (
6
"encoding/base64"
7
"encoding/json"
8
"errors"
9
+ "strconv"
10
"strings"
11
"time"
12
)
@@ -61,21 +62,24 @@ func (i Identity) Key() string {
62
return name + IdentityKeySeparator + address
63
}
64
64
-func (i Identity) DeriveToken(nonce string) (string, error) {
65
+// DeriveToken derives a deterministic identity-scoped token from ordered
66
+// length-prefixed token parts. The first part should identify the token family.
67
+func (i Identity) DeriveToken(parts ...string) (string, error) {
68
privateKey := strings.TrimSpace(i.PrivateKey)
69
if privateKey == "" {
70
return "", errors.New("identity private key is required")
71
}
69
- nonce = strings.TrimSpace(nonce)
70
- if nonce == "" {
71
- return "", errors.New("identity token nonce is required")
72
- }
72
73
mac := hmac.New(sha256.New, []byte(privateKey))
74
_, _ = mac.Write([]byte("Portal identity token v1\n"))
75
_, _ = mac.Write([]byte(i.Key()))
77
- _, _ = mac.Write([]byte("\n"))
78
- _, _ = mac.Write([]byte(nonce))
76
+ for _, part := range parts {
77
+ part = strings.TrimSpace(part)
78
+ _, _ = mac.Write([]byte("\n"))
79
+ _, _ = mac.Write([]byte(strconv.Itoa(len(part))))
80
+ _, _ = mac.Write([]byte(":"))
81
+ _, _ = mac.Write([]byte(part))
82
+ }
83
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)), nil
84
}
85
utils/identity.go
+1
-1
@@ -464,7 +464,7 @@ func populateRelayIdentity(identity *types.RelayIdentity, discoveryEnabled bool)
464
}
465
466
if strings.TrimSpace(identity.AdminSecretKey) == "" {
467
- adminSecretKey, err := identity.Identity.DeriveToken("admin")
467
+ adminSecretKey, err := identity.Identity.DeriveToken("admin-secret")
468
if err != nil {
469
return fmt.Errorf("derive relay admin secret key: %w", err)
470
}