main
go 96 lines 3.53 KB
Raw
1 package auth
2
3 import (
4 "encoding/base64"
5 "encoding/hex"
6 "errors"
7 "fmt"
8 "strings"
9
10 "github.com/gosuda/portal-tunnel/v2/portal/identity"
11 "github.com/gosuda/portal-tunnel/v2/types"
12 )
13
14 // SignRelayDescriptor returns a copy of desc with its Signature field
15 // populated by signing the canonical bytes with authority. The signature is
16 // recoverable, so verifiers do not need to know the public key out of band;
17 // they recover it from the signature and check it derives the descriptor's
18 // Address field.
19 func SignRelayDescriptor(desc types.RelayDescriptor, authority identity.Authority) (types.RelayDescriptor, error) {
20 if authority == nil {
21 return types.RelayDescriptor{}, errors.New("relay descriptor signing authority is required")
22 }
23 signingIdentity := authority.Identity()
24 if desc.Address == "" {
25 desc.Address = signingIdentity.Address
26 }
27
28 desc.Signature = ""
29 normalized, err := identity.NormalizeRelayDescriptor(desc)
30 if err != nil {
31 return types.RelayDescriptor{}, fmt.Errorf("normalize relay descriptor for signing: %w", err)
32 }
33 if signingIdentity.Address != "" && !strings.EqualFold(strings.TrimSpace(signingIdentity.Address), strings.TrimSpace(normalized.Address)) {
34 return types.RelayDescriptor{}, errors.New("relay descriptor address does not match signing authority")
35 }
36 desc = normalized
37
38 canonical, err := types.CanonicalBytes(desc)
39 if err != nil {
40 return types.RelayDescriptor{}, fmt.Errorf("canonicalize relay descriptor: %w", err)
41 }
42 signature, err := authority.SignSHA256Secp256k1(canonical)
43 if err != nil {
44 return types.RelayDescriptor{}, err
45 }
46 compactSignature, err := signature.Compact()
47 if err != nil {
48 return types.RelayDescriptor{}, err
49 }
50
51 desc.Signature = base64.StdEncoding.EncodeToString(compactSignature)
52 return desc, nil
53 }
54
55 // VerifyRelayDescriptor checks the descriptor's signature against its
56 // canonical bytes and confirms that the recovered signing key corresponds to
57 // the descriptor's Address field. It returns the verified normalized
58 // descriptor on success.
59 func VerifyRelayDescriptor(desc types.RelayDescriptor) (types.RelayDescriptor, error) {
60 rawSignature := strings.TrimSpace(desc.Signature)
61 if rawSignature == "" {
62 return types.RelayDescriptor{}, errors.New("relay descriptor is not signed")
63 }
64
65 signature, err := base64.StdEncoding.DecodeString(rawSignature)
66 if err != nil {
67 return types.RelayDescriptor{}, fmt.Errorf("relay descriptor signature is invalid: base64 decode: %w", err)
68 }
69
70 unsignedCopy := desc
71 unsignedCopy.Signature = ""
72 normalized, err := identity.NormalizeRelayDescriptor(unsignedCopy)
73 if err != nil {
74 return types.RelayDescriptor{}, fmt.Errorf("relay descriptor signature is invalid: normalize: %w", err)
75 }
76 canonical, err := types.CanonicalBytes(normalized)
77 if err != nil {
78 return types.RelayDescriptor{}, fmt.Errorf("canonicalize relay descriptor: %w", err)
79 }
80
81 publicKey, err := identity.RecoverSHA256Secp256k1Compact(canonical, signature)
82 if err != nil {
83 return types.RelayDescriptor{}, fmt.Errorf("relay descriptor signature is invalid: %w", err)
84 }
85
86 publicKeyHex := hex.EncodeToString(publicKey.SerializeCompressed())
87 derivedAddress, err := identity.AddressFromCompressedPublicKeyHex(publicKeyHex)
88 if err != nil {
89 return types.RelayDescriptor{}, fmt.Errorf("derive address from recovered key: %w", err)
90 }
91 if !strings.EqualFold(strings.TrimSpace(derivedAddress), strings.TrimSpace(normalized.Address)) {
92 return types.RelayDescriptor{}, errors.New("relay descriptor address does not match recovered signing key")
93 }
94 normalized.Signature = rawSignature
95 return normalized, nil
96 }