main
go 50 lines 1.42 KB
Raw
1 package identity
2
3 import (
4 "crypto/hmac"
5 "crypto/rand"
6 "crypto/sha256"
7 "encoding/base64"
8 "errors"
9 "fmt"
10 "strconv"
11 "strings"
12
13 "github.com/gosuda/portal-tunnel/v2/types"
14 )
15
16 func ensureTokenSecret(identity types.Identity) (types.Identity, error) {
17 identity = identity.Copy()
18 identity.TokenSecret = strings.TrimSpace(identity.TokenSecret)
19 if identity.TokenSecret != "" {
20 return identity, nil
21 }
22
23 var secret [32]byte
24 if _, err := rand.Read(secret[:]); err != nil {
25 return types.Identity{}, fmt.Errorf("generate token secret: %w", err)
26 }
27 identity.TokenSecret = base64.RawURLEncoding.EncodeToString(secret[:])
28 return identity, nil
29 }
30
31 // DeriveToken derives a deterministic identity-scoped token from ordered
32 // length-prefixed token parts. The first part should identify the token family.
33 func DeriveToken(identity types.Identity, parts ...string) (string, error) {
34 tokenSecret := strings.TrimSpace(identity.TokenSecret)
35 if tokenSecret == "" {
36 return "", errors.New("identity token secret is required")
37 }
38
39 mac := hmac.New(sha256.New, []byte(tokenSecret))
40 _, _ = mac.Write([]byte("Portal identity token v1\n"))
41 _, _ = mac.Write([]byte(identity.Key()))
42 for _, part := range parts {
43 part = strings.TrimSpace(part)
44 _, _ = mac.Write([]byte("\n"))
45 _, _ = mac.Write([]byte(strconv.Itoa(len(part))))
46 _, _ = mac.Write([]byte(":"))
47 _, _ = mac.Write([]byte(part))
48 }
49 return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)), nil
50 }