Browse Source

implement SASL OAUTHBEARER and draft/bearer (#2122)

* implement SASL OAUTHBEARER and draft/bearer
* Upgrade JWT lib
* Fix an edge case in SASL EXTERNAL
* Accept longer SASL responses
* review fix: allow multiple token definitions
* enhance tests
* use SASL utilities from irc-go
* test expired tokens
pull/2131/head
Shivaram Lingamneni 2 months ago
parent
commit
ee7f818674
No account linked to committer's email address
58 changed files with 2867 additions and 974 deletions
  1. 34
    0
      default.yaml
  2. 6
    0
      gencapdefs.py
  3. 3
    2
      go.mod
  4. 4
    2
      go.sum
  5. 77
    2
      irc/accounts.go
  6. 3
    1
      irc/authscript.go
  7. 4
    0
      irc/caps/constants.go
  8. 6
    1
      irc/caps/defs.go
  9. 13
    2
      irc/client.go
  10. 39
    7
      irc/config.go
  11. 1
    0
      irc/errors.go
  12. 100
    60
      irc/handlers.go
  13. 157
    0
      irc/jwt/bearer.go
  14. 143
    0
      irc/jwt/bearer_test.go
  15. 3
    18
      irc/jwt/extjwt.go
  16. 108
    0
      irc/oauth2/oauth2.go
  17. 172
    0
      irc/oauth2/sasl.go
  18. 34
    0
      traditional.yaml
  19. 105
    0
      vendor/github.com/ergochat/irc-go/ircutils/sasl.go
  20. 0
    22
      vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md
  21. 0
    113
      vendor/github.com/golang-jwt/jwt/README.md
  22. 0
    146
      vendor/github.com/golang-jwt/jwt/claims.go
  23. 0
    59
      vendor/github.com/golang-jwt/jwt/errors.go
  24. 0
    120
      vendor/github.com/golang-jwt/jwt/map_claims.go
  25. 0
    148
      vendor/github.com/golang-jwt/jwt/parser.go
  26. 0
    35
      vendor/github.com/golang-jwt/jwt/signing_method.go
  27. 0
    104
      vendor/github.com/golang-jwt/jwt/token.go
  28. 0
    0
      vendor/github.com/golang-jwt/jwt/v5/.gitignore
  29. 0
    0
      vendor/github.com/golang-jwt/jwt/v5/LICENSE
  30. 195
    0
      vendor/github.com/golang-jwt/jwt/v5/MIGRATION_GUIDE.md
  31. 167
    0
      vendor/github.com/golang-jwt/jwt/v5/README.md
  32. 19
    0
      vendor/github.com/golang-jwt/jwt/v5/SECURITY.md
  33. 12
    6
      vendor/github.com/golang-jwt/jwt/v5/VERSION_HISTORY.md
  34. 16
    0
      vendor/github.com/golang-jwt/jwt/v5/claims.go
  35. 0
    0
      vendor/github.com/golang-jwt/jwt/v5/doc.go
  36. 11
    19
      vendor/github.com/golang-jwt/jwt/v5/ecdsa.go
  37. 4
    4
      vendor/github.com/golang-jwt/jwt/v5/ecdsa_utils.go
  38. 23
    25
      vendor/github.com/golang-jwt/jwt/v5/ed25519.go
  39. 4
    4
      vendor/github.com/golang-jwt/jwt/v5/ed25519_utils.go
  40. 49
    0
      vendor/github.com/golang-jwt/jwt/v5/errors.go
  41. 47
    0
      vendor/github.com/golang-jwt/jwt/v5/errors_go1_20.go
  42. 78
    0
      vendor/github.com/golang-jwt/jwt/v5/errors_go_other.go
  43. 25
    16
      vendor/github.com/golang-jwt/jwt/v5/hmac.go
  44. 109
    0
      vendor/github.com/golang-jwt/jwt/v5/map_claims.go
  45. 9
    11
      vendor/github.com/golang-jwt/jwt/v5/none.go
  46. 238
    0
      vendor/github.com/golang-jwt/jwt/v5/parser.go
  47. 128
    0
      vendor/github.com/golang-jwt/jwt/v5/parser_option.go
  48. 63
    0
      vendor/github.com/golang-jwt/jwt/v5/registered_claims.go
  49. 10
    18
      vendor/github.com/golang-jwt/jwt/v5/rsa.go
  50. 11
    18
      vendor/github.com/golang-jwt/jwt/v5/rsa_pss.go
  51. 13
    7
      vendor/github.com/golang-jwt/jwt/v5/rsa_utils.go
  52. 49
    0
      vendor/github.com/golang-jwt/jwt/v5/signing_method.go
  53. 1
    0
      vendor/github.com/golang-jwt/jwt/v5/staticcheck.conf
  54. 100
    0
      vendor/github.com/golang-jwt/jwt/v5/token.go
  55. 5
    0
      vendor/github.com/golang-jwt/jwt/v5/token_option.go
  56. 149
    0
      vendor/github.com/golang-jwt/jwt/v5/types.go
  57. 316
    0
      vendor/github.com/golang-jwt/jwt/v5/validator.go
  58. 4
    4
      vendor/modules.txt

+ 34
- 0
default.yaml View File

@@ -586,6 +586,40 @@ accounts:
586 586
         # how many scripts are allowed to run at once? 0 for no limit:
587 587
         max-concurrency: 64
588 588
 
589
+    # support for login via OAuth2 bearer tokens
590
+    oauth2:
591
+        enabled: false
592
+        # should we automatically create users on presentation of a valid token?
593
+        autocreate: true
594
+        # enable this to use auth-script for validation:
595
+        auth-script: false
596
+        introspection-url: "https://example.com/api/oidc/introspection"
597
+        introspection-timeout: 10s
598
+        # omit for auth method `none`; required for auth method `client_secret_basic`:
599
+        client-id: "ergo"
600
+        client-secret: "4TA0I7mJ3fUUcW05KJiODg"
601
+
602
+    # support for login via JWT bearer tokens
603
+    jwt-auth:
604
+        enabled: false
605
+        # should we automatically create users on presentation of a valid token?
606
+        autocreate: true
607
+        # any of these token definitions can be accepted, allowing for key rotation
608
+        tokens:
609
+            -
610
+                algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
611
+                # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
612
+                # either way, the key can be specified either as a YAML string:
613
+                key: "nANiZ1De4v6WnltCHN2H7Q"
614
+                # or as a path to the file containing the key:
615
+                #key-file: "jwt_pubkey.pem"
616
+                # list of JWT claim names to search for the user's account name (make sure the format
617
+                # is what you expect, especially if using "sub"):
618
+                account-claims: ["preferred_username"]
619
+                # if a claim is formatted as an email address, require it to have the following domain,
620
+                # and then strip off the domain and use the local-part as the account name:
621
+                #strip-domain: "example.com"
622
+
589 623
 # channel options
590 624
 channels:
591 625
     # modes that are set when new channels are created

+ 6
- 0
gencapdefs.py View File

@@ -219,6 +219,12 @@ CAPDEFS = [
219 219
         url="https://github.com/ircv3/ircv3-specifications/pull/527",
220 220
         standard="proposed IRCv3",
221 221
     ),
222
+   CapDef(
223
+        identifier="Bearer",
224
+        name="draft/bearer",
225
+        url="https://gist.github.com/slingamn/4fabc7a3d5f335da7bb313a7f0648f37",
226
+        standard="proposed IRCv3",
227
+    ),
222 228
 ]
223 229
 
224 230
 def validate_defs():

+ 3
- 2
go.mod View File

@@ -8,11 +8,10 @@ require (
8 8
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
9 9
 	github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
10 10
 	github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
11
-	github.com/ergochat/irc-go v0.4.0
11
+	github.com/ergochat/irc-go v0.5.0-rc1
12 12
 	github.com/go-sql-driver/mysql v1.7.0
13 13
 	github.com/go-test/deep v1.0.6 // indirect
14 14
 	github.com/gofrs/flock v0.8.1
15
-	github.com/golang-jwt/jwt v3.2.2+incompatible
16 15
 	github.com/gorilla/websocket v1.4.2
17 16
 	github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
18 17
 	github.com/onsi/ginkgo v1.12.0 // indirect
@@ -27,6 +26,8 @@ require (
27 26
 	gopkg.in/yaml.v2 v2.4.0
28 27
 )
29 28
 
29
+require github.com/golang-jwt/jwt/v5 v5.2.0
30
+
30 31
 require (
31 32
 	github.com/tidwall/btree v1.4.2 // indirect
32 33
 	github.com/tidwall/gjson v1.14.3 // indirect

+ 4
- 2
go.sum View File

@@ -12,6 +12,8 @@ github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 h1:+J5m88nvybxB5
12 12
 github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM=
13 13
 github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc=
14 14
 github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
15
+github.com/ergochat/irc-go v0.5.0-rc1 h1:kFoIHExoNFQ2CV+iShAVna/H4xrXQB4t4jK5Sep2j9k=
16
+github.com/ergochat/irc-go v0.5.0-rc1/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
15 17
 github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
16 18
 github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
17 19
 github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
@@ -23,8 +25,8 @@ github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
23 25
 github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
24 26
 github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
25 27
 github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
26
-github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
27
-github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
28
+github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
29
+github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
28 30
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
29 31
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
30 32
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

+ 77
- 2
irc/accounts.go View File

@@ -4,6 +4,7 @@
4 4
 package irc
5 5
 
6 6
 import (
7
+	"context"
7 8
 	"crypto/rand"
8 9
 	"crypto/x509"
9 10
 	"encoding/json"
@@ -19,10 +20,12 @@ import (
19 20
 	"github.com/tidwall/buntdb"
20 21
 	"github.com/xdg-go/scram"
21 22
 
23
+	"github.com/ergochat/ergo/irc/caps"
22 24
 	"github.com/ergochat/ergo/irc/connection_limits"
23 25
 	"github.com/ergochat/ergo/irc/email"
24 26
 	"github.com/ergochat/ergo/irc/migrations"
25 27
 	"github.com/ergochat/ergo/irc/modes"
28
+	"github.com/ergochat/ergo/irc/oauth2"
26 29
 	"github.com/ergochat/ergo/irc/passwd"
27 30
 	"github.com/ergochat/ergo/irc/utils"
28 31
 )
@@ -1395,6 +1398,10 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s
1395 1398
 		}
1396 1399
 	}
1397 1400
 
1401
+	if strings.HasPrefix(accountName, caps.BearerTokenPrefix) {
1402
+		return am.AuthenticateByBearerToken(client, strings.TrimPrefix(accountName, caps.BearerTokenPrefix), passphrase)
1403
+	}
1404
+
1398 1405
 	if throttled, remainingTime := client.checkLoginThrottle(); throttled {
1399 1406
 		return &ThrottleError{remainingTime}
1400 1407
 	}
@@ -1427,6 +1434,71 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s
1427 1434
 	return err
1428 1435
 }
1429 1436
 
1437
+func (am *AccountManager) AuthenticateByBearerToken(client *Client, tokenType, token string) (err error) {
1438
+	switch tokenType {
1439
+	case "oauth2":
1440
+		return am.AuthenticateByOAuthBearer(client, oauth2.OAuthBearerOptions{Token: token})
1441
+	case "jwt":
1442
+		return am.AuthenticateByJWT(client, token)
1443
+	default:
1444
+		return errInvalidBearerTokenType
1445
+	}
1446
+}
1447
+
1448
+func (am *AccountManager) AuthenticateByOAuthBearer(client *Client, opts oauth2.OAuthBearerOptions) (err error) {
1449
+	config := am.server.Config()
1450
+
1451
+	// we need to check this here since we can get here via SASL PLAIN:
1452
+	if !config.Accounts.OAuth2.Enabled {
1453
+		return errFeatureDisabled
1454
+	}
1455
+
1456
+	var username string
1457
+	if config.Accounts.AuthScript.Enabled && config.Accounts.OAuth2.AuthScript {
1458
+		username, err = am.authenticateByOAuthBearerScript(client, config, opts)
1459
+	} else {
1460
+		username, err = config.Accounts.OAuth2.Introspect(context.Background(), opts.Token)
1461
+	}
1462
+	if err != nil {
1463
+		return err
1464
+	}
1465
+
1466
+	account, err := am.loadWithAutocreation(username, config.Accounts.OAuth2.Autocreate)
1467
+	if err == nil {
1468
+		am.Login(client, account)
1469
+	}
1470
+	return err
1471
+}
1472
+
1473
+func (am *AccountManager) AuthenticateByJWT(client *Client, token string) (err error) {
1474
+	config := am.server.Config()
1475
+	// enabled check is encapsulated here:
1476
+	accountName, err := config.Accounts.JWTAuth.Validate(token)
1477
+	if err != nil {
1478
+		am.server.logger.Debug("accounts", "invalid JWT token", err.Error())
1479
+		return errAccountInvalidCredentials
1480
+	}
1481
+	account, err := am.loadWithAutocreation(accountName, config.Accounts.JWTAuth.Autocreate)
1482
+	if err == nil {
1483
+		am.Login(client, account)
1484
+	}
1485
+	return err
1486
+}
1487
+
1488
+func (am *AccountManager) authenticateByOAuthBearerScript(client *Client, config *Config, opts oauth2.OAuthBearerOptions) (username string, err error) {
1489
+	output, err := CheckAuthScript(am.server.semaphores.AuthScript, config.Accounts.AuthScript.ScriptConfig,
1490
+		AuthScriptInput{OAuthBearer: &opts, IP: client.IP().String()})
1491
+
1492
+	if err != nil {
1493
+		am.server.logger.Error("internal", "failed shell auth invocation", err.Error())
1494
+		return "", oauth2.ErrInvalidToken
1495
+	} else if output.Success {
1496
+		return output.AccountName, nil
1497
+	} else {
1498
+		return "", oauth2.ErrInvalidToken
1499
+	}
1500
+}
1501
+
1430 1502
 // AllNicks returns the uncasefolded nicknames for all accounts, including additional (grouped) nicks.
1431 1503
 func (am *AccountManager) AllNicks() (result []string) {
1432 1504
 	accountNamePrefix := fmt.Sprintf(keyAccountName, "")
@@ -1939,8 +2011,10 @@ func (am *AccountManager) AuthenticateByCertificate(client *Client, certfp strin
1939 2011
 		return err
1940 2012
 	}
1941 2013
 
1942
-	if authzid != "" && authzid != account {
1943
-		return errAuthzidAuthcidMismatch
2014
+	if authzid != "" {
2015
+		if cfAuthzid, err := CasefoldName(authzid); err != nil || cfAuthzid != account {
2016
+			return errAuthzidAuthcidMismatch
2017
+		}
1944 2018
 	}
1945 2019
 
1946 2020
 	// ok, we found an account corresponding to their certificate
@@ -2145,6 +2219,7 @@ var (
2145 2219
 		"PLAIN":         authPlainHandler,
2146 2220
 		"EXTERNAL":      authExternalHandler,
2147 2221
 		"SCRAM-SHA-256": authScramHandler,
2222
+		"OAUTHBEARER":   authOauthBearerHandler,
2148 2223
 	}
2149 2224
 )
2150 2225
 

+ 3
- 1
irc/authscript.go View File

@@ -10,6 +10,7 @@ import (
10 10
 	"fmt"
11 11
 	"net"
12 12
 
13
+	"github.com/ergochat/ergo/irc/oauth2"
13 14
 	"github.com/ergochat/ergo/irc/utils"
14 15
 )
15 16
 
@@ -20,7 +21,8 @@ type AuthScriptInput struct {
20 21
 	Certfp      string   `json:"certfp,omitempty"`
21 22
 	PeerCerts   []string `json:"peerCerts,omitempty"`
22 23
 	peerCerts   []*x509.Certificate
23
-	IP          string `json:"ip,omitempty"`
24
+	IP          string                     `json:"ip,omitempty"`
25
+	OAuthBearer *oauth2.OAuthBearerOptions `json:"oauth2,omitempty"`
24 26
 }
25 27
 
26 28
 type AuthScriptOutput struct {

+ 4
- 0
irc/caps/constants.go View File

@@ -64,6 +64,10 @@ const (
64 64
 	BotTagName = "bot"
65 65
 	// https://ircv3.net/specs/extensions/chathistory
66 66
 	ChathistoryTargetsBatchType = "draft/chathistory-targets"
67
+
68
+	// draft/bearer defines this prefix namespace for authcids, enabling tunneling bearer tokens
69
+	// in SASL PLAIN:
70
+	BearerTokenPrefix = "*bearer*"
67 71
 )
68 72
 
69 73
 func init() {

+ 6
- 1
irc/caps/defs.go View File

@@ -7,7 +7,7 @@ package caps
7 7
 
8 8
 const (
9 9
 	// number of recognized capabilities:
10
-	numCapabs = 34
10
+	numCapabs = 35
11 11
 	// length of the uint32 array that represents the bitset:
12 12
 	bitsetLen = 2
13 13
 )
@@ -41,6 +41,10 @@ const (
41 41
 	// https://github.com/ircv3/ircv3-specifications/pull/435
42 42
 	AccountRegistration Capability = iota
43 43
 
44
+	// Bearer is the proposed IRCv3 capability named "draft/bearer":
45
+	// https://gist.github.com/slingamn/4fabc7a3d5f335da7bb313a7f0648f37
46
+	Bearer Capability = iota
47
+
44 48
 	// ChannelRename is the draft IRCv3 capability named "draft/channel-rename":
45 49
 	// https://ircv3.net/specs/extensions/channel-rename
46 50
 	ChannelRename Capability = iota
@@ -160,6 +164,7 @@ var (
160 164
 		"cap-notify",
161 165
 		"chghost",
162 166
 		"draft/account-registration",
167
+		"draft/bearer",
163 168
 		"draft/channel-rename",
164 169
 		"draft/chathistory",
165 170
 		"draft/event-playback",

+ 13
- 2
irc/client.go View File

@@ -21,6 +21,7 @@ import (
21 21
 	"github.com/ergochat/irc-go/ircfmt"
22 22
 	"github.com/ergochat/irc-go/ircmsg"
23 23
 	"github.com/ergochat/irc-go/ircreader"
24
+	"github.com/ergochat/irc-go/ircutils"
24 25
 	"github.com/xdg-go/scram"
25 26
 
26 27
 	"github.com/ergochat/ergo/irc/caps"
@@ -28,6 +29,7 @@ import (
28 29
 	"github.com/ergochat/ergo/irc/flatip"
29 30
 	"github.com/ergochat/ergo/irc/history"
30 31
 	"github.com/ergochat/ergo/irc/modes"
32
+	"github.com/ergochat/ergo/irc/oauth2"
31 33
 	"github.com/ergochat/ergo/irc/sno"
32 34
 	"github.com/ergochat/ergo/irc/utils"
33 35
 )
@@ -119,12 +121,20 @@ type Client struct {
119 121
 
120 122
 type saslStatus struct {
121 123
 	mechanism string
122
-	value     string
124
+	value     ircutils.SASLBuffer
123 125
 	scramConv *scram.ServerConversation
126
+	oauthConv *oauth2.OAuthBearerServer
127
+}
128
+
129
+func (s *saslStatus) Initialize() {
130
+	s.value.Initialize(saslMaxResponseLength)
124 131
 }
125 132
 
126 133
 func (s *saslStatus) Clear() {
127
-	*s = saslStatus{}
134
+	s.mechanism = ""
135
+	s.value.Clear()
136
+	s.scramConv = nil
137
+	s.oauthConv = nil
128 138
 }
129 139
 
130 140
 // what stage the client is at w.r.t. the PASS command:
@@ -362,6 +372,7 @@ func (server *Server) RunClient(conn IRCConn) {
362 372
 		isTor:      wConn.Tor,
363 373
 		hideSTS:    wConn.Tor || wConn.HideSTS,
364 374
 	}
375
+	session.sasl.Initialize()
365 376
 	client.sessions = []*Session{session}
366 377
 
367 378
 	session.resetFakelag()

+ 39
- 7
irc/config.go View File

@@ -38,6 +38,7 @@ import (
38 38
 	"github.com/ergochat/ergo/irc/logger"
39 39
 	"github.com/ergochat/ergo/irc/modes"
40 40
 	"github.com/ergochat/ergo/irc/mysql"
41
+	"github.com/ergochat/ergo/irc/oauth2"
41 42
 	"github.com/ergochat/ergo/irc/passwd"
42 43
 	"github.com/ergochat/ergo/irc/utils"
43 44
 )
@@ -331,7 +332,9 @@ type AccountConfig struct {
331 332
 	Multiclient MulticlientConfig
332 333
 	Bouncer     *MulticlientConfig // # handle old name for 'multiclient'
333 334
 	VHosts      VHostConfig
334
-	AuthScript  AuthScriptConfig `yaml:"auth-script"`
335
+	AuthScript  AuthScriptConfig          `yaml:"auth-script"`
336
+	OAuth2      oauth2.OAuth2BearerConfig `yaml:"oauth2"`
337
+	JWTAuth     jwt.JWTAuthConfig         `yaml:"jwt-auth"`
335 338
 }
336 339
 
337 340
 type ScriptConfig struct {
@@ -1391,15 +1394,44 @@ func LoadConfig(filename string) (config *Config, err error) {
1391 1394
 		config.Accounts.VHosts.validRegexp = defaultValidVhostRegex
1392 1395
 	}
1393 1396
 
1394
-	saslCapValue := "PLAIN,EXTERNAL,SCRAM-SHA-256"
1395
-	if !config.Accounts.AdvertiseSCRAM {
1396
-		saslCapValue = "PLAIN,EXTERNAL"
1397
-	}
1398
-	config.Server.capValues[caps.SASL] = saslCapValue
1399
-	if !config.Accounts.AuthenticationEnabled {
1397
+	if config.Accounts.AuthenticationEnabled {
1398
+		saslCapValues := []string{"PLAIN", "EXTERNAL"}
1399
+		if config.Accounts.AdvertiseSCRAM {
1400
+			saslCapValues = append(saslCapValues, "SCRAM-SHA-256")
1401
+		}
1402
+		if config.Accounts.OAuth2.Enabled {
1403
+			saslCapValues = append(saslCapValues, "OAUTHBEARER")
1404
+		}
1405
+		config.Server.capValues[caps.SASL] = strings.Join(saslCapValues, ",")
1406
+	} else {
1400 1407
 		config.Server.supportedCaps.Disable(caps.SASL)
1401 1408
 	}
1402 1409
 
1410
+	if err := config.Accounts.OAuth2.Postprocess(); err != nil {
1411
+		return nil, err
1412
+	}
1413
+
1414
+	if err := config.Accounts.JWTAuth.Postprocess(); err != nil {
1415
+		return nil, err
1416
+	}
1417
+
1418
+	if config.Accounts.OAuth2.Enabled && config.Accounts.OAuth2.AuthScript && !config.Accounts.AuthScript.Enabled {
1419
+		return nil, fmt.Errorf("oauth2 is enabled with auth-script, but no auth-script is enabled")
1420
+	}
1421
+
1422
+	var bearerCapValues []string
1423
+	if config.Accounts.OAuth2.Enabled {
1424
+		bearerCapValues = append(bearerCapValues, "oauth2")
1425
+	}
1426
+	if config.Accounts.JWTAuth.Enabled {
1427
+		bearerCapValues = append(bearerCapValues, "jwt")
1428
+	}
1429
+	if len(bearerCapValues) != 0 {
1430
+		config.Server.capValues[caps.Bearer] = strings.Join(bearerCapValues, ",")
1431
+	} else {
1432
+		config.Server.supportedCaps.Disable(caps.Bearer)
1433
+	}
1434
+
1403 1435
 	if !config.Accounts.Registration.Enabled {
1404 1436
 		config.Server.supportedCaps.Disable(caps.AccountRegistration)
1405 1437
 	} else {

+ 1
- 0
irc/errors.go View File

@@ -76,6 +76,7 @@ var (
76 76
 	errValidEmailRequired             = errors.New("A valid email address is required for account registration")
77 77
 	errInvalidAccountRename           = errors.New("Account renames can only change the casefolding of the account name")
78 78
 	errNameReserved                   = errors.New(`Name reserved due to a prior registration`)
79
+	errInvalidBearerTokenType         = errors.New("invalid bearer token type")
79 80
 )
80 81
 
81 82
 // String Errors

+ 100
- 60
irc/handlers.go View File

@@ -8,7 +8,6 @@ package irc
8 8
 
9 9
 import (
10 10
 	"bytes"
11
-	"encoding/base64"
12 11
 	"fmt"
13 12
 	"net"
14 13
 	"os"
@@ -31,6 +30,7 @@ import (
31 30
 	"github.com/ergochat/ergo/irc/history"
32 31
 	"github.com/ergochat/ergo/irc/jwt"
33 32
 	"github.com/ergochat/ergo/irc/modes"
33
+	"github.com/ergochat/ergo/irc/oauth2"
34 34
 	"github.com/ergochat/ergo/irc/sno"
35 35
 	"github.com/ergochat/ergo/irc/utils"
36 36
 )
@@ -178,6 +178,10 @@ func acceptHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
178 178
 	return false
179 179
 }
180 180
 
181
+const (
182
+	saslMaxResponseLength = 8192 // implementation-defined sanity check, long enough for bearer tokens
183
+)
184
+
181 185
 // AUTHENTICATE [<mechanism>|<data>|*]
182 186
 func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
183 187
 	session := rb.session
@@ -201,7 +205,7 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
201 205
 		return false
202 206
 	}
203 207
 
204
-	// start new sasl session
208
+	// start new sasl session: parameter is the authentication mechanism
205 209
 	if session.sasl.mechanism == "" {
206 210
 		throttled, remainingTime := client.loginThrottle.Touch()
207 211
 		if throttled {
@@ -213,6 +217,16 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
213 217
 		mechanism := strings.ToUpper(msg.Params[0])
214 218
 		_, mechanismIsEnabled := EnabledSaslMechanisms[mechanism]
215 219
 
220
+		// The spec says: "The AUTHENTICATE command MUST be used before registration
221
+		// is complete and with the sasl capability enabled." Enforcing this universally
222
+		// would simplify the implementation somewhat, but we've never enforced it before
223
+		// and I don't want to break working clients that use PLAIN or EXTERNAL
224
+		// and violate this MUST (e.g. by sending CAP END too early).
225
+		if client.registered && !(mechanism == "PLAIN" || mechanism == "EXTERNAL") {
226
+			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL is only allowed before connection registration"))
227
+			return false
228
+		}
229
+
216 230
 		if mechanismIsEnabled {
217 231
 			session.sasl.mechanism = mechanism
218 232
 			if !config.Server.Compatibility.SendUnprefixedSasl {
@@ -230,46 +244,28 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.Message, rb
230 244
 		return false
231 245
 	}
232 246
 
233
-	// continue existing sasl session
234
-	rawData := msg.Params[0]
235
-
236
-	// https://ircv3.net/specs/extensions/sasl-3.1:
237
-	// "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
238
-	// and each chunk is sent as a separate AUTHENTICATE command."
239
-	saslMaxArgLength := 400
240
-	if len(rawData) > saslMaxArgLength {
241
-		rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
242
-		session.sasl.Clear()
243
-		return false
244
-	} else if len(rawData) == saslMaxArgLength {
245
-		// allow 4 'continuation' lines before rejecting for length
246
-		if len(session.sasl.value) >= saslMaxArgLength*4 {
247
-			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
248
-			session.sasl.Clear()
249
-			return false
247
+	// continue existing sasl session: parameter is a message chunk
248
+	done, value, err := session.sasl.value.Add(msg.Params[0])
249
+	if err == nil {
250
+		if done {
251
+			// call actual handler
252
+			handler := EnabledSaslMechanisms[session.sasl.mechanism]
253
+			return handler(server, client, session, value, rb)
254
+		} else {
255
+			return false // wait for continuation line
250 256
 		}
251
-		session.sasl.value += rawData
252
-		return false
253 257
 	}
254
-	if rawData != "+" {
255
-		session.sasl.value += rawData
256
-	}
257
-
258
-	var data []byte
259
-	var err error
260
-	if session.sasl.value != "+" {
261
-		data, err = base64.StdEncoding.DecodeString(session.sasl.value)
262
-		session.sasl.value = ""
263
-		if err != nil {
264
-			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
265
-			session.sasl.Clear()
266
-			return false
267
-		}
258
+	// else: error handling
259
+	switch err {
260
+	case ircutils.ErrSASLTooLong:
261
+		rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
262
+	case ircutils.ErrSASLLimitExceeded:
263
+		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
264
+	default:
265
+		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
268 266
 	}
269
-
270
-	// call actual handler
271
-	handler := EnabledSaslMechanisms[session.sasl.mechanism]
272
-	return handler(server, client, session, data, rb)
267
+	session.sasl.Clear()
268
+	return false
273 269
 }
274 270
 
275 271
 // AUTHENTICATE PLAIN
@@ -331,7 +327,7 @@ func authErrorToMessage(server *Server, err error) (msg string) {
331 327
 	}
332 328
 
333 329
 	switch err {
334
-	case errAccountDoesNotExist, errAccountUnverified, errAccountInvalidCredentials, errAuthzidAuthcidMismatch, errNickAccountMismatch, errAccountSuspended:
330
+	case errAccountDoesNotExist, errAccountUnverified, errAccountInvalidCredentials, errAuthzidAuthcidMismatch, errNickAccountMismatch, errAccountSuspended, oauth2.ErrInvalidToken:
335 331
 		return err.Error()
336 332
 	default:
337 333
 		// don't expose arbitrary error messages to the user
@@ -351,28 +347,18 @@ func authExternalHandler(server *Server, client *Client, session *Session, value
351 347
 
352 348
 	// EXTERNAL doesn't carry an authentication ID (this is determined from the
353 349
 	// certificate), but does carry an optional authorization ID.
354
-	var authzid string
350
+	authzid := string(value)
351
+	var deviceID string
355 352
 	var err error
356
-	if len(value) != 0 {
357
-		authzid, err = CasefoldName(string(value))
358
-		if err != nil {
359
-			err = errAuthzidAuthcidMismatch
360
-		}
353
+	// see #843: strip the device ID for the benefit of clients that don't
354
+	// distinguish user/ident from account name
355
+	if strudelIndex := strings.IndexByte(authzid, '@'); strudelIndex != -1 {
356
+		authzid, deviceID = authzid[:strudelIndex], authzid[strudelIndex+1:]
361 357
 	}
362 358
 
363 359
 	if err == nil {
364
-		// see #843: strip the device ID for the benefit of clients that don't
365
-		// distinguish user/ident from account name
366
-		if strudelIndex := strings.IndexByte(authzid, '@'); strudelIndex != -1 {
367
-			var deviceID string
368
-			authzid, deviceID = authzid[:strudelIndex], authzid[strudelIndex+1:]
369
-			if !client.registered {
370
-				rb.session.deviceID = deviceID
371
-			}
372
-		}
373 360
 		err = server.accounts.AuthenticateByCertificate(client, rb.session.certfp, rb.session.peerCerts, authzid)
374 361
 	}
375
-
376 362
 	if err != nil {
377 363
 		sendAuthErrorResponse(client, rb, err)
378 364
 		return false
@@ -381,6 +367,9 @@ func authExternalHandler(server *Server, client *Client, session *Session, value
381 367
 	}
382 368
 
383 369
 	sendSuccessfulAccountAuth(nil, client, rb, true)
370
+	if !client.registered && deviceID != "" {
371
+		rb.session.deviceID = deviceID
372
+	}
384 373
 	return false
385 374
 }
386 375
 
@@ -418,9 +407,8 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
418 407
 			account, err := server.accounts.LoadAccount(authcid)
419 408
 			if err == nil {
420 409
 				server.accounts.Login(client, account)
421
-				if fixupNickEqualsAccount(client, rb, server.Config(), "") {
422
-					sendSuccessfulAccountAuth(nil, client, rb, true)
423
-				}
410
+				// fixupNickEqualsAccount is not needed for unregistered clients
411
+				sendSuccessfulAccountAuth(nil, client, rb, true)
424 412
 			} else {
425 413
 				server.logger.Error("internal", "SCRAM succeeded but couldn't load account", authcid, err.Error())
426 414
 				rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
@@ -433,7 +421,7 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
433 421
 
434 422
 	response, err := session.sasl.scramConv.Step(string(value))
435 423
 	if err == nil {
436
-		rb.Add(nil, server.name, "AUTHENTICATE", base64.StdEncoding.EncodeToString([]byte(response)))
424
+		sendSASLChallenge(server, rb, []byte(response))
437 425
 	} else {
438 426
 		continueAuth = false
439 427
 		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), err.Error())
@@ -443,6 +431,58 @@ func authScramHandler(server *Server, client *Client, session *Session, value []
443 431
 	return false
444 432
 }
445 433
 
434
+// AUTHENTICATE OAUTHBEARER
435
+func authOauthBearerHandler(server *Server, client *Client, session *Session, value []byte, rb *ResponseBuffer) bool {
436
+	if !server.Config().Accounts.OAuth2.Enabled {
437
+		rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), "SASL authentication failed: mechanism not enabled")
438
+		return false
439
+	}
440
+
441
+	if session.sasl.oauthConv == nil {
442
+		session.sasl.oauthConv = oauth2.NewOAuthBearerServer(
443
+			func(opts oauth2.OAuthBearerOptions) *oauth2.OAuthBearerError {
444
+				err := server.accounts.AuthenticateByOAuthBearer(client, opts)
445
+				switch err {
446
+				case nil:
447
+					return nil
448
+				case oauth2.ErrInvalidToken:
449
+					return &oauth2.OAuthBearerError{Status: "invalid_token", Schemes: "bearer"}
450
+				case errFeatureDisabled:
451
+					return &oauth2.OAuthBearerError{Status: "invalid_request", Schemes: "bearer"}
452
+				default:
453
+					// this is probably a misconfiguration or infrastructure error so we should log it
454
+					server.logger.Error("internal", "failed to validate OAUTHBEARER token", err.Error())
455
+					// tell the client it was their fault even though it probably wasn't:
456
+					return &oauth2.OAuthBearerError{Status: "invalid_request", Schemes: "bearer"}
457
+				}
458
+			},
459
+		)
460
+	}
461
+
462
+	challenge, done, err := session.sasl.oauthConv.Next(value)
463
+	if done {
464
+		if err == nil {
465
+			sendSuccessfulAccountAuth(nil, client, rb, true)
466
+		} else {
467
+			rb.Add(nil, server.name, ERR_SASLFAIL, client.Nick(), ircutils.SanitizeText(err.Error(), 350))
468
+		}
469
+		session.sasl.Clear()
470
+	} else {
471
+		// ignore `err`, we need to relay the challenge (which may contain a JSON-encoded error)
472
+		// to the client
473
+		sendSASLChallenge(server, rb, challenge)
474
+	}
475
+	return false
476
+}
477
+
478
+// helper to b64 a sasl response and chunk it into 400-byte lines
479
+// as per https://ircv3.net/specs/extensions/sasl-3.1
480
+func sendSASLChallenge(server *Server, rb *ResponseBuffer, challenge []byte) {
481
+	for _, chunk := range ircutils.EncodeSASLResponse(challenge) {
482
+		rb.Add(nil, server.name, "AUTHENTICATE", chunk)
483
+	}
484
+}
485
+
446 486
 // AWAY [<message>]
447 487
 func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
448 488
 	// #1996: `AWAY :` is treated the same as `AWAY`

+ 157
- 0
irc/jwt/bearer.go View File

@@ -0,0 +1,157 @@
1
+// Copyright (c) 2024 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package jwt
5
+
6
+import (
7
+	"fmt"
8
+	"io"
9
+	"os"
10
+	"strings"
11
+
12
+	jwt "github.com/golang-jwt/jwt/v5"
13
+)
14
+
15
+var (
16
+	ErrAuthDisabled        = fmt.Errorf("JWT authentication is disabled")
17
+	ErrNoValidAccountClaim = fmt.Errorf("JWT token did not contain an acceptable account name claim")
18
+)
19
+
20
+// JWTAuthConfig is the config for Ergo to accept JWTs via draft/bearer
21
+type JWTAuthConfig struct {
22
+	Enabled    bool                 `yaml:"enabled"`
23
+	Autocreate bool                 `yaml:"autocreate"`
24
+	Tokens     []JWTAuthTokenConfig `yaml:"tokens"`
25
+}
26
+
27
+type JWTAuthTokenConfig struct {
28
+	Algorithm     string `yaml:"algorithm"`
29
+	KeyString     string `yaml:"key"`
30
+	KeyFile       string `yaml:"key-file"`
31
+	key           any
32
+	parser        *jwt.Parser
33
+	AccountClaims []string `yaml:"account-claims"`
34
+	StripDomain   string   `yaml:"strip-domain"`
35
+}
36
+
37
+func (j *JWTAuthConfig) Postprocess() error {
38
+	if !j.Enabled {
39
+		return nil
40
+	}
41
+
42
+	if len(j.Tokens) == 0 {
43
+		return fmt.Errorf("JWT authentication enabled, but no valid tokens defined")
44
+	}
45
+
46
+	for i := range j.Tokens {
47
+		if err := j.Tokens[i].Postprocess(); err != nil {
48
+			return err
49
+		}
50
+	}
51
+
52
+	return nil
53
+}
54
+
55
+func (j *JWTAuthTokenConfig) Postprocess() error {
56
+	keyBytes, err := j.keyBytes()
57
+	if err != nil {
58
+		return err
59
+	}
60
+
61
+	j.Algorithm = strings.ToLower(j.Algorithm)
62
+
63
+	var methods []string
64
+	switch j.Algorithm {
65
+	case "hmac":
66
+		j.key = keyBytes
67
+		methods = []string{"HS256", "HS384", "HS512"}
68
+	case "rsa":
69
+		rsaKey, err := jwt.ParseRSAPublicKeyFromPEM(keyBytes)
70
+		if err != nil {
71
+			return err
72
+		}
73
+		j.key = rsaKey
74
+		methods = []string{"RS256", "RS384", "RS512"}
75
+	case "eddsa":
76
+		eddsaKey, err := jwt.ParseEdPublicKeyFromPEM(keyBytes)
77
+		if err != nil {
78
+			return err
79
+		}
80
+		j.key = eddsaKey
81
+		methods = []string{"EdDSA"}
82
+	default:
83
+		return fmt.Errorf("invalid jwt algorithm: %s", j.Algorithm)
84
+	}
85
+	j.parser = jwt.NewParser(jwt.WithValidMethods(methods))
86
+
87
+	if len(j.AccountClaims) == 0 {
88
+		return fmt.Errorf("JWT auth enabled, but no account-claims specified")
89
+	}
90
+
91
+	j.StripDomain = strings.ToLower(j.StripDomain)
92
+	return nil
93
+}
94
+
95
+func (j *JWTAuthConfig) Validate(t string) (accountName string, err error) {
96
+	if !j.Enabled || len(j.Tokens) == 0 {
97
+		return "", ErrAuthDisabled
98
+	}
99
+
100
+	for i := range j.Tokens {
101
+		accountName, err = j.Tokens[i].Validate(t)
102
+		if err == nil {
103
+			return
104
+		}
105
+	}
106
+	return
107
+}
108
+
109
+func (j *JWTAuthTokenConfig) keyBytes() (result []byte, err error) {
110
+	if j.KeyFile != "" {
111
+		o, err := os.Open(j.KeyFile)
112
+		if err != nil {
113
+			return nil, err
114
+		}
115
+		return io.ReadAll(o)
116
+	}
117
+	if j.KeyString != "" {
118
+		return []byte(j.KeyString), nil
119
+	}
120
+	return nil, fmt.Errorf("JWT auth enabled, but no JWT key specified")
121
+}
122
+
123
+// implements jwt.Keyfunc
124
+func (j *JWTAuthTokenConfig) keyFunc(_ *jwt.Token) (interface{}, error) {
125
+	return j.key, nil
126
+}
127
+
128
+func (j *JWTAuthTokenConfig) Validate(t string) (accountName string, err error) {
129
+	token, err := j.parser.Parse(t, j.keyFunc)
130
+	if err != nil {
131
+		return "", err
132
+	}
133
+
134
+	claims, ok := token.Claims.(jwt.MapClaims)
135
+	if !ok {
136
+		// impossible with Parse (as opposed to ParseWithClaims)
137
+		return "", fmt.Errorf("unexpected type from parsed token claims: %T", claims)
138
+	}
139
+
140
+	for _, c := range j.AccountClaims {
141
+		if v, ok := claims[c]; ok {
142
+			if vstr, ok := v.(string); ok {
143
+				// validate and strip email addresses:
144
+				if idx := strings.IndexByte(vstr, '@'); idx != -1 {
145
+					suffix := vstr[idx+1:]
146
+					vstr = vstr[:idx]
147
+					if strings.ToLower(suffix) != j.StripDomain {
148
+						continue
149
+					}
150
+				}
151
+				return vstr, nil // success
152
+			}
153
+		}
154
+	}
155
+
156
+	return "", ErrNoValidAccountClaim
157
+}

+ 143
- 0
irc/jwt/bearer_test.go View File

@@ -0,0 +1,143 @@
1
+package jwt
2
+
3
+import (
4
+	"testing"
5
+
6
+	jwt "github.com/golang-jwt/jwt/v5"
7
+)
8
+
9
+const (
10
+	rsaTestPubKey = `-----BEGIN PUBLIC KEY-----
11
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhcCcXrfR/GmoPKxBi0H
12
+cUl2pUl4acq2m3abFtMMoYTydJdEhgYWfsXuragyEIVkJU1ZnrgedW0QJUcANRGO
13
+hP/B+MjBevDNsRXQECfhyjfzhz6KWZb4i7C2oImJuAjq/F4qGLdEGQDBpAzof8qv
14
+9Zt5iN3GXY/EQtQVMFyR/7BPcbPLbHlOtzZ6tVEioXuUxQoai7x3Kc0jIcPWuyGa
15
+Q04IvsgdaWO6oH4fhPfyVsmX37rYUn79zcqPHS4ieWM1KN9qc7W+/UJIeiwAStpJ
16
+8gv+OSMrijRZGgQGCeOO5U59GGJC4mqUczB+JFvrlAIv0rggNpl+qalngosNxukB
17
+uQIDAQAB
18
+-----END PUBLIC KEY-----`
19
+
20
+	rsaTestPrivKey = `-----BEGIN PRIVATE KEY-----
21
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCFwJxet9H8aag
22
+8rEGLQdxSXalSXhpyrabdpsW0wyhhPJ0l0SGBhZ+xe6tqDIQhWQlTVmeuB51bRAl
23
+RwA1EY6E/8H4yMF68M2xFdAQJ+HKN/OHPopZlviLsLagiYm4COr8XioYt0QZAMGk
24
+DOh/yq/1m3mI3cZdj8RC1BUwXJH/sE9xs8tseU63Nnq1USKhe5TFChqLvHcpzSMh
25
+w9a7IZpDTgi+yB1pY7qgfh+E9/JWyZffuthSfv3Nyo8dLiJ5YzUo32pztb79Qkh6
26
+LABK2knyC/45IyuKNFkaBAYJ447lTn0YYkLiapRzMH4kW+uUAi/SuCA2mX6pqWeC
27
+iw3G6QG5AgMBAAECggEARaAnejoP2ykvE1G8e3Cv2M33x/eBQMI9m6uCmz9+qnqc
28
+14JkTIfmjffHVXie7RpNAKys16lJE+rZ/eVoh6EStVdiaDLsZYP45evjRcho0Tgd
29
+Hokq7FSiOMpd2V09kE1yrrHA/DjSLv38eTNAPIejc8IgaR7VyD6Is0iNiVnL7iLa
30
+mj1zB6+dSeQ5ICYkrihb1gA+SvECsjLZ/5XESXEdHJvxhC0vLAdHmdQf3BPPlrGg
31
+VHondxL5gt6MFykpOxTFA6f5JkSefhUR/2OcCDpMs6a5GUytjl3rA3aGT6v3CbnR
32
+ykD6PzyC20EUADQYF2pmJfzbxyRqfNdbSJwQv5QQYQKBgQD4rFdvgZC97L7WhZ5T
33
+axW8hRW2dH24GIqFT4ZnCg0suyMNshyGvDMuBfGvokN/yACmvsdE0/f57esar+ye
34
+l9RC+CzGUch08Ke5WdqwACOCNDpx0kJcXKTuLIgkvthdla/oAQQ9T7OgEwDrvaR0
35
+m8s/Z7Hb3hLD3xdOt6Xjrv/6xQKBgQDHzvbcIkhmWdvaPDT9NEu7psR/fxF5UjqU
36
+Cca/bfHhySRQs3A1CF57pfwpUqAcSivNf7O+3NI62AKoyMDYv0ek2h6hGk6g5GJ1
37
+SuXYfjcbkL6SWNV0InsgmzCjvxhyms83xZq7uMClEBvkiKVMdt6zFkwW9eRKtUuZ
38
+pzVK5RfqZQKBgF5SME/xGw+O7su7ntQROAtrh1LPWKgtVs093sLSgzDGQoN9XWiV
39
+lewNASEXMPcUy3pzvm2S4OoBnj1fISb+e9py+7i1aI1CgrvBIzvCsbU/TjPCBr21
40
+vjFA3trhMHw+vJwJVqxSwNUkoCLKqcg5F5yTHllBIGj/A34uFlQIGrvpAoGAextm
41
+d+1bhExbLBQqZdOh0cWHjjKBVqm2U93OKcYY4Q9oI5zbRqGYbUCwo9k3sxZz9JJ4
42
+8eDmWsEaqlm+kA0SnFyTwJkP1wvAKhpykTf6xi4hbNP0+DACgu17Q3iLHJmLkQZc
43
+Nss3TrwlI2KZzgnzXo4fZYotFWasZMhkCngqiw0CgYEAmz2D70RYEauUNE1+zLhS
44
+6Ox5+PF/8Z0rZOlTghMTfqYcDJa+qQe9pJp7RPgilsgemqo0XtgLKz3ATE5FmMa4
45
+HRRGXPkMNu6Hzz4Yk4eM/yJqckoEc8azV25myqQ+7QXTwZEvxVbtUWZtxfImGwq+
46
+s/uzBKNwWf9UPTeIt+4JScg=
47
+-----END PRIVATE KEY-----`
48
+)
49
+
50
+func TestJWTBearerAuth(t *testing.T) {
51
+	j := JWTAuthConfig{
52
+		Enabled: true,
53
+		Tokens: []JWTAuthTokenConfig{
54
+			{
55
+				Algorithm:     "rsa",
56
+				KeyString:     rsaTestPubKey,
57
+				AccountClaims: []string{"preferred_username", "email"},
58
+				StripDomain:   "example.com",
59
+			},
60
+		},
61
+	}
62
+
63
+	if err := j.Postprocess(); err != nil {
64
+		t.Fatal(err)
65
+	}
66
+
67
+	// fixed test vector signed with the RSA privkey:
68
+	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzbGluZ2FtbiJ9.caPZw2Dl4KZN-SErD5-WZB_lPPveHXaMCoUHxNebb94G9w3VaWDIRdngVU99JKx5nE_yRtpewkHHvXsQnNA_M63GBXGK7afXB8e-kV33QF3v9pXALMP5SzRwMgokyxas0RgHu4e4L0d7dn9o_nkdXp34GX3Pn1MVkUGBH6GdlbOdDHrs04pPQ0Qj-O2U0AIpnZq-X_GQs9ECJo4TlPKWR7Jlq5l9bS0dBnohea4FuqJr232je-dlRVkbCa7nrnFmsIsezsgA3Jb_j9Zu_iv460t_d2eaytbVp9P-DOVfzUfkBsKs-81URQEnTjW6ut445AJz2pxjX92X0GdmORpAkQ"
69
+	accountName, err := j.Validate(token)
70
+	if err != nil {
71
+		t.Errorf("could not validate valid token: %v", err)
72
+	}
73
+	if accountName != "slingamn" {
74
+		t.Errorf("incorrect account name for token: `%s`", accountName)
75
+	}
76
+
77
+	// programmatically sign a new token, validate it
78
+	privKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(rsaTestPrivKey))
79
+	if err != nil {
80
+		t.Fatal(err)
81
+	}
82
+	jTok := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(map[string]any{"preferred_username": "slingamn"}))
83
+	token, err = jTok.SignedString(privKey)
84
+	if err != nil {
85
+		t.Fatal(err)
86
+	}
87
+	accountName, err = j.Validate(token)
88
+	if err != nil {
89
+		t.Errorf("could not validate valid token: %v", err)
90
+	}
91
+	if accountName != "slingamn" {
92
+		t.Errorf("incorrect account name for token: `%s`", accountName)
93
+	}
94
+
95
+	// test expiration
96
+	jTok = jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(map[string]any{"preferred_username": "slingamn", "exp": 1675740865}))
97
+	token, err = jTok.SignedString(privKey)
98
+	if err != nil {
99
+		t.Fatal(err)
100
+	}
101
+	accountName, err = j.Validate(token)
102
+	if err == nil {
103
+		t.Errorf("validated expired token")
104
+	}
105
+
106
+	// test for the infamous algorithm confusion bug
107
+	jTok = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(map[string]any{"preferred_username": "slingamn"}))
108
+	token, err = jTok.SignedString([]byte(rsaTestPubKey))
109
+	if err != nil {
110
+		t.Fatal(err)
111
+	}
112
+	accountName, err = j.Validate(token)
113
+	if err == nil {
114
+		t.Errorf("validated HS256 token despite RSA being required")
115
+	}
116
+
117
+	// test no valid claims
118
+	jTok = jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(map[string]any{"sub": "slingamn"}))
119
+	token, err = jTok.SignedString(privKey)
120
+	if err != nil {
121
+		t.Fatal(err)
122
+	}
123
+
124
+	accountName, err = j.Validate(token)
125
+	if err != ErrNoValidAccountClaim {
126
+		t.Errorf("expected ErrNoValidAccountClaim, got: %v", err)
127
+	}
128
+
129
+	// test email addresses
130
+	jTok = jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(map[string]any{"email": "Slingamn@example.com"}))
131
+	token, err = jTok.SignedString(privKey)
132
+	if err != nil {
133
+		t.Fatal(err)
134
+	}
135
+
136
+	accountName, err = j.Validate(token)
137
+	if err != nil {
138
+		t.Errorf("could not validate valid token: %v", err)
139
+	}
140
+	if accountName != "Slingamn" {
141
+		t.Errorf("incorrect account name for token: `%s`", accountName)
142
+	}
143
+}

+ 3
- 18
irc/jwt/extjwt.go View File

@@ -6,18 +6,15 @@ package jwt
6 6
 
7 7
 import (
8 8
 	"crypto/rsa"
9
-	"crypto/x509"
10
-	"encoding/pem"
11 9
 	"errors"
12
-	"fmt"
13 10
 	"os"
14 11
 	"time"
15 12
 
16
-	"github.com/golang-jwt/jwt"
13
+	jwt "github.com/golang-jwt/jwt/v5"
17 14
 )
18 15
 
19 16
 var (
20
-	ErrNoKeys = errors.New("No signing keys are enabled")
17
+	ErrNoKeys = errors.New("No EXTJWT signing keys are enabled")
21 18
 )
22 19
 
23 20
 type MapClaims jwt.MapClaims
@@ -38,22 +35,10 @@ func (t *JwtServiceConfig) Postprocess() (err error) {
38 35
 		if err != nil {
39 36
 			return err
40 37
 		}
41
-		d, _ := pem.Decode(keyBytes)
38
+		t.rsaPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
42 39
 		if err != nil {
43 40
 			return err
44 41
 		}
45
-		t.rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(d.Bytes)
46
-		if err != nil {
47
-			privateKey, err := x509.ParsePKCS8PrivateKey(d.Bytes)
48
-			if err != nil {
49
-				return err
50
-			}
51
-			if rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey); ok {
52
-				t.rsaPrivateKey = rsaPrivateKey
53
-			} else {
54
-				return fmt.Errorf("Non-RSA key type for extjwt: %T", privateKey)
55
-			}
56
-		}
57 42
 	}
58 43
 	return nil
59 44
 }

+ 108
- 0
irc/oauth2/oauth2.go View File

@@ -0,0 +1,108 @@
1
+// Copyright 2022-2023 Simon Ser <contact@emersion.fr>
2
+// Derived from https://git.sr.ht/~emersion/soju/tree/36d6cb19a4f90d217d55afb0b15318321baaad09/item/auth/oauth2.go
3
+// Originally released under the AGPLv3, relicensed to the Ergo project under the MIT license
4
+// Modifications copyright 2024 Shivaram Lingamneni <slingamn@cs.stanford.edu>
5
+// Released under the MIT license
6
+
7
+package oauth2
8
+
9
+import (
10
+	"context"
11
+	"encoding/json"
12
+	"fmt"
13
+	"net/http"
14
+	"net/url"
15
+	"strings"
16
+	"time"
17
+)
18
+
19
+var (
20
+	ErrAuthDisabled = fmt.Errorf("OAuth 2.0 authentication is disabled")
21
+
22
+	// all cases where the infrastructure is working correctly, but we determined
23
+	// that the user supplied an invalid token
24
+	ErrInvalidToken = fmt.Errorf("OAuth 2.0 bearer token invalid")
25
+)
26
+
27
+type OAuth2BearerConfig struct {
28
+	Enabled              bool          `yaml:"enabled"`
29
+	Autocreate           bool          `yaml:"autocreate"`
30
+	AuthScript           bool          `yaml:"auth-script"`
31
+	IntrospectionURL     string        `yaml:"introspection-url"`
32
+	IntrospectionTimeout time.Duration `yaml:"introspection-timeout"`
33
+	// omit for `none`, required for `client_secret_basic`
34
+	ClientID     string `yaml:"client-id"`
35
+	ClientSecret string `yaml:"client-secret"`
36
+}
37
+
38
+func (o *OAuth2BearerConfig) Postprocess() error {
39
+	if !o.Enabled {
40
+		return nil
41
+	}
42
+
43
+	if o.IntrospectionTimeout == 0 {
44
+		return fmt.Errorf("a nonzero oauthbearer introspection timeout is required (try 10s)")
45
+	}
46
+
47
+	if _, err := url.Parse(o.IntrospectionURL); err != nil {
48
+		return fmt.Errorf("invalid introspection-url: %w", err)
49
+	}
50
+
51
+	return nil
52
+}
53
+
54
+func (o *OAuth2BearerConfig) Introspect(ctx context.Context, token string) (username string, err error) {
55
+	if !o.Enabled {
56
+		return "", ErrAuthDisabled
57
+	}
58
+
59
+	ctx, cancel := context.WithTimeout(ctx, o.IntrospectionTimeout)
60
+	defer cancel()
61
+
62
+	reqValues := make(url.Values)
63
+	reqValues.Set("token", token)
64
+
65
+	reqBody := strings.NewReader(reqValues.Encode())
66
+
67
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, o.IntrospectionURL, reqBody)
68
+	if err != nil {
69
+		return "", fmt.Errorf("failed to create OAuth 2.0 introspection request: %w", err)
70
+	}
71
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
72
+	req.Header.Set("Accept", "application/json")
73
+
74
+	if o.ClientID != "" {
75
+		req.SetBasicAuth(url.QueryEscape(o.ClientID), url.QueryEscape(o.ClientSecret))
76
+	}
77
+
78
+	resp, err := http.DefaultClient.Do(req)
79
+	if err != nil {
80
+		return "", fmt.Errorf("failed to send OAuth 2.0 introspection request: %v", err)
81
+	}
82
+	defer resp.Body.Close()
83
+
84
+	if resp.StatusCode != http.StatusOK {
85
+		return "", fmt.Errorf("OAuth 2.0 introspection error: %v", resp.Status)
86
+	}
87
+
88
+	var data oauth2Introspection
89
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
90
+		return "", fmt.Errorf("failed to decode OAuth 2.0 introspection response: %v", err)
91
+	}
92
+
93
+	if !data.Active {
94
+		return "", ErrInvalidToken
95
+	}
96
+	if data.Username == "" {
97
+		// We really need the username here, otherwise an OAuth 2.0 user can
98
+		// impersonate any other user.
99
+		return "", fmt.Errorf("missing username in OAuth 2.0 introspection response")
100
+	}
101
+
102
+	return data.Username, nil
103
+}
104
+
105
+type oauth2Introspection struct {
106
+	Active   bool   `json:"active"`
107
+	Username string `json:"username"`
108
+}

+ 172
- 0
irc/oauth2/sasl.go View File

@@ -0,0 +1,172 @@
1
+package oauth2
2
+
3
+/*
4
+https://github.com/emersion/go-sasl/blob/e73c9f7bad438a9bf3f5b28e661b74d752ecafdd/oauthbearer.go
5
+
6
+Copyright 2019-2022 Simon Ser, Frode Aannevik, Max Mazurov
7
+Released under the MIT license
8
+*/
9
+
10
+import (
11
+	"bytes"
12
+	"encoding/json"
13
+	"errors"
14
+	"fmt"
15
+	"strconv"
16
+	"strings"
17
+)
18
+
19
+var (
20
+	ErrUnexpectedClientResponse = errors.New("unexpected client response")
21
+)
22
+
23
+// The OAUTHBEARER mechanism name.
24
+const OAuthBearer = "OAUTHBEARER"
25
+
26
+type OAuthBearerError struct {
27
+	Status  string `json:"status"`
28
+	Schemes string `json:"schemes"`
29
+	Scope   string `json:"scope"`
30
+}
31
+
32
+type OAuthBearerOptions struct {
33
+	Username string `json:"username,omitempty"`
34
+	Token    string `json:"token,omitempty"`
35
+	Host     string `json:"host,omitempty"`
36
+	Port     int    `json:"port,omitempty"`
37
+}
38
+
39
+func (err *OAuthBearerError) Error() string {
40
+	return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
41
+}
42
+
43
+type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
44
+
45
+type OAuthBearerServer struct {
46
+	done         bool
47
+	failErr      error
48
+	authenticate OAuthBearerAuthenticator
49
+}
50
+
51
+func (a *OAuthBearerServer) fail(descr string) ([]byte, bool, error) {
52
+	blob, err := json.Marshal(OAuthBearerError{
53
+		Status:  "invalid_request",
54
+		Schemes: "bearer",
55
+	})
56
+	if err != nil {
57
+		panic(err) // wtf
58
+	}
59
+	a.failErr = errors.New(descr)
60
+	return blob, false, nil
61
+}
62
+
63
+func (a *OAuthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
64
+	// Per RFC, we cannot just send an error, we need to return JSON-structured
65
+	// value as a challenge and then after getting dummy response from the
66
+	// client stop the exchange.
67
+	if a.failErr != nil {
68
+		// Server libraries (go-smtp, go-imap) will not call Next on
69
+		// protocol-specific SASL cancel response ('*'). However, GS2 (and
70
+		// indirectly OAUTHBEARER) defines a protocol-independent way to do so
71
+		// using 0x01.
72
+		if len(response) != 1 && response[0] != 0x01 {
73
+			return nil, true, errors.New("unexpected response")
74
+		}
75
+		return nil, true, a.failErr
76
+	}
77
+
78
+	if a.done {
79
+		err = ErrUnexpectedClientResponse
80
+		return
81
+	}
82
+
83
+	// Generate empty challenge.
84
+	if response == nil {
85
+		return []byte{}, false, nil
86
+	}
87
+
88
+	a.done = true
89
+
90
+	// Cut n,a=username,\x01host=...\x01auth=...
91
+	// into
92
+	//   n
93
+	//   a=username
94
+	//   \x01host=...\x01auth=...\x01\x01
95
+	parts := bytes.SplitN(response, []byte{','}, 3)
96
+	if len(parts) != 3 {
97
+		return a.fail("Invalid response")
98
+	}
99
+	flag := parts[0]
100
+	authzid := parts[1]
101
+	if !bytes.Equal(flag, []byte{'n'}) {
102
+		return a.fail("Invalid response, missing 'n' in gs2-cb-flag")
103
+	}
104
+	opts := OAuthBearerOptions{}
105
+	if len(authzid) > 0 {
106
+		if !bytes.HasPrefix(authzid, []byte("a=")) {
107
+			return a.fail("Invalid response, missing 'a=' in gs2-authzid")
108
+		}
109
+		opts.Username = string(bytes.TrimPrefix(authzid, []byte("a=")))
110
+	}
111
+
112
+	// Cut \x01host=...\x01auth=...\x01\x01
113
+	// into
114
+	//   *empty*
115
+	//   host=...
116
+	//   auth=...
117
+	//   *empty*
118
+	//
119
+	// Note that this code does not do a lot of checks to make sure the input
120
+	// follows the exact format specified by RFC.
121
+	params := bytes.Split(parts[2], []byte{0x01})
122
+	for _, p := range params {
123
+		// Skip empty fields (one at start and end).
124
+		if len(p) == 0 {
125
+			continue
126
+		}
127
+
128
+		pParts := bytes.SplitN(p, []byte{'='}, 2)
129
+		if len(pParts) != 2 {
130
+			return a.fail("Invalid response, missing '='")
131
+		}
132
+
133
+		switch string(pParts[0]) {
134
+		case "host":
135
+			opts.Host = string(pParts[1])
136
+		case "port":
137
+			port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
138
+			if err != nil {
139
+				return a.fail("Invalid response, malformed 'port' value")
140
+			}
141
+			opts.Port = int(port)
142
+		case "auth":
143
+			const prefix = "bearer "
144
+			strValue := string(pParts[1])
145
+			// Token type is case-insensitive.
146
+			if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
147
+				return a.fail("Unsupported token type")
148
+			}
149
+			opts.Token = strValue[len(prefix):]
150
+		default:
151
+			return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
152
+		}
153
+	}
154
+
155
+	authzErr := a.authenticate(opts)
156
+	if authzErr != nil {
157
+		blob, err := json.Marshal(authzErr)
158
+		if err != nil {
159
+			panic(err) // wtf
160
+		}
161
+		a.failErr = authzErr
162
+		return blob, false, nil
163
+	}
164
+
165
+	return nil, true, nil
166
+}
167
+
168
+func NewOAuthBearerServer(auth OAuthBearerAuthenticator) *OAuthBearerServer {
169
+	return &OAuthBearerServer{
170
+		authenticate: auth,
171
+	}
172
+}

+ 34
- 0
traditional.yaml View File

@@ -559,6 +559,40 @@ accounts:
559 559
         # how many scripts are allowed to run at once? 0 for no limit:
560 560
         max-concurrency: 64
561 561
 
562
+    # support for login via OAuth2 bearer tokens
563
+    oauth2:
564
+        enabled: false
565
+        # should we automatically create users on presentation of a valid token?
566
+        autocreate: true
567
+        # enable this to use auth-script for validation:
568
+        auth-script: false
569
+        introspection-url: "https://example.com/api/oidc/introspection"
570
+        introspection-timeout: 10s
571
+        # omit for auth method `none`; required for auth method `client_secret_basic`:
572
+        client-id: "ergo"
573
+        client-secret: "4TA0I7mJ3fUUcW05KJiODg"
574
+
575
+    # support for login via JWT bearer tokens
576
+    jwt-auth:
577
+        enabled: false
578
+        # should we automatically create users on presentation of a valid token?
579
+        autocreate: true
580
+        # any of these token definitions can be accepted, allowing for key rotation
581
+        tokens:
582
+            -
583
+                algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
584
+                # hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
585
+                # either way, the key can be specified either as a YAML string:
586
+                key: "nANiZ1De4v6WnltCHN2H7Q"
587
+                # or as a path to the file containing the key:
588
+                #key-file: "jwt_pubkey.pem"
589
+                # list of JWT claim names to search for the user's account name (make sure the format
590
+                # is what you expect, especially if using "sub"):
591
+                account-claims: ["preferred_username"]
592
+                # if a claim is formatted as an email address, require it to have the following domain,
593
+                # and then strip off the domain and use the local-part as the account name:
594
+                #strip-domain: "example.com"
595
+
562 596
 # channel options
563 597
 channels:
564 598
     # modes that are set when new channels are created

+ 105
- 0
vendor/github.com/ergochat/irc-go/ircutils/sasl.go View File

@@ -0,0 +1,105 @@
1
+package ircutils
2
+
3
+import (
4
+	"encoding/base64"
5
+	"errors"
6
+	"strings"
7
+)
8
+
9
+var (
10
+	ErrSASLLimitExceeded = errors.New("SASL total response size exceeded configured limit")
11
+	ErrSASLTooLong       = errors.New("SASL response chunk exceeded 400-byte limit")
12
+)
13
+
14
+// EncodeSASLResponse encodes a raw SASL response as parameters to successive
15
+// AUTHENTICATE commands, as described in the IRCv3 SASL specification.
16
+func EncodeSASLResponse(raw []byte) (result []string) {
17
+	// https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command
18
+	// "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
19
+	// and each chunk is sent as a separate AUTHENTICATE command. Empty (zero-length)
20
+	// responses are sent as AUTHENTICATE +. If the last chunk was exactly 400 bytes
21
+	// long, it must also be followed by AUTHENTICATE + to signal end of response."
22
+
23
+	if len(raw) == 0 {
24
+		return []string{"+"}
25
+	}
26
+
27
+	response := base64.StdEncoding.EncodeToString(raw)
28
+	lastLen := 0
29
+	for len(response) > 0 {
30
+		// TODO once we require go 1.21, this can be: lastLen = min(len(response), 400)
31
+		lastLen = len(response)
32
+		if lastLen > 400 {
33
+			lastLen = 400
34
+		}
35
+		result = append(result, response[:lastLen])
36
+		response = response[lastLen:]
37
+	}
38
+
39
+	if lastLen == 400 {
40
+		result = append(result, "+")
41
+	}
42
+
43
+	return result
44
+}
45
+
46
+// SASLBuffer handles buffering and decoding SASL responses sent as parameters
47
+// to AUTHENTICATE commands, as described in the IRCv3 SASL specification.
48
+// Do not copy a SASLBuffer after first use.
49
+type SASLBuffer struct {
50
+	maxLength int
51
+	buffer    strings.Builder
52
+}
53
+
54
+// NewSASLBuffer returns a new SASLBuffer. maxLength is the maximum amount of
55
+// base64'ed data to buffer (0 for no limit).
56
+func NewSASLBuffer(maxLength int) *SASLBuffer {
57
+	result := new(SASLBuffer)
58
+	result.Initialize(maxLength)
59
+	return result
60
+}
61
+
62
+// Initialize initializes a SASLBuffer in place.
63
+func (b *SASLBuffer) Initialize(maxLength int) {
64
+	b.maxLength = maxLength
65
+}
66
+
67
+// Add processes an additional SASL response chunk sent via AUTHENTICATE.
68
+// If the response is complete, it resets the buffer and returns the decoded
69
+// response along with any decoding or protocol errors detected.
70
+func (b *SASLBuffer) Add(value string) (done bool, output []byte, err error) {
71
+	if value == "+" {
72
+		output, err = b.getAndReset()
73
+		return true, output, err
74
+	}
75
+
76
+	if len(value) > 400 {
77
+		b.buffer.Reset()
78
+		return true, nil, ErrSASLTooLong
79
+	}
80
+
81
+	if b.maxLength != 0 && (b.buffer.Len()+len(value)) > b.maxLength {
82
+		b.buffer.Reset()
83
+		return true, nil, ErrSASLLimitExceeded
84
+	}
85
+
86
+	b.buffer.WriteString(value)
87
+	if len(value) < 400 {
88
+		output, err = b.getAndReset()
89
+		return true, output, err
90
+	} else {
91
+		// 400 bytes, wait for continuation line or +
92
+		return false, nil, nil
93
+	}
94
+}
95
+
96
+// Clear resets the buffer state.
97
+func (b *SASLBuffer) Clear() {
98
+	b.buffer.Reset()
99
+}
100
+
101
+func (b *SASLBuffer) getAndReset() (output []byte, err error) {
102
+	output, err = base64.StdEncoding.DecodeString(b.buffer.String())
103
+	b.buffer.Reset()
104
+	return
105
+}

+ 0
- 22
vendor/github.com/golang-jwt/jwt/MIGRATION_GUIDE.md View File

@@ -1,22 +0,0 @@
1
-## Migration Guide (v3.2.1)
2
-
3
-Starting from [v3.2.1](https://github.com/golang-jwt/jwt/releases/tag/v3.2.1]), the import path has changed from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt`. Future releases will be using the `github.com/golang-jwt/jwt` import path and continue the existing versioning scheme of `v3.x.x+incompatible`. Backwards-compatible patches and fixes will be done on the `v3` release branch, where as new build-breaking features will be developed in a `v4` release, possibly including a SIV-style import path.
4
-
5
-### go.mod replacement
6
-
7
-In a first step, the easiest way is to use `go mod edit` to issue a replacement.
8
-
9
-```
10
-go mod edit -replace github.com/dgrijalva/jwt-go=github.com/golang-jwt/jwt@v3.2.1+incompatible
11
-go mod tidy
12
-```
13
-
14
-This will still keep the old import path in your code but replace it with the new package and also introduce a new indirect dependency to `github.com/golang-jwt/jwt`. Try to compile your project; it should still work.
15
-
16
-### Cleanup
17
-
18
-If your code still consistently builds, you can replace all occurences of `github.com/dgrijalva/jwt-go` with `github.com/golang-jwt/jwt`, either manually or by using tools such as `sed`. Finally, the `replace` directive in the `go.mod` file can be removed.
19
-
20
-## Older releases (before v3.2.0)
21
-
22
-The original migration guide for older releases can be found at https://github.com/dgrijalva/jwt-go/blob/master/MIGRATION_GUIDE.md.

+ 0
- 113
vendor/github.com/golang-jwt/jwt/README.md View File

@@ -1,113 +0,0 @@
1
-# jwt-go
2
-
3
-[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml)
4
-[![Go Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt)
5
-
6
-A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519).
7
-
8
-**IMPORT PATH CHANGE:** Starting from [v3.2.1](https://github.com/golang-jwt/jwt/releases/tag/v3.2.1), the import path has changed from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt`. After the original author of the library suggested migrating the maintenance of `jwt-go`, a dedicated team of open source maintainers decided to clone the existing library into this repository. See [dgrijalva/jwt-go#462](https://github.com/dgrijalva/jwt-go/issues/462) for a detailed discussion on this topic.
9
-
10
-Future releases will be using the `github.com/golang-jwt/jwt` import path and continue the existing versioning scheme of `v3.x.x+incompatible`. Backwards-compatible patches and fixes will be done on the `v3` release branch, where as new build-breaking features will be developed in a `v4` release, possibly including a SIV-style import path.
11
-
12
-**SECURITY NOTICE:** Some older versions of Go have a security issue in the crypto/elliptic. Recommendation is to upgrade to at least 1.15 See issue [dgrijalva/jwt-go#216](https://github.com/dgrijalva/jwt-go/issues/216) for more detail.
13
-
14
-**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage.  See the examples provided.
15
-
16
-### Supported Go versions
17
-
18
-Our support of Go versions is aligned with Go's [version release policy](https://golang.org/doc/devel/release#policy).
19
-So we will support a major version of Go until there are two newer major releases.
20
-We no longer support building jwt-go with unsupported Go versions, as these contain security vulnerabilities
21
-which will not be fixed.
22
-
23
-## What the heck is a JWT?
24
-
25
-JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens.
26
-
27
-In short, it's a signed JSON object that does something useful (for example, authentication).  It's commonly used for `Bearer` tokens in Oauth 2.  A token is made of three parts, separated by `.`'s.  The first two parts are JSON objects, that have been [base64url](https://datatracker.ietf.org/doc/html/rfc4648) encoded.  The last part is the signature, encoded the same way.
28
-
29
-The first part is called the header.  It contains the necessary information for verifying the last part, the signature.  For example, which encryption method was used for signing and what key was used.
30
-
31
-The part in the middle is the interesting bit.  It's called the Claims and contains the actual stuff you care about.  Refer to [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) for information about reserved keys and the proper way to add your own.
32
-
33
-## What's in the box?
34
-
35
-This library supports the parsing and verification as well as the generation and signing of JWTs.  Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
36
-
37
-## Examples
38
-
39
-See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage:
40
-
41
-* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac)
42
-* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac)
43
-* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples)
44
-
45
-## Extensions
46
-
47
-This library publishes all the necessary components for adding your own signing methods.  Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.  
48
-
49
-Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go
50
-
51
-## Compliance
52
-
53
-This library was last reviewed to comply with [RTF 7519](https://datatracker.ietf.org/doc/html/rfc7519) dated May 2015 with a few notable differences:
54
-
55
-* In order to protect against accidental use of [Unsecured JWTs](https://datatracker.ietf.org/doc/html/rfc7519#section-6), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
56
-
57
-## Project Status & Versioning
58
-
59
-This library is considered production ready.  Feedback and feature requests are appreciated.  The API should be considered stable.  There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
60
-
61
-This project uses [Semantic Versioning 2.0.0](http://semver.org).  Accepted pull requests will land on `main`.  Periodically, versions will be tagged from `main`.  You can find all the releases on [the project releases page](https://github.com/golang-jwt/jwt/releases).
62
-
63
-While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users.  You may want to use this alternative package include: `gopkg.in/golang-jwt/jwt.v3`.  It will do the right thing WRT semantic versioning.
64
-
65
-**BREAKING CHANGES:*** 
66
-* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API.  We've tried to break as few things as possible, so there should just be a few type signature changes.  A full list of breaking changes is available in `VERSION_HISTORY.md`.  See `MIGRATION_GUIDE.md` for more information on updating your code.
67
-
68
-## Usage Tips
69
-
70
-### Signing vs Encryption
71
-
72
-A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
73
-
74
-* The author of the token was in the possession of the signing secret
75
-* The data has not been modified since it was signed
76
-
77
-It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
78
-
79
-### Choosing a Signing Method
80
-
81
-There are several signing methods available, and you should probably take the time to learn about the various options before choosing one.  The principal design decision is most likely going to be symmetric vs asymmetric.
82
-
83
-Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
84
-
85
-Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
86
-
87
-### Signing Methods and Key Types
88
-
89
-Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
90
-
91
-* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
92
-* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
93
-* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
94
-
95
-### JWT and OAuth
96
-
97
-It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
98
-
99
-Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
100
-
101
-* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
102
-* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
103
-* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
104
-
105
-### Troubleshooting
106
-
107
-This library uses descriptive error messages whenever possible. If you are not getting the expected result, have a look at the errors. The most common place people get stuck is providing the correct type of key to the parser. See the above section on signing methods and key types.
108
-
109
-## More
110
-
111
-Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt).
112
-
113
-The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.

+ 0
- 146
vendor/github.com/golang-jwt/jwt/claims.go View File

@@ -1,146 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"crypto/subtle"
5
-	"fmt"
6
-	"time"
7
-)
8
-
9
-// For a type to be a Claims object, it must just have a Valid method that determines
10
-// if the token is invalid for any supported reason
11
-type Claims interface {
12
-	Valid() error
13
-}
14
-
15
-// Structured version of Claims Section, as referenced at
16
-// https://tools.ietf.org/html/rfc7519#section-4.1
17
-// See examples for how to use this with your own claim types
18
-type StandardClaims struct {
19
-	Audience  string `json:"aud,omitempty"`
20
-	ExpiresAt int64  `json:"exp,omitempty"`
21
-	Id        string `json:"jti,omitempty"`
22
-	IssuedAt  int64  `json:"iat,omitempty"`
23
-	Issuer    string `json:"iss,omitempty"`
24
-	NotBefore int64  `json:"nbf,omitempty"`
25
-	Subject   string `json:"sub,omitempty"`
26
-}
27
-
28
-// Validates time based claims "exp, iat, nbf".
29
-// There is no accounting for clock skew.
30
-// As well, if any of the above claims are not in the token, it will still
31
-// be considered a valid claim.
32
-func (c StandardClaims) Valid() error {
33
-	vErr := new(ValidationError)
34
-	now := TimeFunc().Unix()
35
-
36
-	// The claims below are optional, by default, so if they are set to the
37
-	// default value in Go, let's not fail the verification for them.
38
-	if !c.VerifyExpiresAt(now, false) {
39
-		delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
40
-		vErr.Inner = fmt.Errorf("token is expired by %v", delta)
41
-		vErr.Errors |= ValidationErrorExpired
42
-	}
43
-
44
-	if !c.VerifyIssuedAt(now, false) {
45
-		vErr.Inner = fmt.Errorf("Token used before issued")
46
-		vErr.Errors |= ValidationErrorIssuedAt
47
-	}
48
-
49
-	if !c.VerifyNotBefore(now, false) {
50
-		vErr.Inner = fmt.Errorf("token is not valid yet")
51
-		vErr.Errors |= ValidationErrorNotValidYet
52
-	}
53
-
54
-	if vErr.valid() {
55
-		return nil
56
-	}
57
-
58
-	return vErr
59
-}
60
-
61
-// Compares the aud claim against cmp.
62
-// If required is false, this method will return true if the value matches or is unset
63
-func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
64
-	return verifyAud([]string{c.Audience}, cmp, req)
65
-}
66
-
67
-// Compares the exp claim against cmp.
68
-// If required is false, this method will return true if the value matches or is unset
69
-func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
70
-	return verifyExp(c.ExpiresAt, cmp, req)
71
-}
72
-
73
-// Compares the iat claim against cmp.
74
-// If required is false, this method will return true if the value matches or is unset
75
-func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
76
-	return verifyIat(c.IssuedAt, cmp, req)
77
-}
78
-
79
-// Compares the iss claim against cmp.
80
-// If required is false, this method will return true if the value matches or is unset
81
-func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
82
-	return verifyIss(c.Issuer, cmp, req)
83
-}
84
-
85
-// Compares the nbf claim against cmp.
86
-// If required is false, this method will return true if the value matches or is unset
87
-func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
88
-	return verifyNbf(c.NotBefore, cmp, req)
89
-}
90
-
91
-// ----- helpers
92
-
93
-func verifyAud(aud []string, cmp string, required bool) bool {
94
-	if len(aud) == 0 {
95
-		return !required
96
-	}
97
-	// use a var here to keep constant time compare when looping over a number of claims
98
-	result := false
99
-
100
-	var stringClaims string
101
-	for _, a := range aud {
102
-		if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
103
-			result = true
104
-		}
105
-		stringClaims = stringClaims + a
106
-	}
107
-
108
-	// case where "" is sent in one or many aud claims
109
-	if len(stringClaims) == 0 {
110
-		return !required
111
-	}
112
-
113
-	return result
114
-}
115
-
116
-func verifyExp(exp int64, now int64, required bool) bool {
117
-	if exp == 0 {
118
-		return !required
119
-	}
120
-	return now <= exp
121
-}
122
-
123
-func verifyIat(iat int64, now int64, required bool) bool {
124
-	if iat == 0 {
125
-		return !required
126
-	}
127
-	return now >= iat
128
-}
129
-
130
-func verifyIss(iss string, cmp string, required bool) bool {
131
-	if iss == "" {
132
-		return !required
133
-	}
134
-	if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
135
-		return true
136
-	} else {
137
-		return false
138
-	}
139
-}
140
-
141
-func verifyNbf(nbf int64, now int64, required bool) bool {
142
-	if nbf == 0 {
143
-		return !required
144
-	}
145
-	return now >= nbf
146
-}

+ 0
- 59
vendor/github.com/golang-jwt/jwt/errors.go View File

@@ -1,59 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"errors"
5
-)
6
-
7
-// Error constants
8
-var (
9
-	ErrInvalidKey      = errors.New("key is invalid")
10
-	ErrInvalidKeyType  = errors.New("key is of invalid type")
11
-	ErrHashUnavailable = errors.New("the requested hash function is unavailable")
12
-)
13
-
14
-// The errors that might occur when parsing and validating a token
15
-const (
16
-	ValidationErrorMalformed        uint32 = 1 << iota // Token is malformed
17
-	ValidationErrorUnverifiable                        // Token could not be verified because of signing problems
18
-	ValidationErrorSignatureInvalid                    // Signature validation failed
19
-
20
-	// Standard Claim validation errors
21
-	ValidationErrorAudience      // AUD validation failed
22
-	ValidationErrorExpired       // EXP validation failed
23
-	ValidationErrorIssuedAt      // IAT validation failed
24
-	ValidationErrorIssuer        // ISS validation failed
25
-	ValidationErrorNotValidYet   // NBF validation failed
26
-	ValidationErrorId            // JTI validation failed
27
-	ValidationErrorClaimsInvalid // Generic claims validation error
28
-)
29
-
30
-// Helper for constructing a ValidationError with a string error message
31
-func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
32
-	return &ValidationError{
33
-		text:   errorText,
34
-		Errors: errorFlags,
35
-	}
36
-}
37
-
38
-// The error from Parse if token is not valid
39
-type ValidationError struct {
40
-	Inner  error  // stores the error returned by external dependencies, i.e.: KeyFunc
41
-	Errors uint32 // bitfield.  see ValidationError... constants
42
-	text   string // errors that do not have a valid error just have text
43
-}
44
-
45
-// Validation error is an error type
46
-func (e ValidationError) Error() string {
47
-	if e.Inner != nil {
48
-		return e.Inner.Error()
49
-	} else if e.text != "" {
50
-		return e.text
51
-	} else {
52
-		return "token is invalid"
53
-	}
54
-}
55
-
56
-// No errors
57
-func (e *ValidationError) valid() bool {
58
-	return e.Errors == 0
59
-}

+ 0
- 120
vendor/github.com/golang-jwt/jwt/map_claims.go View File

@@ -1,120 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"encoding/json"
5
-	"errors"
6
-	// "fmt"
7
-)
8
-
9
-// Claims type that uses the map[string]interface{} for JSON decoding
10
-// This is the default claims type if you don't supply one
11
-type MapClaims map[string]interface{}
12
-
13
-// VerifyAudience Compares the aud claim against cmp.
14
-// If required is false, this method will return true if the value matches or is unset
15
-func (m MapClaims) VerifyAudience(cmp string, req bool) bool {
16
-	var aud []string
17
-	switch v := m["aud"].(type) {
18
-	case string:
19
-		aud = append(aud, v)
20
-	case []string:
21
-		aud = v
22
-	case []interface{}:
23
-		for _, a := range v {
24
-			vs, ok := a.(string)
25
-			if !ok {
26
-				return false
27
-			}
28
-			aud = append(aud, vs)
29
-		}
30
-	}
31
-	return verifyAud(aud, cmp, req)
32
-}
33
-
34
-// Compares the exp claim against cmp.
35
-// If required is false, this method will return true if the value matches or is unset
36
-func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
37
-	exp, ok := m["exp"]
38
-	if !ok {
39
-		return !req
40
-	}
41
-	switch expType := exp.(type) {
42
-	case float64:
43
-		return verifyExp(int64(expType), cmp, req)
44
-	case json.Number:
45
-		v, _ := expType.Int64()
46
-		return verifyExp(v, cmp, req)
47
-	}
48
-	return false
49
-}
50
-
51
-// Compares the iat claim against cmp.
52
-// If required is false, this method will return true if the value matches or is unset
53
-func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
54
-	iat, ok := m["iat"]
55
-	if !ok {
56
-		return !req
57
-	}
58
-	switch iatType := iat.(type) {
59
-	case float64:
60
-		return verifyIat(int64(iatType), cmp, req)
61
-	case json.Number:
62
-		v, _ := iatType.Int64()
63
-		return verifyIat(v, cmp, req)
64
-	}
65
-	return false
66
-}
67
-
68
-// Compares the iss claim against cmp.
69
-// If required is false, this method will return true if the value matches or is unset
70
-func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
71
-	iss, _ := m["iss"].(string)
72
-	return verifyIss(iss, cmp, req)
73
-}
74
-
75
-// Compares the nbf claim against cmp.
76
-// If required is false, this method will return true if the value matches or is unset
77
-func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
78
-	nbf, ok := m["nbf"]
79
-	if !ok {
80
-		return !req
81
-	}
82
-	switch nbfType := nbf.(type) {
83
-	case float64:
84
-		return verifyNbf(int64(nbfType), cmp, req)
85
-	case json.Number:
86
-		v, _ := nbfType.Int64()
87
-		return verifyNbf(v, cmp, req)
88
-	}
89
-	return false
90
-}
91
-
92
-// Validates time based claims "exp, iat, nbf".
93
-// There is no accounting for clock skew.
94
-// As well, if any of the above claims are not in the token, it will still
95
-// be considered a valid claim.
96
-func (m MapClaims) Valid() error {
97
-	vErr := new(ValidationError)
98
-	now := TimeFunc().Unix()
99
-
100
-	if !m.VerifyExpiresAt(now, false) {
101
-		vErr.Inner = errors.New("Token is expired")
102
-		vErr.Errors |= ValidationErrorExpired
103
-	}
104
-
105
-	if !m.VerifyIssuedAt(now, false) {
106
-		vErr.Inner = errors.New("Token used before issued")
107
-		vErr.Errors |= ValidationErrorIssuedAt
108
-	}
109
-
110
-	if !m.VerifyNotBefore(now, false) {
111
-		vErr.Inner = errors.New("Token is not valid yet")
112
-		vErr.Errors |= ValidationErrorNotValidYet
113
-	}
114
-
115
-	if vErr.valid() {
116
-		return nil
117
-	}
118
-
119
-	return vErr
120
-}

+ 0
- 148
vendor/github.com/golang-jwt/jwt/parser.go View File

@@ -1,148 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"strings"
8
-)
9
-
10
-type Parser struct {
11
-	ValidMethods         []string // If populated, only these methods will be considered valid
12
-	UseJSONNumber        bool     // Use JSON Number format in JSON decoder
13
-	SkipClaimsValidation bool     // Skip claims validation during token parsing
14
-}
15
-
16
-// Parse, validate, and return a token.
17
-// keyFunc will receive the parsed token and should return the key for validating.
18
-// If everything is kosher, err will be nil
19
-func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
20
-	return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
21
-}
22
-
23
-func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
24
-	token, parts, err := p.ParseUnverified(tokenString, claims)
25
-	if err != nil {
26
-		return token, err
27
-	}
28
-
29
-	// Verify signing method is in the required set
30
-	if p.ValidMethods != nil {
31
-		var signingMethodValid = false
32
-		var alg = token.Method.Alg()
33
-		for _, m := range p.ValidMethods {
34
-			if m == alg {
35
-				signingMethodValid = true
36
-				break
37
-			}
38
-		}
39
-		if !signingMethodValid {
40
-			// signing method is not in the listed set
41
-			return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
42
-		}
43
-	}
44
-
45
-	// Lookup key
46
-	var key interface{}
47
-	if keyFunc == nil {
48
-		// keyFunc was not provided.  short circuiting validation
49
-		return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
50
-	}
51
-	if key, err = keyFunc(token); err != nil {
52
-		// keyFunc returned an error
53
-		if ve, ok := err.(*ValidationError); ok {
54
-			return token, ve
55
-		}
56
-		return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
57
-	}
58
-
59
-	vErr := &ValidationError{}
60
-
61
-	// Validate Claims
62
-	if !p.SkipClaimsValidation {
63
-		if err := token.Claims.Valid(); err != nil {
64
-
65
-			// If the Claims Valid returned an error, check if it is a validation error,
66
-			// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
67
-			if e, ok := err.(*ValidationError); !ok {
68
-				vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
69
-			} else {
70
-				vErr = e
71
-			}
72
-		}
73
-	}
74
-
75
-	// Perform validation
76
-	token.Signature = parts[2]
77
-	if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
78
-		vErr.Inner = err
79
-		vErr.Errors |= ValidationErrorSignatureInvalid
80
-	}
81
-
82
-	if vErr.valid() {
83
-		token.Valid = true
84
-		return token, nil
85
-	}
86
-
87
-	return token, vErr
88
-}
89
-
90
-// WARNING: Don't use this method unless you know what you're doing
91
-//
92
-// This method parses the token but doesn't validate the signature. It's only
93
-// ever useful in cases where you know the signature is valid (because it has
94
-// been checked previously in the stack) and you want to extract values from
95
-// it.
96
-func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
97
-	parts = strings.Split(tokenString, ".")
98
-	if len(parts) != 3 {
99
-		return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
100
-	}
101
-
102
-	token = &Token{Raw: tokenString}
103
-
104
-	// parse Header
105
-	var headerBytes []byte
106
-	if headerBytes, err = DecodeSegment(parts[0]); err != nil {
107
-		if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
108
-			return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
109
-		}
110
-		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
111
-	}
112
-	if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
113
-		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
114
-	}
115
-
116
-	// parse Claims
117
-	var claimBytes []byte
118
-	token.Claims = claims
119
-
120
-	if claimBytes, err = DecodeSegment(parts[1]); err != nil {
121
-		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
122
-	}
123
-	dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
124
-	if p.UseJSONNumber {
125
-		dec.UseNumber()
126
-	}
127
-	// JSON Decode.  Special case for map type to avoid weird pointer behavior
128
-	if c, ok := token.Claims.(MapClaims); ok {
129
-		err = dec.Decode(&c)
130
-	} else {
131
-		err = dec.Decode(&claims)
132
-	}
133
-	// Handle decode error
134
-	if err != nil {
135
-		return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
136
-	}
137
-
138
-	// Lookup signature method
139
-	if method, ok := token.Header["alg"].(string); ok {
140
-		if token.Method = GetSigningMethod(method); token.Method == nil {
141
-			return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
142
-		}
143
-	} else {
144
-		return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
145
-	}
146
-
147
-	return token, parts, nil
148
-}

+ 0
- 35
vendor/github.com/golang-jwt/jwt/signing_method.go View File

@@ -1,35 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"sync"
5
-)
6
-
7
-var signingMethods = map[string]func() SigningMethod{}
8
-var signingMethodLock = new(sync.RWMutex)
9
-
10
-// Implement SigningMethod to add new methods for signing or verifying tokens.
11
-type SigningMethod interface {
12
-	Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
13
-	Sign(signingString string, key interface{}) (string, error)    // Returns encoded signature or error
14
-	Alg() string                                                   // returns the alg identifier for this method (example: 'HS256')
15
-}
16
-
17
-// Register the "alg" name and a factory function for signing method.
18
-// This is typically done during init() in the method's implementation
19
-func RegisterSigningMethod(alg string, f func() SigningMethod) {
20
-	signingMethodLock.Lock()
21
-	defer signingMethodLock.Unlock()
22
-
23
-	signingMethods[alg] = f
24
-}
25
-
26
-// Get a signing method from an "alg" string
27
-func GetSigningMethod(alg string) (method SigningMethod) {
28
-	signingMethodLock.RLock()
29
-	defer signingMethodLock.RUnlock()
30
-
31
-	if methodF, ok := signingMethods[alg]; ok {
32
-		method = methodF()
33
-	}
34
-	return
35
-}

+ 0
- 104
vendor/github.com/golang-jwt/jwt/token.go View File

@@ -1,104 +0,0 @@
1
-package jwt
2
-
3
-import (
4
-	"encoding/base64"
5
-	"encoding/json"
6
-	"strings"
7
-	"time"
8
-)
9
-
10
-// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
11
-// You can override it to use another time value.  This is useful for testing or if your
12
-// server uses a different time zone than your tokens.
13
-var TimeFunc = time.Now
14
-
15
-// Parse methods use this callback function to supply
16
-// the key for verification.  The function receives the parsed,
17
-// but unverified Token.  This allows you to use properties in the
18
-// Header of the token (such as `kid`) to identify which key to use.
19
-type Keyfunc func(*Token) (interface{}, error)
20
-
21
-// A JWT Token.  Different fields will be used depending on whether you're
22
-// creating or parsing/verifying a token.
23
-type Token struct {
24
-	Raw       string                 // The raw token.  Populated when you Parse a token
25
-	Method    SigningMethod          // The signing method used or to be used
26
-	Header    map[string]interface{} // The first segment of the token
27
-	Claims    Claims                 // The second segment of the token
28
-	Signature string                 // The third segment of the token.  Populated when you Parse a token
29
-	Valid     bool                   // Is the token valid?  Populated when you Parse/Verify a token
30
-}
31
-
32
-// Create a new Token.  Takes a signing method
33
-func New(method SigningMethod) *Token {
34
-	return NewWithClaims(method, MapClaims{})
35
-}
36
-
37
-func NewWithClaims(method SigningMethod, claims Claims) *Token {
38
-	return &Token{
39
-		Header: map[string]interface{}{
40
-			"typ": "JWT",
41
-			"alg": method.Alg(),
42
-		},
43
-		Claims: claims,
44
-		Method: method,
45
-	}
46
-}
47
-
48
-// Get the complete, signed token
49
-func (t *Token) SignedString(key interface{}) (string, error) {
50
-	var sig, sstr string
51
-	var err error
52
-	if sstr, err = t.SigningString(); err != nil {
53
-		return "", err
54
-	}
55
-	if sig, err = t.Method.Sign(sstr, key); err != nil {
56
-		return "", err
57
-	}
58
-	return strings.Join([]string{sstr, sig}, "."), nil
59
-}
60
-
61
-// Generate the signing string.  This is the
62
-// most expensive part of the whole deal.  Unless you
63
-// need this for something special, just go straight for
64
-// the SignedString.
65
-func (t *Token) SigningString() (string, error) {
66
-	var err error
67
-	parts := make([]string, 2)
68
-	for i := range parts {
69
-		var jsonValue []byte
70
-		if i == 0 {
71
-			if jsonValue, err = json.Marshal(t.Header); err != nil {
72
-				return "", err
73
-			}
74
-		} else {
75
-			if jsonValue, err = json.Marshal(t.Claims); err != nil {
76
-				return "", err
77
-			}
78
-		}
79
-
80
-		parts[i] = EncodeSegment(jsonValue)
81
-	}
82
-	return strings.Join(parts, "."), nil
83
-}
84
-
85
-// Parse, validate, and return a token.
86
-// keyFunc will receive the parsed token and should return the key for validating.
87
-// If everything is kosher, err will be nil
88
-func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
89
-	return new(Parser).Parse(tokenString, keyFunc)
90
-}
91
-
92
-func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
93
-	return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
94
-}
95
-
96
-// Encode JWT specific base64url encoding with padding stripped
97
-func EncodeSegment(seg []byte) string {
98
-	return base64.RawURLEncoding.EncodeToString(seg)
99
-}
100
-
101
-// Decode JWT specific base64url encoding with padding stripped
102
-func DecodeSegment(seg string) ([]byte, error) {
103
-	return base64.RawURLEncoding.DecodeString(seg)
104
-}

vendor/github.com/golang-jwt/jwt/.gitignore → vendor/github.com/golang-jwt/jwt/v5/.gitignore View File


vendor/github.com/golang-jwt/jwt/LICENSE → vendor/github.com/golang-jwt/jwt/v5/LICENSE View File


+ 195
- 0
vendor/github.com/golang-jwt/jwt/v5/MIGRATION_GUIDE.md View File

@@ -0,0 +1,195 @@
1
+# Migration Guide (v5.0.0)
2
+
3
+Version `v5` contains a major rework of core functionalities in the `jwt-go`
4
+library. This includes support for several validation options as well as a
5
+re-design of the `Claims` interface. Lastly, we reworked how errors work under
6
+the hood, which should provide a better overall developer experience.
7
+
8
+Starting from [v5.0.0](https://github.com/golang-jwt/jwt/releases/tag/v5.0.0),
9
+the import path will be:
10
+
11
+    "github.com/golang-jwt/jwt/v5"
12
+
13
+For most users, changing the import path *should* suffice. However, since we
14
+intentionally changed and cleaned some of the public API, existing programs
15
+might need to be updated. The following sections describe significant changes
16
+and corresponding updates for existing programs.
17
+
18
+## Parsing and Validation Options
19
+
20
+Under the hood, a new `Validator` struct takes care of validating the claims. A
21
+long awaited feature has been the option to fine-tune the validation of tokens.
22
+This is now possible with several `ParserOption` functions that can be appended
23
+to most `Parse` functions, such as `ParseWithClaims`. The most important options
24
+and changes are:
25
+  * Added `WithLeeway` to support specifying the leeway that is allowed when
26
+    validating time-based claims, such as `exp` or `nbf`.
27
+  * Changed default behavior to not check the `iat` claim. Usage of this claim
28
+    is OPTIONAL according to the JWT RFC. The claim itself is also purely
29
+    informational according to the RFC, so a strict validation failure is not
30
+    recommended. If you want to check for sensible values in these claims,
31
+    please use the `WithIssuedAt` parser option.
32
+  * Added `WithAudience`, `WithSubject` and `WithIssuer` to support checking for
33
+    expected `aud`, `sub` and `iss`.
34
+  * Added `WithStrictDecoding` and `WithPaddingAllowed` options to allow
35
+    previously global settings to enable base64 strict encoding and the parsing
36
+    of base64 strings with padding. The latter is strictly speaking against the
37
+    standard, but unfortunately some of the major identity providers issue some
38
+    of these incorrect tokens. Both options are disabled by default.
39
+
40
+## Changes to the `Claims` interface
41
+
42
+### Complete Restructuring
43
+
44
+Previously, the claims interface was satisfied with an implementation of a
45
+`Valid() error` function. This had several issues:
46
+  * The different claim types (struct claims, map claims, etc.) then contained
47
+    similar (but not 100 % identical) code of how this validation was done. This
48
+    lead to a lot of (almost) duplicate code and was hard to maintain
49
+  * It was not really semantically close to what a "claim" (or a set of claims)
50
+    really is; which is a list of defined key/value pairs with a certain
51
+    semantic meaning.
52
+
53
+Since all the validation functionality is now extracted into the validator, all
54
+`VerifyXXX` and `Valid` functions have been removed from the `Claims` interface.
55
+Instead, the interface now represents a list of getters to retrieve values with
56
+a specific meaning. This allows us to completely decouple the validation logic
57
+with the underlying storage representation of the claim, which could be a
58
+struct, a map or even something stored in a database.
59
+
60
+```go
61
+type Claims interface {
62
+	GetExpirationTime() (*NumericDate, error)
63
+	GetIssuedAt() (*NumericDate, error)
64
+	GetNotBefore() (*NumericDate, error)
65
+	GetIssuer() (string, error)
66
+	GetSubject() (string, error)
67
+	GetAudience() (ClaimStrings, error)
68
+}
69
+```
70
+
71
+Users that previously directly called the `Valid` function on their claims,
72
+e.g., to perform validation independently of parsing/verifying a token, can now
73
+use the `jwt.NewValidator` function to create a `Validator` independently of the
74
+`Parser`.
75
+
76
+```go
77
+var v = jwt.NewValidator(jwt.WithLeeway(5*time.Second))
78
+v.Validate(myClaims)
79
+```
80
+
81
+### Supported Claim Types and Removal of `StandardClaims`
82
+
83
+The two standard claim types supported by this library, `MapClaims` and
84
+`RegisteredClaims` both implement the necessary functions of this interface. The
85
+old `StandardClaims` struct, which has already been deprecated in `v4` is now
86
+removed.
87
+
88
+Users using custom claims, in most cases, will not experience any changes in the
89
+behavior as long as they embedded `RegisteredClaims`. If they created a new
90
+claim type from scratch, they now need to implemented the proper getter
91
+functions.
92
+
93
+### Migrating Application Specific Logic of the old `Valid`
94
+
95
+Previously, users could override the `Valid` method in a custom claim, for
96
+example to extend the validation with application-specific claims. However, this
97
+was always very dangerous, since once could easily disable the standard
98
+validation and signature checking.
99
+
100
+In order to avoid that, while still supporting the use-case, a new
101
+`ClaimsValidator` interface has been introduced. This interface consists of the
102
+`Validate() error` function. If the validator sees, that a `Claims` struct
103
+implements this interface, the errors returned to the `Validate` function will
104
+be *appended* to the regular standard validation. It is not possible to disable
105
+the standard validation anymore (even only by accident).
106
+
107
+Usage examples can be found in [example_test.go](./example_test.go), to build
108
+claims structs like the following.
109
+
110
+```go
111
+// MyCustomClaims includes all registered claims, plus Foo.
112
+type MyCustomClaims struct {
113
+	Foo string `json:"foo"`
114
+	jwt.RegisteredClaims
115
+}
116
+
117
+// Validate can be used to execute additional application-specific claims
118
+// validation.
119
+func (m MyCustomClaims) Validate() error {
120
+	if m.Foo != "bar" {
121
+		return errors.New("must be foobar")
122
+	}
123
+
124
+	return nil
125
+}
126
+```
127
+
128
+## Changes to the `Token` and `Parser` struct
129
+
130
+The previously global functions `DecodeSegment` and `EncodeSegment` were moved
131
+to the `Parser` and `Token` struct respectively. This will allow us in the
132
+future to configure the behavior of these two based on options supplied on the
133
+parser or the token (creation). This also removes two previously global
134
+variables and moves them to parser options `WithStrictDecoding` and
135
+`WithPaddingAllowed`.
136
+
137
+In order to do that, we had to adjust the way signing methods work. Previously
138
+they were given a base64 encoded signature in `Verify` and were expected to
139
+return a base64 encoded version of the signature in `Sign`, both as a `string`.
140
+However, this made it necessary to have `DecodeSegment` and `EncodeSegment`
141
+global and was a less than perfect design because we were repeating
142
+encoding/decoding steps for all signing methods. Now, `Sign` and `Verify`
143
+operate on a decoded signature as a `[]byte`, which feels more natural for a
144
+cryptographic operation anyway. Lastly, `Parse` and `SignedString` take care of
145
+the final encoding/decoding part.
146
+
147
+In addition to that, we also changed the `Signature` field on `Token` from a
148
+`string` to `[]byte` and this is also now populated with the decoded form. This
149
+is also more consistent, because the other parts of the JWT, mainly `Header` and
150
+`Claims` were already stored in decoded form in `Token`. Only the signature was
151
+stored in base64 encoded form, which was redundant with the information in the
152
+`Raw` field, which contains the complete token as base64.
153
+
154
+```go
155
+type Token struct {
156
+	Raw       string                 // Raw contains the raw token
157
+	Method    SigningMethod          // Method is the signing method used or to be used
158
+	Header    map[string]interface{} // Header is the first segment of the token in decoded form
159
+	Claims    Claims                 // Claims is the second segment of the token in decoded form
160
+	Signature []byte                 // Signature is the third segment of the token in decoded form
161
+	Valid     bool                   // Valid specifies if the token is valid
162
+}
163
+```
164
+
165
+Most (if not all) of these changes should not impact the normal usage of this
166
+library. Only users directly accessing the `Signature` field as well as
167
+developers of custom signing methods should be affected.
168
+
169
+# Migration Guide (v4.0.0)
170
+
171
+Starting from [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0),
172
+the import path will be:
173
+
174
+    "github.com/golang-jwt/jwt/v4"
175
+
176
+The `/v4` version will be backwards compatible with existing `v3.x.y` tags in
177
+this repo, as well as `github.com/dgrijalva/jwt-go`. For most users this should
178
+be a drop-in replacement, if you're having troubles migrating, please open an
179
+issue.
180
+
181
+You can replace all occurrences of `github.com/dgrijalva/jwt-go` or
182
+`github.com/golang-jwt/jwt` with `github.com/golang-jwt/jwt/v4`, either manually
183
+or by using tools such as `sed` or `gofmt`.
184
+
185
+And then you'd typically run:
186
+
187
+```
188
+go get github.com/golang-jwt/jwt/v4
189
+go mod tidy
190
+```
191
+
192
+# Older releases (before v3.2.0)
193
+
194
+The original migration guide for older releases can be found at
195
+https://github.com/dgrijalva/jwt-go/blob/master/MIGRATION_GUIDE.md.

+ 167
- 0
vendor/github.com/golang-jwt/jwt/v5/README.md View File

@@ -0,0 +1,167 @@
1
+# jwt-go
2
+
3
+[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml)
4
+[![Go
5
+Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt/v5.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt/v5)
6
+[![Coverage Status](https://coveralls.io/repos/github/golang-jwt/jwt/badge.svg?branch=main)](https://coveralls.io/github/golang-jwt/jwt?branch=main)
7
+
8
+A [go](http://www.golang.org) (or 'golang' for search engine friendliness)
9
+implementation of [JSON Web
10
+Tokens](https://datatracker.ietf.org/doc/html/rfc7519).
11
+
12
+Starting with [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0)
13
+this project adds Go module support, but maintains backwards compatibility with
14
+older `v3.x.y` tags and upstream `github.com/dgrijalva/jwt-go`. See the
15
+[`MIGRATION_GUIDE.md`](./MIGRATION_GUIDE.md) for more information. Version
16
+v5.0.0 introduces major improvements to the validation of tokens, but is not
17
+entirely backwards compatible. 
18
+
19
+> After the original author of the library suggested migrating the maintenance
20
+> of `jwt-go`, a dedicated team of open source maintainers decided to clone the
21
+> existing library into this repository. See
22
+> [dgrijalva/jwt-go#462](https://github.com/dgrijalva/jwt-go/issues/462) for a
23
+> detailed discussion on this topic.
24
+
25
+
26
+**SECURITY NOTICE:** Some older versions of Go have a security issue in the
27
+crypto/elliptic. Recommendation is to upgrade to at least 1.15 See issue
28
+[dgrijalva/jwt-go#216](https://github.com/dgrijalva/jwt-go/issues/216) for more
29
+detail.
30
+
31
+**SECURITY NOTICE:** It's important that you [validate the `alg` presented is
32
+what you
33
+expect](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/).
34
+This library attempts to make it easy to do the right thing by requiring key
35
+types match the expected alg, but you should take the extra step to verify it in
36
+your usage.  See the examples provided.
37
+
38
+### Supported Go versions
39
+
40
+Our support of Go versions is aligned with Go's [version release
41
+policy](https://golang.org/doc/devel/release#policy). So we will support a major
42
+version of Go until there are two newer major releases. We no longer support
43
+building jwt-go with unsupported Go versions, as these contain security
44
+vulnerabilities which will not be fixed.
45
+
46
+## What the heck is a JWT?
47
+
48
+JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web
49
+Tokens.
50
+
51
+In short, it's a signed JSON object that does something useful (for example,
52
+authentication).  It's commonly used for `Bearer` tokens in Oauth 2.  A token is
53
+made of three parts, separated by `.`'s.  The first two parts are JSON objects,
54
+that have been [base64url](https://datatracker.ietf.org/doc/html/rfc4648)
55
+encoded.  The last part is the signature, encoded the same way.
56
+
57
+The first part is called the header.  It contains the necessary information for
58
+verifying the last part, the signature.  For example, which encryption method
59
+was used for signing and what key was used.
60
+
61
+The part in the middle is the interesting bit.  It's called the Claims and
62
+contains the actual stuff you care about.  Refer to [RFC
63
+7519](https://datatracker.ietf.org/doc/html/rfc7519) for information about
64
+reserved keys and the proper way to add your own.
65
+
66
+## What's in the box?
67
+
68
+This library supports the parsing and verification as well as the generation and
69
+signing of JWTs.  Current supported signing algorithms are HMAC SHA, RSA,
70
+RSA-PSS, and ECDSA, though hooks are present for adding your own.
71
+
72
+## Installation Guidelines
73
+
74
+1. To install the jwt package, you first need to have
75
+   [Go](https://go.dev/doc/install) installed, then you can use the command
76
+   below to add `jwt-go` as a dependency in your Go program.
77
+
78
+```sh
79
+go get -u github.com/golang-jwt/jwt/v5
80
+```
81
+
82
+2. Import it in your code:
83
+
84
+```go
85
+import "github.com/golang-jwt/jwt/v5"
86
+```
87
+
88
+## Usage
89
+
90
+A detailed usage guide, including how to sign and verify tokens can be found on
91
+our [documentation website](https://golang-jwt.github.io/jwt/usage/create/).
92
+
93
+## Examples
94
+
95
+See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt/v5)
96
+for examples of usage:
97
+
98
+* [Simple example of parsing and validating a
99
+  token](https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-Parse-Hmac)
100
+* [Simple example of building and signing a
101
+  token](https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-New-Hmac)
102
+* [Directory of
103
+  Examples](https://pkg.go.dev/github.com/golang-jwt/jwt/v5#pkg-examples)
104
+
105
+## Compliance
106
+
107
+This library was last reviewed to comply with [RFC
108
+7519](https://datatracker.ietf.org/doc/html/rfc7519) dated May 2015 with a few
109
+notable differences:
110
+
111
+* In order to protect against accidental use of [Unsecured
112
+  JWTs](https://datatracker.ietf.org/doc/html/rfc7519#section-6), tokens using
113
+  `alg=none` will only be accepted if the constant
114
+  `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
115
+
116
+## Project Status & Versioning
117
+
118
+This library is considered production ready.  Feedback and feature requests are
119
+appreciated.  The API should be considered stable.  There should be very few
120
+backwards-incompatible changes outside of major version updates (and only with
121
+good reason).
122
+
123
+This project uses [Semantic Versioning 2.0.0](http://semver.org).  Accepted pull
124
+requests will land on `main`.  Periodically, versions will be tagged from
125
+`main`.  You can find all the releases on [the project releases
126
+page](https://github.com/golang-jwt/jwt/releases).
127
+
128
+**BREAKING CHANGES:*** A full list of breaking changes is available in
129
+`VERSION_HISTORY.md`.  See `MIGRATION_GUIDE.md` for more information on updating
130
+your code.
131
+
132
+## Extensions
133
+
134
+This library publishes all the necessary components for adding your own signing
135
+methods or key functions.  Simply implement the `SigningMethod` interface and
136
+register a factory method using `RegisterSigningMethod` or provide a
137
+`jwt.Keyfunc`.
138
+
139
+A common use case would be integrating with different 3rd party signature
140
+providers, like key management services from various cloud providers or Hardware
141
+Security Modules (HSMs) or to implement additional standards.
142
+
143
+| Extension | Purpose                                                                                                  | Repo                                       |
144
+| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
145
+| GCP       | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS)             | https://github.com/someone1/gcp-jwt-go     |
146
+| AWS       | Integrates with AWS Key Management Service, KMS                                                          | https://github.com/matelang/jwt-go-aws-kms |
147
+| JWKS      | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc      |
148
+
149
+*Disclaimer*: Unless otherwise specified, these integrations are maintained by
150
+third parties and should not be considered as a primary offer by any of the
151
+mentioned cloud providers
152
+
153
+## More
154
+
155
+Go package documentation can be found [on
156
+pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt/v5). Additional
157
+documentation can be found on [our project
158
+page](https://golang-jwt.github.io/jwt/).
159
+
160
+The command line utility included in this project (cmd/jwt) provides a
161
+straightforward example of token creation and parsing as well as a useful tool
162
+for debugging your own integration. You'll also find several implementation
163
+examples in the documentation.
164
+
165
+[golang-jwt](https://github.com/orgs/golang-jwt) incorporates a modified version
166
+of the JWT logo, which is distributed under the terms of the [MIT
167
+License](https://github.com/jsonwebtoken/jsonwebtoken.github.io/blob/master/LICENSE.txt).

+ 19
- 0
vendor/github.com/golang-jwt/jwt/v5/SECURITY.md View File

@@ -0,0 +1,19 @@
1
+# Security Policy
2
+
3
+## Supported Versions
4
+
5
+As of February 2022 (and until this document is updated), the latest version `v4` is supported.
6
+
7
+## Reporting a Vulnerability
8
+
9
+If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
10
+
11
+You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem.
12
+
13
+## Public Discussions
14
+
15
+Please avoid publicly discussing a potential security vulnerability.
16
+
17
+Let's take this offline and find a solution first, this limits the potential impact as much as possible.
18
+
19
+We appreciate your help!

vendor/github.com/golang-jwt/jwt/VERSION_HISTORY.md → vendor/github.com/golang-jwt/jwt/v5/VERSION_HISTORY.md View File

@@ -1,13 +1,19 @@
1
-## `jwt-go` Version History
1
+# `jwt-go` Version History
2 2
 
3
-#### 3.2.2
3
+The following version history is kept for historic purposes. To retrieve the current changes of each version, please refer to the change-log of the specific release versions on https://github.com/golang-jwt/jwt/releases.
4
+
5
+## 4.0.0
6
+
7
+* Introduces support for Go modules. The `v4` version will be backwards compatible with `v3.x.y`.
8
+
9
+## 3.2.2
4 10
 
5 11
 * Starting from this release, we are adopting the policy to support the most 2 recent versions of Go currently available. By the time of this release, this is Go 1.15 and 1.16 ([#28](https://github.com/golang-jwt/jwt/pull/28)).
6 12
 * Fixed a potential issue that could occur when the verification of `exp`, `iat` or `nbf` was not required and contained invalid contents, i.e. non-numeric/date. Thanks for @thaJeztah for making us aware of that and @giorgos-f3 for originally reporting it to the formtech fork ([#40](https://github.com/golang-jwt/jwt/pull/40)).
7 13
 * Added support for EdDSA / ED25519 ([#36](https://github.com/golang-jwt/jwt/pull/36)).
8 14
 * Optimized allocations ([#33](https://github.com/golang-jwt/jwt/pull/33)).
9 15
 
10
-#### 3.2.1
16
+## 3.2.1
11 17
 
12 18
 * **Import Path Change**: See MIGRATION_GUIDE.md for tips on updating your code
13 19
 	* Changed the import path from `github.com/dgrijalva/jwt-go` to `github.com/golang-jwt/jwt`
@@ -113,17 +119,17 @@ It is likely the only integration change required here will be to change `func(t
113 119
 * Refactored the RSA implementation to be easier to read
114 120
 * Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
115 121
 
116
-#### 1.0.2
122
+## 1.0.2
117 123
 
118 124
 * Fixed bug in parsing public keys from certificates
119 125
 * Added more tests around the parsing of keys for RS256
120 126
 * Code refactoring in RS256 implementation.  No functional changes
121 127
 
122
-#### 1.0.1
128
+## 1.0.1
123 129
 
124 130
 * Fixed panic if RS256 signing method was passed an invalid key
125 131
 
126
-#### 1.0.0
132
+## 1.0.0
127 133
 
128 134
 * First versioned release
129 135
 * API stabilized

+ 16
- 0
vendor/github.com/golang-jwt/jwt/v5/claims.go View File

@@ -0,0 +1,16 @@
1
+package jwt
2
+
3
+// Claims represent any form of a JWT Claims Set according to
4
+// https://datatracker.ietf.org/doc/html/rfc7519#section-4. In order to have a
5
+// common basis for validation, it is required that an implementation is able to
6
+// supply at least the claim names provided in
7
+// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 namely `exp`,
8
+// `iat`, `nbf`, `iss`, `sub` and `aud`.
9
+type Claims interface {
10
+	GetExpirationTime() (*NumericDate, error)
11
+	GetIssuedAt() (*NumericDate, error)
12
+	GetNotBefore() (*NumericDate, error)
13
+	GetIssuer() (string, error)
14
+	GetSubject() (string, error)
15
+	GetAudience() (ClaimStrings, error)
16
+}

vendor/github.com/golang-jwt/jwt/doc.go → vendor/github.com/golang-jwt/jwt/v5/doc.go View File


vendor/github.com/golang-jwt/jwt/ecdsa.go → vendor/github.com/golang-jwt/jwt/v5/ecdsa.go View File

@@ -13,7 +13,7 @@ var (
13 13
 	ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
14 14
 )
15 15
 
16
-// Implements the ECDSA family of signing methods signing methods
16
+// SigningMethodECDSA implements the ECDSA family of signing methods.
17 17
 // Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
18 18
 type SigningMethodECDSA struct {
19 19
 	Name      string
@@ -53,24 +53,16 @@ func (m *SigningMethodECDSA) Alg() string {
53 53
 	return m.Name
54 54
 }
55 55
 
56
-// Implements the Verify method from SigningMethod
56
+// Verify implements token verification for the SigningMethod.
57 57
 // For this verify method, key must be an ecdsa.PublicKey struct
58
-func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
59
-	var err error
60
-
61
-	// Decode the signature
62
-	var sig []byte
63
-	if sig, err = DecodeSegment(signature); err != nil {
64
-		return err
65
-	}
66
-
58
+func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key interface{}) error {
67 59
 	// Get the key
68 60
 	var ecdsaKey *ecdsa.PublicKey
69 61
 	switch k := key.(type) {
70 62
 	case *ecdsa.PublicKey:
71 63
 		ecdsaKey = k
72 64
 	default:
73
-		return ErrInvalidKeyType
65
+		return newError("ECDSA verify expects *ecsda.PublicKey", ErrInvalidKeyType)
74 66
 	}
75 67
 
76 68
 	if len(sig) != 2*m.KeySize {
@@ -95,21 +87,21 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa
95 87
 	return ErrECDSAVerification
96 88
 }
97 89
 
98
-// Implements the Sign method from SigningMethod
90
+// Sign implements token signing for the SigningMethod.
99 91
 // For this signing method, key must be an ecdsa.PrivateKey struct
100
-func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
92
+func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) ([]byte, error) {
101 93
 	// Get the key
102 94
 	var ecdsaKey *ecdsa.PrivateKey
103 95
 	switch k := key.(type) {
104 96
 	case *ecdsa.PrivateKey:
105 97
 		ecdsaKey = k
106 98
 	default:
107
-		return "", ErrInvalidKeyType
99
+		return nil, newError("ECDSA sign expects *ecsda.PrivateKey", ErrInvalidKeyType)
108 100
 	}
109 101
 
110 102
 	// Create the hasher
111 103
 	if !m.Hash.Available() {
112
-		return "", ErrHashUnavailable
104
+		return nil, ErrHashUnavailable
113 105
 	}
114 106
 
115 107
 	hasher := m.Hash.New()
@@ -120,7 +112,7 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string
120 112
 		curveBits := ecdsaKey.Curve.Params().BitSize
121 113
 
122 114
 		if m.CurveBits != curveBits {
123
-			return "", ErrInvalidKey
115
+			return nil, ErrInvalidKey
124 116
 		}
125 117
 
126 118
 		keyBytes := curveBits / 8
@@ -135,8 +127,8 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string
135 127
 		r.FillBytes(out[0:keyBytes]) // r is assigned to the first half of output.
136 128
 		s.FillBytes(out[keyBytes:])  // s is assigned to the second half of output.
137 129
 
138
-		return EncodeSegment(out), nil
130
+		return out, nil
139 131
 	} else {
140
-		return "", err
132
+		return nil, err
141 133
 	}
142 134
 }

vendor/github.com/golang-jwt/jwt/ecdsa_utils.go → vendor/github.com/golang-jwt/jwt/v5/ecdsa_utils.go View File

@@ -8,11 +8,11 @@ import (
8 8
 )
9 9
 
10 10
 var (
11
-	ErrNotECPublicKey  = errors.New("Key is not a valid ECDSA public key")
12
-	ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
11
+	ErrNotECPublicKey  = errors.New("key is not a valid ECDSA public key")
12
+	ErrNotECPrivateKey = errors.New("key is not a valid ECDSA private key")
13 13
 )
14 14
 
15
-// Parse PEM encoded Elliptic Curve Private Key Structure
15
+// ParseECPrivateKeyFromPEM parses a PEM encoded Elliptic Curve Private Key Structure
16 16
 func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
17 17
 	var err error
18 18
 
@@ -39,7 +39,7 @@ func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
39 39
 	return pkey, nil
40 40
 }
41 41
 
42
-// Parse PEM encoded PKCS1 or PKCS8 public key
42
+// ParseECPublicKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 public key
43 43
 func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
44 44
 	var err error
45 45
 

vendor/github.com/golang-jwt/jwt/ed25519.go → vendor/github.com/golang-jwt/jwt/v5/ed25519.go View File

@@ -1,16 +1,17 @@
1 1
 package jwt
2 2
 
3 3
 import (
4
-	"errors"
5
-
4
+	"crypto"
6 5
 	"crypto/ed25519"
6
+	"crypto/rand"
7
+	"errors"
7 8
 )
8 9
 
9 10
 var (
10 11
 	ErrEd25519Verification = errors.New("ed25519: verification error")
11 12
 )
12 13
 
13
-// Implements the EdDSA family
14
+// SigningMethodEd25519 implements the EdDSA family.
14 15
 // Expects ed25519.PrivateKey for signing and ed25519.PublicKey for verification
15 16
 type SigningMethodEd25519 struct{}
16 17
 
@@ -30,27 +31,20 @@ func (m *SigningMethodEd25519) Alg() string {
30 31
 	return "EdDSA"
31 32
 }
32 33
 
33
-// Implements the Verify method from SigningMethod
34
+// Verify implements token verification for the SigningMethod.
34 35
 // For this verify method, key must be an ed25519.PublicKey
35
-func (m *SigningMethodEd25519) Verify(signingString, signature string, key interface{}) error {
36
-	var err error
36
+func (m *SigningMethodEd25519) Verify(signingString string, sig []byte, key interface{}) error {
37 37
 	var ed25519Key ed25519.PublicKey
38 38
 	var ok bool
39 39
 
40 40
 	if ed25519Key, ok = key.(ed25519.PublicKey); !ok {
41
-		return ErrInvalidKeyType
41
+		return newError("Ed25519 verify expects ed25519.PublicKey", ErrInvalidKeyType)
42 42
 	}
43 43
 
44 44
 	if len(ed25519Key) != ed25519.PublicKeySize {
45 45
 		return ErrInvalidKey
46 46
 	}
47 47
 
48
-	// Decode the signature
49
-	var sig []byte
50
-	if sig, err = DecodeSegment(signature); err != nil {
51
-		return err
52
-	}
53
-
54 48
 	// Verify the signature
55 49
 	if !ed25519.Verify(ed25519Key, []byte(signingString), sig) {
56 50
 		return ErrEd25519Verification
@@ -59,23 +53,27 @@ func (m *SigningMethodEd25519) Verify(signingString, signature string, key inter
59 53
 	return nil
60 54
 }
61 55
 
62
-// Implements the Sign method from SigningMethod
56
+// Sign implements token signing for the SigningMethod.
63 57
 // For this signing method, key must be an ed25519.PrivateKey
64
-func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (string, error) {
65
-	var ed25519Key ed25519.PrivateKey
58
+func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) ([]byte, error) {
59
+	var ed25519Key crypto.Signer
66 60
 	var ok bool
67 61
 
68
-	if ed25519Key, ok = key.(ed25519.PrivateKey); !ok {
69
-		return "", ErrInvalidKeyType
62
+	if ed25519Key, ok = key.(crypto.Signer); !ok {
63
+		return nil, newError("Ed25519 sign expects crypto.Signer", ErrInvalidKeyType)
64
+	}
65
+
66
+	if _, ok := ed25519Key.Public().(ed25519.PublicKey); !ok {
67
+		return nil, ErrInvalidKey
70 68
 	}
71 69
 
72
-	// ed25519.Sign panics if private key not equal to ed25519.PrivateKeySize
73
-	// this allows to avoid recover usage
74
-	if len(ed25519Key) != ed25519.PrivateKeySize {
75
-		return "", ErrInvalidKey
70
+	// Sign the string and return the result. ed25519 performs a two-pass hash
71
+	// as part of its algorithm. Therefore, we need to pass a non-prehashed
72
+	// message into the Sign function, as indicated by crypto.Hash(0)
73
+	sig, err := ed25519Key.Sign(rand.Reader, []byte(signingString), crypto.Hash(0))
74
+	if err != nil {
75
+		return nil, err
76 76
 	}
77 77
 
78
-	// Sign the string and return the encoded result
79
-	sig := ed25519.Sign(ed25519Key, []byte(signingString))
80
-	return EncodeSegment(sig), nil
78
+	return sig, nil
81 79
 }

vendor/github.com/golang-jwt/jwt/ed25519_utils.go → vendor/github.com/golang-jwt/jwt/v5/ed25519_utils.go View File

@@ -9,11 +9,11 @@ import (
9 9
 )
10 10
 
11 11
 var (
12
-	ErrNotEdPrivateKey = errors.New("Key is not a valid Ed25519 private key")
13
-	ErrNotEdPublicKey  = errors.New("Key is not a valid Ed25519 public key")
12
+	ErrNotEdPrivateKey = errors.New("key is not a valid Ed25519 private key")
13
+	ErrNotEdPublicKey  = errors.New("key is not a valid Ed25519 public key")
14 14
 )
15 15
 
16
-// Parse PEM-encoded Edwards curve private key
16
+// ParseEdPrivateKeyFromPEM parses a PEM-encoded Edwards curve private key
17 17
 func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) {
18 18
 	var err error
19 19
 
@@ -38,7 +38,7 @@ func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) {
38 38
 	return pkey, nil
39 39
 }
40 40
 
41
-// Parse PEM-encoded Edwards curve public key
41
+// ParseEdPublicKeyFromPEM parses a PEM-encoded Edwards curve public key
42 42
 func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) {
43 43
 	var err error
44 44
 

+ 49
- 0
vendor/github.com/golang-jwt/jwt/v5/errors.go View File

@@ -0,0 +1,49 @@
1
+package jwt
2
+
3
+import (
4
+	"errors"
5
+	"strings"
6
+)
7
+
8
+var (
9
+	ErrInvalidKey                = errors.New("key is invalid")
10
+	ErrInvalidKeyType            = errors.New("key is of invalid type")
11
+	ErrHashUnavailable           = errors.New("the requested hash function is unavailable")
12
+	ErrTokenMalformed            = errors.New("token is malformed")
13
+	ErrTokenUnverifiable         = errors.New("token is unverifiable")
14
+	ErrTokenSignatureInvalid     = errors.New("token signature is invalid")
15
+	ErrTokenRequiredClaimMissing = errors.New("token is missing required claim")
16
+	ErrTokenInvalidAudience      = errors.New("token has invalid audience")
17
+	ErrTokenExpired              = errors.New("token is expired")
18
+	ErrTokenUsedBeforeIssued     = errors.New("token used before issued")
19
+	ErrTokenInvalidIssuer        = errors.New("token has invalid issuer")
20
+	ErrTokenInvalidSubject       = errors.New("token has invalid subject")
21
+	ErrTokenNotValidYet          = errors.New("token is not valid yet")
22
+	ErrTokenInvalidId            = errors.New("token has invalid id")
23
+	ErrTokenInvalidClaims        = errors.New("token has invalid claims")
24
+	ErrInvalidType               = errors.New("invalid type for claim")
25
+)
26
+
27
+// joinedError is an error type that works similar to what [errors.Join]
28
+// produces, with the exception that it has a nice error string; mainly its
29
+// error messages are concatenated using a comma, rather than a newline.
30
+type joinedError struct {
31
+	errs []error
32
+}
33
+
34
+func (je joinedError) Error() string {
35
+	msg := []string{}
36
+	for _, err := range je.errs {
37
+		msg = append(msg, err.Error())
38
+	}
39
+
40
+	return strings.Join(msg, ", ")
41
+}
42
+
43
+// joinErrors joins together multiple errors. Useful for scenarios where
44
+// multiple errors next to each other occur, e.g., in claims validation.
45
+func joinErrors(errs ...error) error {
46
+	return &joinedError{
47
+		errs: errs,
48
+	}
49
+}

+ 47
- 0
vendor/github.com/golang-jwt/jwt/v5/errors_go1_20.go View File

@@ -0,0 +1,47 @@
1
+//go:build go1.20
2
+// +build go1.20
3
+
4
+package jwt
5
+
6
+import (
7
+	"fmt"
8
+)
9
+
10
+// Unwrap implements the multiple error unwrapping for this error type, which is
11
+// possible in Go 1.20.
12
+func (je joinedError) Unwrap() []error {
13
+	return je.errs
14
+}
15
+
16
+// newError creates a new error message with a detailed error message. The
17
+// message will be prefixed with the contents of the supplied error type.
18
+// Additionally, more errors, that provide more context can be supplied which
19
+// will be appended to the message. This makes use of Go 1.20's possibility to
20
+// include more than one %w formatting directive in [fmt.Errorf].
21
+//
22
+// For example,
23
+//
24
+//	newError("no keyfunc was provided", ErrTokenUnverifiable)
25
+//
26
+// will produce the error string
27
+//
28
+//	"token is unverifiable: no keyfunc was provided"
29
+func newError(message string, err error, more ...error) error {
30
+	var format string
31
+	var args []any
32
+	if message != "" {
33
+		format = "%w: %s"
34
+		args = []any{err, message}
35
+	} else {
36
+		format = "%w"
37
+		args = []any{err}
38
+	}
39
+
40
+	for _, e := range more {
41
+		format += ": %w"
42
+		args = append(args, e)
43
+	}
44
+
45
+	err = fmt.Errorf(format, args...)
46
+	return err
47
+}

+ 78
- 0
vendor/github.com/golang-jwt/jwt/v5/errors_go_other.go View File

@@ -0,0 +1,78 @@
1
+//go:build !go1.20
2
+// +build !go1.20
3
+
4
+package jwt
5
+
6
+import (
7
+	"errors"
8
+	"fmt"
9
+)
10
+
11
+// Is implements checking for multiple errors using [errors.Is], since multiple
12
+// error unwrapping is not possible in versions less than Go 1.20.
13
+func (je joinedError) Is(err error) bool {
14
+	for _, e := range je.errs {
15
+		if errors.Is(e, err) {
16
+			return true
17
+		}
18
+	}
19
+
20
+	return false
21
+}
22
+
23
+// wrappedErrors is a workaround for wrapping multiple errors in environments
24
+// where Go 1.20 is not available. It basically uses the already implemented
25
+// functionality of joinedError to handle multiple errors with supplies a
26
+// custom error message that is identical to the one we produce in Go 1.20 using
27
+// multiple %w directives.
28
+type wrappedErrors struct {
29
+	msg string
30
+	joinedError
31
+}
32
+
33
+// Error returns the stored error string
34
+func (we wrappedErrors) Error() string {
35
+	return we.msg
36
+}
37
+
38
+// newError creates a new error message with a detailed error message. The
39
+// message will be prefixed with the contents of the supplied error type.
40
+// Additionally, more errors, that provide more context can be supplied which
41
+// will be appended to the message. Since we cannot use of Go 1.20's possibility
42
+// to include more than one %w formatting directive in [fmt.Errorf], we have to
43
+// emulate that.
44
+//
45
+// For example,
46
+//
47
+//	newError("no keyfunc was provided", ErrTokenUnverifiable)
48
+//
49
+// will produce the error string
50
+//
51
+//	"token is unverifiable: no keyfunc was provided"
52
+func newError(message string, err error, more ...error) error {
53
+	// We cannot wrap multiple errors here with %w, so we have to be a little
54
+	// bit creative. Basically, we are using %s instead of %w to produce the
55
+	// same error message and then throw the result into a custom error struct.
56
+	var format string
57
+	var args []any
58
+	if message != "" {
59
+		format = "%s: %s"
60
+		args = []any{err, message}
61
+	} else {
62
+		format = "%s"
63
+		args = []any{err}
64
+	}
65
+	errs := []error{err}
66
+
67
+	for _, e := range more {
68
+		format += ": %s"
69
+		args = append(args, e)
70
+		errs = append(errs, e)
71
+	}
72
+
73
+	err = &wrappedErrors{
74
+		msg:         fmt.Sprintf(format, args...),
75
+		joinedError: joinedError{errs: errs},
76
+	}
77
+	return err
78
+}

vendor/github.com/golang-jwt/jwt/hmac.go → vendor/github.com/golang-jwt/jwt/v5/hmac.go View File

@@ -6,7 +6,7 @@ import (
6 6
 	"errors"
7 7
 )
8 8
 
9
-// Implements the HMAC-SHA family of signing methods signing methods
9
+// SigningMethodHMAC implements the HMAC-SHA family of signing methods.
10 10
 // Expects key type of []byte for both signing and validation
11 11
 type SigningMethodHMAC struct {
12 12
 	Name string
@@ -45,18 +45,21 @@ func (m *SigningMethodHMAC) Alg() string {
45 45
 	return m.Name
46 46
 }
47 47
 
48
-// Verify the signature of HSXXX tokens.  Returns nil if the signature is valid.
49
-func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
48
+// Verify implements token verification for the SigningMethod. Returns nil if
49
+// the signature is valid. Key must be []byte.
50
+//
51
+// Note it is not advised to provide a []byte which was converted from a 'human
52
+// readable' string using a subset of ASCII characters. To maximize entropy, you
53
+// should ideally be providing a []byte key which was produced from a
54
+// cryptographically random source, e.g. crypto/rand. Additional information
55
+// about this, and why we intentionally are not supporting string as a key can
56
+// be found on our usage guide
57
+// https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types.
58
+func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key interface{}) error {
50 59
 	// Verify the key is the right type
51 60
 	keyBytes, ok := key.([]byte)
52 61
 	if !ok {
53
-		return ErrInvalidKeyType
54
-	}
55
-
56
-	// Decode signature, for comparison
57
-	sig, err := DecodeSegment(signature)
58
-	if err != nil {
59
-		return err
62
+		return newError("HMAC verify expects []byte", ErrInvalidKeyType)
60 63
 	}
61 64
 
62 65
 	// Can we use the specified hashing method?
@@ -77,19 +80,25 @@ func (m *SigningMethodHMAC) Verify(signingString, signature string, key interfac
77 80
 	return nil
78 81
 }
79 82
 
80
-// Implements the Sign method from SigningMethod for this signing method.
81
-// Key must be []byte
82
-func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
83
+// Sign implements token signing for the SigningMethod. Key must be []byte.
84
+//
85
+// Note it is not advised to provide a []byte which was converted from a 'human
86
+// readable' string using a subset of ASCII characters. To maximize entropy, you
87
+// should ideally be providing a []byte key which was produced from a
88
+// cryptographically random source, e.g. crypto/rand. Additional information
89
+// about this, and why we intentionally are not supporting string as a key can
90
+// be found on our usage guide https://golang-jwt.github.io/jwt/usage/signing_methods/.
91
+func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) ([]byte, error) {
83 92
 	if keyBytes, ok := key.([]byte); ok {
84 93
 		if !m.Hash.Available() {
85
-			return "", ErrHashUnavailable
94
+			return nil, newError("HMAC sign expects []byte", ErrInvalidKeyType)
86 95
 		}
87 96
 
88 97
 		hasher := hmac.New(m.Hash.New, keyBytes)
89 98
 		hasher.Write([]byte(signingString))
90 99
 
91
-		return EncodeSegment(hasher.Sum(nil)), nil
100
+		return hasher.Sum(nil), nil
92 101
 	}
93 102
 
94
-	return "", ErrInvalidKeyType
103
+	return nil, ErrInvalidKeyType
95 104
 }

+ 109
- 0
vendor/github.com/golang-jwt/jwt/v5/map_claims.go View File

@@ -0,0 +1,109 @@
1
+package jwt
2
+
3
+import (
4
+	"encoding/json"
5
+	"fmt"
6
+)
7
+
8
+// MapClaims is a claims type that uses the map[string]interface{} for JSON
9
+// decoding. This is the default claims type if you don't supply one
10
+type MapClaims map[string]interface{}
11
+
12
+// GetExpirationTime implements the Claims interface.
13
+func (m MapClaims) GetExpirationTime() (*NumericDate, error) {
14
+	return m.parseNumericDate("exp")
15
+}
16
+
17
+// GetNotBefore implements the Claims interface.
18
+func (m MapClaims) GetNotBefore() (*NumericDate, error) {
19
+	return m.parseNumericDate("nbf")
20
+}
21
+
22
+// GetIssuedAt implements the Claims interface.
23
+func (m MapClaims) GetIssuedAt() (*NumericDate, error) {
24
+	return m.parseNumericDate("iat")
25
+}
26
+
27
+// GetAudience implements the Claims interface.
28
+func (m MapClaims) GetAudience() (ClaimStrings, error) {
29
+	return m.parseClaimsString("aud")
30
+}
31
+
32
+// GetIssuer implements the Claims interface.
33
+func (m MapClaims) GetIssuer() (string, error) {
34
+	return m.parseString("iss")
35
+}
36
+
37
+// GetSubject implements the Claims interface.
38
+func (m MapClaims) GetSubject() (string, error) {
39
+	return m.parseString("sub")
40
+}
41
+
42
+// parseNumericDate tries to parse a key in the map claims type as a number
43
+// date. This will succeed, if the underlying type is either a [float64] or a
44
+// [json.Number]. Otherwise, nil will be returned.
45
+func (m MapClaims) parseNumericDate(key string) (*NumericDate, error) {
46
+	v, ok := m[key]
47
+	if !ok {
48
+		return nil, nil
49
+	}
50
+
51
+	switch exp := v.(type) {
52
+	case float64:
53
+		if exp == 0 {
54
+			return nil, nil
55
+		}
56
+
57
+		return newNumericDateFromSeconds(exp), nil
58
+	case json.Number:
59
+		v, _ := exp.Float64()
60
+
61
+		return newNumericDateFromSeconds(v), nil
62
+	}
63
+
64
+	return nil, newError(fmt.Sprintf("%s is invalid", key), ErrInvalidType)
65
+}
66
+
67
+// parseClaimsString tries to parse a key in the map claims type as a
68
+// [ClaimsStrings] type, which can either be a string or an array of string.
69
+func (m MapClaims) parseClaimsString(key string) (ClaimStrings, error) {
70
+	var cs []string
71
+	switch v := m[key].(type) {
72
+	case string:
73
+		cs = append(cs, v)
74
+	case []string:
75
+		cs = v
76
+	case []interface{}:
77
+		for _, a := range v {
78
+			vs, ok := a.(string)
79
+			if !ok {
80
+				return nil, newError(fmt.Sprintf("%s is invalid", key), ErrInvalidType)
81
+			}
82
+			cs = append(cs, vs)
83
+		}
84
+	}
85
+
86
+	return cs, nil
87
+}
88
+
89
+// parseString tries to parse a key in the map claims type as a [string] type.
90
+// If the key does not exist, an empty string is returned. If the key has the
91
+// wrong type, an error is returned.
92
+func (m MapClaims) parseString(key string) (string, error) {
93
+	var (
94
+		ok  bool
95
+		raw interface{}
96
+		iss string
97
+	)
98
+	raw, ok = m[key]
99
+	if !ok {
100
+		return "", nil
101
+	}
102
+
103
+	iss, ok = raw.(string)
104
+	if !ok {
105
+		return "", newError(fmt.Sprintf("%s is invalid", key), ErrInvalidType)
106
+	}
107
+
108
+	return iss, nil
109
+}

vendor/github.com/golang-jwt/jwt/none.go → vendor/github.com/golang-jwt/jwt/v5/none.go View File

@@ -1,6 +1,6 @@
1 1
 package jwt
2 2
 
3
-// Implements the none signing method.  This is required by the spec
3
+// SigningMethodNone implements the none signing method.  This is required by the spec
4 4
 // but you probably should never use it.
5 5
 var SigningMethodNone *signingMethodNone
6 6
 
@@ -13,7 +13,7 @@ type unsafeNoneMagicConstant string
13 13
 
14 14
 func init() {
15 15
 	SigningMethodNone = &signingMethodNone{}
16
-	NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
16
+	NoneSignatureTypeDisallowedError = newError("'none' signature type is not allowed", ErrTokenUnverifiable)
17 17
 
18 18
 	RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
19 19
 		return SigningMethodNone
@@ -25,18 +25,15 @@ func (m *signingMethodNone) Alg() string {
25 25
 }
26 26
 
27 27
 // Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
28
-func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
28
+func (m *signingMethodNone) Verify(signingString string, sig []byte, key interface{}) (err error) {
29 29
 	// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
30 30
 	// accepting 'none' signing method
31 31
 	if _, ok := key.(unsafeNoneMagicConstant); !ok {
32 32
 		return NoneSignatureTypeDisallowedError
33 33
 	}
34 34
 	// If signing method is none, signature must be an empty string
35
-	if signature != "" {
36
-		return NewValidationError(
37
-			"'none' signing method with non-empty signature",
38
-			ValidationErrorSignatureInvalid,
39
-		)
35
+	if len(sig) != 0 {
36
+		return newError("'none' signing method with non-empty signature", ErrTokenUnverifiable)
40 37
 	}
41 38
 
42 39
 	// Accept 'none' signing method.
@@ -44,9 +41,10 @@ func (m *signingMethodNone) Verify(signingString, signature string, key interfac
44 41
 }
45 42
 
46 43
 // Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
47
-func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
44
+func (m *signingMethodNone) Sign(signingString string, key interface{}) ([]byte, error) {
48 45
 	if _, ok := key.(unsafeNoneMagicConstant); ok {
49
-		return "", nil
46
+		return []byte{}, nil
50 47
 	}
51
-	return "", NoneSignatureTypeDisallowedError
48
+
49
+	return nil, NoneSignatureTypeDisallowedError
52 50
 }

+ 238
- 0
vendor/github.com/golang-jwt/jwt/v5/parser.go View File

@@ -0,0 +1,238 @@
1
+package jwt
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/base64"
6
+	"encoding/json"
7
+	"fmt"
8
+	"strings"
9
+)
10
+
11
+type Parser struct {
12
+	// If populated, only these methods will be considered valid.
13
+	validMethods []string
14
+
15
+	// Use JSON Number format in JSON decoder.
16
+	useJSONNumber bool
17
+
18
+	// Skip claims validation during token parsing.
19
+	skipClaimsValidation bool
20
+
21
+	validator *Validator
22
+
23
+	decodeStrict bool
24
+
25
+	decodePaddingAllowed bool
26
+}
27
+
28
+// NewParser creates a new Parser with the specified options
29
+func NewParser(options ...ParserOption) *Parser {
30
+	p := &Parser{
31
+		validator: &Validator{},
32
+	}
33
+
34
+	// Loop through our parsing options and apply them
35
+	for _, option := range options {
36
+		option(p)
37
+	}
38
+
39
+	return p
40
+}
41
+
42
+// Parse parses, validates, verifies the signature and returns the parsed token.
43
+// keyFunc will receive the parsed token and should return the key for validating.
44
+func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
45
+	return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
46
+}
47
+
48
+// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims
49
+// interface. This provides default values which can be overridden and allows a caller to use their own type, rather
50
+// than the default MapClaims implementation of Claims.
51
+//
52
+// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
53
+// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
54
+// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
55
+func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
56
+	token, parts, err := p.ParseUnverified(tokenString, claims)
57
+	if err != nil {
58
+		return token, err
59
+	}
60
+
61
+	// Verify signing method is in the required set
62
+	if p.validMethods != nil {
63
+		var signingMethodValid = false
64
+		var alg = token.Method.Alg()
65
+		for _, m := range p.validMethods {
66
+			if m == alg {
67
+				signingMethodValid = true
68
+				break
69
+			}
70
+		}
71
+		if !signingMethodValid {
72
+			// signing method is not in the listed set
73
+			return token, newError(fmt.Sprintf("signing method %v is invalid", alg), ErrTokenSignatureInvalid)
74
+		}
75
+	}
76
+
77
+	// Decode signature
78
+	token.Signature, err = p.DecodeSegment(parts[2])
79
+	if err != nil {
80
+		return token, newError("could not base64 decode signature", ErrTokenMalformed, err)
81
+	}
82
+	text := strings.Join(parts[0:2], ".")
83
+
84
+	// Lookup key(s)
85
+	if keyFunc == nil {
86
+		// keyFunc was not provided.  short circuiting validation
87
+		return token, newError("no keyfunc was provided", ErrTokenUnverifiable)
88
+	}
89
+
90
+	got, err := keyFunc(token)
91
+	if err != nil {
92
+		return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
93
+	}
94
+
95
+	switch have := got.(type) {
96
+	case VerificationKeySet:
97
+		if len(have.Keys) == 0 {
98
+			return token, newError("keyfunc returned empty verification key set", ErrTokenUnverifiable)
99
+		}
100
+		// Iterate through keys and verify signature, skipping the rest when a match is found.
101
+		// Return the last error if no match is found.
102
+		for _, key := range have.Keys {
103
+			if err = token.Method.Verify(text, token.Signature, key); err == nil {
104
+				break
105
+			}
106
+		}
107
+	default:
108
+		err = token.Method.Verify(text, token.Signature, have)
109
+	}
110
+	if err != nil {
111
+		return token, newError("", ErrTokenSignatureInvalid, err)
112
+	}
113
+
114
+	// Validate Claims
115
+	if !p.skipClaimsValidation {
116
+		// Make sure we have at least a default validator
117
+		if p.validator == nil {
118
+			p.validator = NewValidator()
119
+		}
120
+
121
+		if err := p.validator.Validate(claims); err != nil {
122
+			return token, newError("", ErrTokenInvalidClaims, err)
123
+		}
124
+	}
125
+
126
+	// No errors so far, token is valid.
127
+	token.Valid = true
128
+
129
+	return token, nil
130
+}
131
+
132
+// ParseUnverified parses the token but doesn't validate the signature.
133
+//
134
+// WARNING: Don't use this method unless you know what you're doing.
135
+//
136
+// It's only ever useful in cases where you know the signature is valid (since it has already
137
+// been or will be checked elsewhere in the stack) and you want to extract values from it.
138
+func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
139
+	parts = strings.Split(tokenString, ".")
140
+	if len(parts) != 3 {
141
+		return nil, parts, newError("token contains an invalid number of segments", ErrTokenMalformed)
142
+	}
143
+
144
+	token = &Token{Raw: tokenString}
145
+
146
+	// parse Header
147
+	var headerBytes []byte
148
+	if headerBytes, err = p.DecodeSegment(parts[0]); err != nil {
149
+		return token, parts, newError("could not base64 decode header", ErrTokenMalformed, err)
150
+	}
151
+	if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
152
+		return token, parts, newError("could not JSON decode header", ErrTokenMalformed, err)
153
+	}
154
+
155
+	// parse Claims
156
+	token.Claims = claims
157
+
158
+	claimBytes, err := p.DecodeSegment(parts[1])
159
+	if err != nil {
160
+		return token, parts, newError("could not base64 decode claim", ErrTokenMalformed, err)
161
+	}
162
+
163
+	// If `useJSONNumber` is enabled then we must use *json.Decoder to decode
164
+	// the claims. However, this comes with a performance penalty so only use
165
+	// it if we must and, otherwise, simple use json.Unmarshal.
166
+	if !p.useJSONNumber {
167
+		// JSON Unmarshal. Special case for map type to avoid weird pointer behavior.
168
+		if c, ok := token.Claims.(MapClaims); ok {
169
+			err = json.Unmarshal(claimBytes, &c)
170
+		} else {
171
+			err = json.Unmarshal(claimBytes, &claims)
172
+		}
173
+	} else {
174
+		dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
175
+		dec.UseNumber()
176
+		// JSON Decode. Special case for map type to avoid weird pointer behavior.
177
+		if c, ok := token.Claims.(MapClaims); ok {
178
+			err = dec.Decode(&c)
179
+		} else {
180
+			err = dec.Decode(&claims)
181
+		}
182
+	}
183
+	if err != nil {
184
+		return token, parts, newError("could not JSON decode claim", ErrTokenMalformed, err)
185
+	}
186
+
187
+	// Lookup signature method
188
+	if method, ok := token.Header["alg"].(string); ok {
189
+		if token.Method = GetSigningMethod(method); token.Method == nil {
190
+			return token, parts, newError("signing method (alg) is unavailable", ErrTokenUnverifiable)
191
+		}
192
+	} else {
193
+		return token, parts, newError("signing method (alg) is unspecified", ErrTokenUnverifiable)
194
+	}
195
+
196
+	return token, parts, nil
197
+}
198
+
199
+// DecodeSegment decodes a JWT specific base64url encoding. This function will
200
+// take into account whether the [Parser] is configured with additional options,
201
+// such as [WithStrictDecoding] or [WithPaddingAllowed].
202
+func (p *Parser) DecodeSegment(seg string) ([]byte, error) {
203
+	encoding := base64.RawURLEncoding
204
+
205
+	if p.decodePaddingAllowed {
206
+		if l := len(seg) % 4; l > 0 {
207
+			seg += strings.Repeat("=", 4-l)
208
+		}
209
+		encoding = base64.URLEncoding
210
+	}
211
+
212
+	if p.decodeStrict {
213
+		encoding = encoding.Strict()
214
+	}
215
+	return encoding.DecodeString(seg)
216
+}
217
+
218
+// Parse parses, validates, verifies the signature and returns the parsed token.
219
+// keyFunc will receive the parsed token and should return the cryptographic key
220
+// for verifying the signature. The caller is strongly encouraged to set the
221
+// WithValidMethods option to validate the 'alg' claim in the token matches the
222
+// expected algorithm. For more details about the importance of validating the
223
+// 'alg' claim, see
224
+// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
225
+func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
226
+	return NewParser(options...).Parse(tokenString, keyFunc)
227
+}
228
+
229
+// ParseWithClaims is a shortcut for NewParser().ParseWithClaims().
230
+//
231
+// Note: If you provide a custom claim implementation that embeds one of the
232
+// standard claims (such as RegisteredClaims), make sure that a) you either
233
+// embed a non-pointer version of the claims or b) if you are using a pointer,
234
+// allocate the proper memory for it before passing in the overall claims,
235
+// otherwise you might run into a panic.
236
+func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
237
+	return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
238
+}

+ 128
- 0
vendor/github.com/golang-jwt/jwt/v5/parser_option.go View File

@@ -0,0 +1,128 @@
1
+package jwt
2
+
3
+import "time"
4
+
5
+// ParserOption is used to implement functional-style options that modify the
6
+// behavior of the parser. To add new options, just create a function (ideally
7
+// beginning with With or Without) that returns an anonymous function that takes
8
+// a *Parser type as input and manipulates its configuration accordingly.
9
+type ParserOption func(*Parser)
10
+
11
+// WithValidMethods is an option to supply algorithm methods that the parser
12
+// will check. Only those methods will be considered valid. It is heavily
13
+// encouraged to use this option in order to prevent attacks such as
14
+// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/.
15
+func WithValidMethods(methods []string) ParserOption {
16
+	return func(p *Parser) {
17
+		p.validMethods = methods
18
+	}
19
+}
20
+
21
+// WithJSONNumber is an option to configure the underlying JSON parser with
22
+// UseNumber.
23
+func WithJSONNumber() ParserOption {
24
+	return func(p *Parser) {
25
+		p.useJSONNumber = true
26
+	}
27
+}
28
+
29
+// WithoutClaimsValidation is an option to disable claims validation. This
30
+// option should only be used if you exactly know what you are doing.
31
+func WithoutClaimsValidation() ParserOption {
32
+	return func(p *Parser) {
33
+		p.skipClaimsValidation = true
34
+	}
35
+}
36
+
37
+// WithLeeway returns the ParserOption for specifying the leeway window.
38
+func WithLeeway(leeway time.Duration) ParserOption {
39
+	return func(p *Parser) {
40
+		p.validator.leeway = leeway
41
+	}
42
+}
43
+
44
+// WithTimeFunc returns the ParserOption for specifying the time func. The
45
+// primary use-case for this is testing. If you are looking for a way to account
46
+// for clock-skew, WithLeeway should be used instead.
47
+func WithTimeFunc(f func() time.Time) ParserOption {
48
+	return func(p *Parser) {
49
+		p.validator.timeFunc = f
50
+	}
51
+}
52
+
53
+// WithIssuedAt returns the ParserOption to enable verification
54
+// of issued-at.
55
+func WithIssuedAt() ParserOption {
56
+	return func(p *Parser) {
57
+		p.validator.verifyIat = true
58
+	}
59
+}
60
+
61
+// WithExpirationRequired returns the ParserOption to make exp claim required.
62
+// By default exp claim is optional.
63
+func WithExpirationRequired() ParserOption {
64
+	return func(p *Parser) {
65
+		p.validator.requireExp = true
66
+	}
67
+}
68
+
69
+// WithAudience configures the validator to require the specified audience in
70
+// the `aud` claim. Validation will fail if the audience is not listed in the
71
+// token or the `aud` claim is missing.
72
+//
73
+// NOTE: While the `aud` claim is OPTIONAL in a JWT, the handling of it is
74
+// application-specific. Since this validation API is helping developers in
75
+// writing secure application, we decided to REQUIRE the existence of the claim,
76
+// if an audience is expected.
77
+func WithAudience(aud string) ParserOption {
78
+	return func(p *Parser) {
79
+		p.validator.expectedAud = aud
80
+	}
81
+}
82
+
83
+// WithIssuer configures the validator to require the specified issuer in the
84
+// `iss` claim. Validation will fail if a different issuer is specified in the
85
+// token or the `iss` claim is missing.
86
+//
87
+// NOTE: While the `iss` claim is OPTIONAL in a JWT, the handling of it is
88
+// application-specific. Since this validation API is helping developers in
89
+// writing secure application, we decided to REQUIRE the existence of the claim,
90
+// if an issuer is expected.
91
+func WithIssuer(iss string) ParserOption {
92
+	return func(p *Parser) {
93
+		p.validator.expectedIss = iss
94
+	}
95
+}
96
+
97
+// WithSubject configures the validator to require the specified subject in the
98
+// `sub` claim. Validation will fail if a different subject is specified in the
99
+// token or the `sub` claim is missing.
100
+//
101
+// NOTE: While the `sub` claim is OPTIONAL in a JWT, the handling of it is
102
+// application-specific. Since this validation API is helping developers in
103
+// writing secure application, we decided to REQUIRE the existence of the claim,
104
+// if a subject is expected.
105
+func WithSubject(sub string) ParserOption {
106
+	return func(p *Parser) {
107
+		p.validator.expectedSub = sub
108
+	}
109
+}
110
+
111
+// WithPaddingAllowed will enable the codec used for decoding JWTs to allow
112
+// padding. Note that the JWS RFC7515 states that the tokens will utilize a
113
+// Base64url encoding with no padding. Unfortunately, some implementations of
114
+// JWT are producing non-standard tokens, and thus require support for decoding.
115
+func WithPaddingAllowed() ParserOption {
116
+	return func(p *Parser) {
117
+		p.decodePaddingAllowed = true
118
+	}
119
+}
120
+
121
+// WithStrictDecoding will switch the codec used for decoding JWTs into strict
122
+// mode. In this mode, the decoder requires that trailing padding bits are zero,
123
+// as described in RFC 4648 section 3.5.
124
+func WithStrictDecoding() ParserOption {
125
+	return func(p *Parser) {
126
+		p.decodeStrict = true
127
+	}
128
+}

+ 63
- 0
vendor/github.com/golang-jwt/jwt/v5/registered_claims.go View File

@@ -0,0 +1,63 @@
1
+package jwt
2
+
3
+// RegisteredClaims are a structured version of the JWT Claims Set,
4
+// restricted to Registered Claim Names, as referenced at
5
+// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
6
+//
7
+// This type can be used on its own, but then additional private and
8
+// public claims embedded in the JWT will not be parsed. The typical use-case
9
+// therefore is to embedded this in a user-defined claim type.
10
+//
11
+// See examples for how to use this with your own claim types.
12
+type RegisteredClaims struct {
13
+	// the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
14
+	Issuer string `json:"iss,omitempty"`
15
+
16
+	// the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
17
+	Subject string `json:"sub,omitempty"`
18
+
19
+	// the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
20
+	Audience ClaimStrings `json:"aud,omitempty"`
21
+
22
+	// the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
23
+	ExpiresAt *NumericDate `json:"exp,omitempty"`
24
+
25
+	// the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
26
+	NotBefore *NumericDate `json:"nbf,omitempty"`
27
+
28
+	// the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
29
+	IssuedAt *NumericDate `json:"iat,omitempty"`
30
+
31
+	// the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
32
+	ID string `json:"jti,omitempty"`
33
+}
34
+
35
+// GetExpirationTime implements the Claims interface.
36
+func (c RegisteredClaims) GetExpirationTime() (*NumericDate, error) {
37
+	return c.ExpiresAt, nil
38
+}
39
+
40
+// GetNotBefore implements the Claims interface.
41
+func (c RegisteredClaims) GetNotBefore() (*NumericDate, error) {
42
+	return c.NotBefore, nil
43
+}
44
+
45
+// GetIssuedAt implements the Claims interface.
46
+func (c RegisteredClaims) GetIssuedAt() (*NumericDate, error) {
47
+	return c.IssuedAt, nil
48
+}
49
+
50
+// GetAudience implements the Claims interface.
51
+func (c RegisteredClaims) GetAudience() (ClaimStrings, error) {
52
+	return c.Audience, nil
53
+}
54
+
55
+// GetIssuer implements the Claims interface.
56
+func (c RegisteredClaims) GetIssuer() (string, error) {
57
+	return c.Issuer, nil
58
+}
59
+
60
+// GetSubject implements the Claims interface.
61
+func (c RegisteredClaims) GetSubject() (string, error) {
62
+	return c.Subject, nil
63
+}

vendor/github.com/golang-jwt/jwt/rsa.go → vendor/github.com/golang-jwt/jwt/v5/rsa.go View File

@@ -6,7 +6,7 @@ import (
6 6
 	"crypto/rsa"
7 7
 )
8 8
 
9
-// Implements the RSA family of signing methods signing methods
9
+// SigningMethodRSA implements the RSA family of signing methods.
10 10
 // Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
11 11
 type SigningMethodRSA struct {
12 12
 	Name string
@@ -44,22 +44,14 @@ func (m *SigningMethodRSA) Alg() string {
44 44
 	return m.Name
45 45
 }
46 46
 
47
-// Implements the Verify method from SigningMethod
47
+// Verify implements token verification for the SigningMethod
48 48
 // For this signing method, must be an *rsa.PublicKey structure.
49
-func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
50
-	var err error
51
-
52
-	// Decode the signature
53
-	var sig []byte
54
-	if sig, err = DecodeSegment(signature); err != nil {
55
-		return err
56
-	}
57
-
49
+func (m *SigningMethodRSA) Verify(signingString string, sig []byte, key interface{}) error {
58 50
 	var rsaKey *rsa.PublicKey
59 51
 	var ok bool
60 52
 
61 53
 	if rsaKey, ok = key.(*rsa.PublicKey); !ok {
62
-		return ErrInvalidKeyType
54
+		return newError("RSA verify expects *rsa.PublicKey", ErrInvalidKeyType)
63 55
 	}
64 56
 
65 57
 	// Create hasher
@@ -73,20 +65,20 @@ func (m *SigningMethodRSA) Verify(signingString, signature string, key interface
73 65
 	return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
74 66
 }
75 67
 
76
-// Implements the Sign method from SigningMethod
68
+// Sign implements token signing for the SigningMethod
77 69
 // For this signing method, must be an *rsa.PrivateKey structure.
78
-func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
70
+func (m *SigningMethodRSA) Sign(signingString string, key interface{}) ([]byte, error) {
79 71
 	var rsaKey *rsa.PrivateKey
80 72
 	var ok bool
81 73
 
82 74
 	// Validate type of key
83 75
 	if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
84
-		return "", ErrInvalidKey
76
+		return nil, newError("RSA sign expects *rsa.PrivateKey", ErrInvalidKeyType)
85 77
 	}
86 78
 
87 79
 	// Create the hasher
88 80
 	if !m.Hash.Available() {
89
-		return "", ErrHashUnavailable
81
+		return nil, ErrHashUnavailable
90 82
 	}
91 83
 
92 84
 	hasher := m.Hash.New()
@@ -94,8 +86,8 @@ func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string,
94 86
 
95 87
 	// Sign the string and return the encoded bytes
96 88
 	if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
97
-		return EncodeSegment(sigBytes), nil
89
+		return sigBytes, nil
98 90
 	} else {
99
-		return "", err
91
+		return nil, err
100 92
 	}
101 93
 }

vendor/github.com/golang-jwt/jwt/rsa_pss.go → vendor/github.com/golang-jwt/jwt/v5/rsa_pss.go View File

@@ -1,3 +1,4 @@
1
+//go:build go1.4
1 2
 // +build go1.4
2 3
 
3 4
 package jwt
@@ -8,7 +9,7 @@ import (
8 9
 	"crypto/rsa"
9 10
 )
10 11
 
11
-// Implements the RSAPSS family of signing methods signing methods
12
+// SigningMethodRSAPSS implements the RSAPSS family of signing methods signing methods
12 13
 type SigningMethodRSAPSS struct {
13 14
 	*SigningMethodRSA
14 15
 	Options *rsa.PSSOptions
@@ -79,23 +80,15 @@ func init() {
79 80
 	})
80 81
 }
81 82
 
82
-// Implements the Verify method from SigningMethod
83
+// Verify implements token verification for the SigningMethod.
83 84
 // For this verify method, key must be an rsa.PublicKey struct
84
-func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
85
-	var err error
86
-
87
-	// Decode the signature
88
-	var sig []byte
89
-	if sig, err = DecodeSegment(signature); err != nil {
90
-		return err
91
-	}
92
-
85
+func (m *SigningMethodRSAPSS) Verify(signingString string, sig []byte, key interface{}) error {
93 86
 	var rsaKey *rsa.PublicKey
94 87
 	switch k := key.(type) {
95 88
 	case *rsa.PublicKey:
96 89
 		rsaKey = k
97 90
 	default:
98
-		return ErrInvalidKey
91
+		return newError("RSA-PSS verify expects *rsa.PublicKey", ErrInvalidKeyType)
99 92
 	}
100 93
 
101 94
 	// Create hasher
@@ -113,21 +106,21 @@ func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interf
113 106
 	return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, opts)
114 107
 }
115 108
 
116
-// Implements the Sign method from SigningMethod
109
+// Sign implements token signing for the SigningMethod.
117 110
 // For this signing method, key must be an rsa.PrivateKey struct
118
-func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
111
+func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) ([]byte, error) {
119 112
 	var rsaKey *rsa.PrivateKey
120 113
 
121 114
 	switch k := key.(type) {
122 115
 	case *rsa.PrivateKey:
123 116
 		rsaKey = k
124 117
 	default:
125
-		return "", ErrInvalidKeyType
118
+		return nil, newError("RSA-PSS sign expects *rsa.PrivateKey", ErrInvalidKeyType)
126 119
 	}
127 120
 
128 121
 	// Create the hasher
129 122
 	if !m.Hash.Available() {
130
-		return "", ErrHashUnavailable
123
+		return nil, ErrHashUnavailable
131 124
 	}
132 125
 
133 126
 	hasher := m.Hash.New()
@@ -135,8 +128,8 @@ func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (strin
135 128
 
136 129
 	// Sign the string and return the encoded bytes
137 130
 	if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
138
-		return EncodeSegment(sigBytes), nil
131
+		return sigBytes, nil
139 132
 	} else {
140
-		return "", err
133
+		return nil, err
141 134
 	}
142 135
 }

vendor/github.com/golang-jwt/jwt/rsa_utils.go → vendor/github.com/golang-jwt/jwt/v5/rsa_utils.go View File

@@ -8,12 +8,12 @@ import (
8 8
 )
9 9
 
10 10
 var (
11
-	ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be a PEM encoded PKCS1 or PKCS8 key")
12
-	ErrNotRSAPrivateKey    = errors.New("Key is not a valid RSA private key")
13
-	ErrNotRSAPublicKey     = errors.New("Key is not a valid RSA public key")
11
+	ErrKeyMustBePEMEncoded = errors.New("invalid key: Key must be a PEM encoded PKCS1 or PKCS8 key")
12
+	ErrNotRSAPrivateKey    = errors.New("key is not a valid RSA private key")
13
+	ErrNotRSAPublicKey     = errors.New("key is not a valid RSA public key")
14 14
 )
15 15
 
16
-// Parse PEM encoded PKCS1 or PKCS8 private key
16
+// ParseRSAPrivateKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 private key
17 17
 func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
18 18
 	var err error
19 19
 
@@ -39,7 +39,11 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
39 39
 	return pkey, nil
40 40
 }
41 41
 
42
-// Parse PEM encoded PKCS1 or PKCS8 private key protected with password
42
+// ParseRSAPrivateKeyFromPEMWithPassword parses a PEM encoded PKCS1 or PKCS8 private key protected with password
43
+//
44
+// Deprecated: This function is deprecated and should not be used anymore. It uses the deprecated x509.DecryptPEMBlock
45
+// function, which was deprecated since RFC 1423 is regarded insecure by design. Unfortunately, there is no alternative
46
+// in the Go standard library for now. See https://github.com/golang/go/issues/8860.
43 47
 func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) {
44 48
 	var err error
45 49
 
@@ -71,7 +75,7 @@ func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.Pr
71 75
 	return pkey, nil
72 76
 }
73 77
 
74
-// Parse PEM encoded PKCS1 or PKCS8 public key
78
+// ParseRSAPublicKeyFromPEM parses a certificate or a PEM encoded PKCS1 or PKIX public key
75 79
 func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
76 80
 	var err error
77 81
 
@@ -87,7 +91,9 @@ func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
87 91
 		if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
88 92
 			parsedKey = cert.PublicKey
89 93
 		} else {
90
-			return nil, err
94
+			if parsedKey, err = x509.ParsePKCS1PublicKey(block.Bytes); err != nil {
95
+				return nil, err
96
+			}
91 97
 		}
92 98
 	}
93 99
 

+ 49
- 0
vendor/github.com/golang-jwt/jwt/v5/signing_method.go View File

@@ -0,0 +1,49 @@
1
+package jwt
2
+
3
+import (
4
+	"sync"
5
+)
6
+
7
+var signingMethods = map[string]func() SigningMethod{}
8
+var signingMethodLock = new(sync.RWMutex)
9
+
10
+// SigningMethod can be used add new methods for signing or verifying tokens. It
11
+// takes a decoded signature as an input in the Verify function and produces a
12
+// signature in Sign. The signature is then usually base64 encoded as part of a
13
+// JWT.
14
+type SigningMethod interface {
15
+	Verify(signingString string, sig []byte, key interface{}) error // Returns nil if signature is valid
16
+	Sign(signingString string, key interface{}) ([]byte, error)     // Returns signature or error
17
+	Alg() string                                                    // returns the alg identifier for this method (example: 'HS256')
18
+}
19
+
20
+// RegisterSigningMethod registers the "alg" name and a factory function for signing method.
21
+// This is typically done during init() in the method's implementation
22
+func RegisterSigningMethod(alg string, f func() SigningMethod) {
23
+	signingMethodLock.Lock()
24
+	defer signingMethodLock.Unlock()
25
+
26
+	signingMethods[alg] = f
27
+}
28
+
29
+// GetSigningMethod retrieves a signing method from an "alg" string
30
+func GetSigningMethod(alg string) (method SigningMethod) {
31
+	signingMethodLock.RLock()
32
+	defer signingMethodLock.RUnlock()
33
+
34
+	if methodF, ok := signingMethods[alg]; ok {
35
+		method = methodF()
36
+	}
37
+	return
38
+}
39
+
40
+// GetAlgorithms returns a list of registered "alg" names
41
+func GetAlgorithms() (algs []string) {
42
+	signingMethodLock.RLock()
43
+	defer signingMethodLock.RUnlock()
44
+
45
+	for alg := range signingMethods {
46
+		algs = append(algs, alg)
47
+	}
48
+	return
49
+}

+ 1
- 0
vendor/github.com/golang-jwt/jwt/v5/staticcheck.conf View File

@@ -0,0 +1 @@
1
+checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1023"]

+ 100
- 0
vendor/github.com/golang-jwt/jwt/v5/token.go View File

@@ -0,0 +1,100 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto"
5
+	"encoding/base64"
6
+	"encoding/json"
7
+)
8
+
9
+// Keyfunc will be used by the Parse methods as a callback function to supply
10
+// the key for verification.  The function receives the parsed, but unverified
11
+// Token.  This allows you to use properties in the Header of the token (such as
12
+// `kid`) to identify which key to use.
13
+//
14
+// The returned interface{} may be a single key or a VerificationKeySet containing
15
+// multiple keys.
16
+type Keyfunc func(*Token) (interface{}, error)
17
+
18
+// VerificationKey represents a public or secret key for verifying a token's signature.
19
+type VerificationKey interface {
20
+	crypto.PublicKey | []uint8
21
+}
22
+
23
+// VerificationKeySet is a set of public or secret keys. It is used by the parser to verify a token.
24
+type VerificationKeySet struct {
25
+	Keys []VerificationKey
26
+}
27
+
28
+// Token represents a JWT Token.  Different fields will be used depending on
29
+// whether you're creating or parsing/verifying a token.
30
+type Token struct {
31
+	Raw       string                 // Raw contains the raw token.  Populated when you [Parse] a token
32
+	Method    SigningMethod          // Method is the signing method used or to be used
33
+	Header    map[string]interface{} // Header is the first segment of the token in decoded form
34
+	Claims    Claims                 // Claims is the second segment of the token in decoded form
35
+	Signature []byte                 // Signature is the third segment of the token in decoded form.  Populated when you Parse a token
36
+	Valid     bool                   // Valid specifies if the token is valid.  Populated when you Parse/Verify a token
37
+}
38
+
39
+// New creates a new [Token] with the specified signing method and an empty map
40
+// of claims. Additional options can be specified, but are currently unused.
41
+func New(method SigningMethod, opts ...TokenOption) *Token {
42
+	return NewWithClaims(method, MapClaims{}, opts...)
43
+}
44
+
45
+// NewWithClaims creates a new [Token] with the specified signing method and
46
+// claims. Additional options can be specified, but are currently unused.
47
+func NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Token {
48
+	return &Token{
49
+		Header: map[string]interface{}{
50
+			"typ": "JWT",
51
+			"alg": method.Alg(),
52
+		},
53
+		Claims: claims,
54
+		Method: method,
55
+	}
56
+}
57
+
58
+// SignedString creates and returns a complete, signed JWT. The token is signed
59
+// using the SigningMethod specified in the token. Please refer to
60
+// https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types
61
+// for an overview of the different signing methods and their respective key
62
+// types.
63
+func (t *Token) SignedString(key interface{}) (string, error) {
64
+	sstr, err := t.SigningString()
65
+	if err != nil {
66
+		return "", err
67
+	}
68
+
69
+	sig, err := t.Method.Sign(sstr, key)
70
+	if err != nil {
71
+		return "", err
72
+	}
73
+
74
+	return sstr + "." + t.EncodeSegment(sig), nil
75
+}
76
+
77
+// SigningString generates the signing string.  This is the most expensive part
78
+// of the whole deal.  Unless you need this for something special, just go
79
+// straight for the SignedString.
80
+func (t *Token) SigningString() (string, error) {
81
+	h, err := json.Marshal(t.Header)
82
+	if err != nil {
83
+		return "", err
84
+	}
85
+
86
+	c, err := json.Marshal(t.Claims)
87
+	if err != nil {
88
+		return "", err
89
+	}
90
+
91
+	return t.EncodeSegment(h) + "." + t.EncodeSegment(c), nil
92
+}
93
+
94
+// EncodeSegment encodes a JWT specific base64url encoding with padding
95
+// stripped. In the future, this function might take into account a
96
+// [TokenOption]. Therefore, this function exists as a method of [Token], rather
97
+// than a global function.
98
+func (*Token) EncodeSegment(seg []byte) string {
99
+	return base64.RawURLEncoding.EncodeToString(seg)
100
+}

+ 5
- 0
vendor/github.com/golang-jwt/jwt/v5/token_option.go View File

@@ -0,0 +1,5 @@
1
+package jwt
2
+
3
+// TokenOption is a reserved type, which provides some forward compatibility,
4
+// if we ever want to introduce token creation-related options.
5
+type TokenOption func(*Token)

+ 149
- 0
vendor/github.com/golang-jwt/jwt/v5/types.go View File

@@ -0,0 +1,149 @@
1
+package jwt
2
+
3
+import (
4
+	"encoding/json"
5
+	"fmt"
6
+	"math"
7
+	"strconv"
8
+	"time"
9
+)
10
+
11
+// TimePrecision sets the precision of times and dates within this library. This
12
+// has an influence on the precision of times when comparing expiry or other
13
+// related time fields. Furthermore, it is also the precision of times when
14
+// serializing.
15
+//
16
+// For backwards compatibility the default precision is set to seconds, so that
17
+// no fractional timestamps are generated.
18
+var TimePrecision = time.Second
19
+
20
+// MarshalSingleStringAsArray modifies the behavior of the ClaimStrings type,
21
+// especially its MarshalJSON function.
22
+//
23
+// If it is set to true (the default), it will always serialize the type as an
24
+// array of strings, even if it just contains one element, defaulting to the
25
+// behavior of the underlying []string. If it is set to false, it will serialize
26
+// to a single string, if it contains one element. Otherwise, it will serialize
27
+// to an array of strings.
28
+var MarshalSingleStringAsArray = true
29
+
30
+// NumericDate represents a JSON numeric date value, as referenced at
31
+// https://datatracker.ietf.org/doc/html/rfc7519#section-2.
32
+type NumericDate struct {
33
+	time.Time
34
+}
35
+
36
+// NewNumericDate constructs a new *NumericDate from a standard library time.Time struct.
37
+// It will truncate the timestamp according to the precision specified in TimePrecision.
38
+func NewNumericDate(t time.Time) *NumericDate {
39
+	return &NumericDate{t.Truncate(TimePrecision)}
40
+}
41
+
42
+// newNumericDateFromSeconds creates a new *NumericDate out of a float64 representing a
43
+// UNIX epoch with the float fraction representing non-integer seconds.
44
+func newNumericDateFromSeconds(f float64) *NumericDate {
45
+	round, frac := math.Modf(f)
46
+	return NewNumericDate(time.Unix(int64(round), int64(frac*1e9)))
47
+}
48
+
49
+// MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch
50
+// represented in NumericDate to a byte array, using the precision specified in TimePrecision.
51
+func (date NumericDate) MarshalJSON() (b []byte, err error) {
52
+	var prec int
53
+	if TimePrecision < time.Second {
54
+		prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
55
+	}
56
+	truncatedDate := date.Truncate(TimePrecision)
57
+
58
+	// For very large timestamps, UnixNano would overflow an int64, but this
59
+	// function requires nanosecond level precision, so we have to use the
60
+	// following technique to get round the issue:
61
+	//
62
+	// 1. Take the normal unix timestamp to form the whole number part of the
63
+	//    output,
64
+	// 2. Take the result of the Nanosecond function, which returns the offset
65
+	//    within the second of the particular unix time instance, to form the
66
+	//    decimal part of the output
67
+	// 3. Concatenate them to produce the final result
68
+	seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
69
+	nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
70
+
71
+	output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
72
+
73
+	return output, nil
74
+}
75
+
76
+// UnmarshalJSON is an implementation of the json.RawMessage interface and
77
+// deserializes a [NumericDate] from a JSON representation, i.e. a
78
+// [json.Number]. This number represents an UNIX epoch with either integer or
79
+// non-integer seconds.
80
+func (date *NumericDate) UnmarshalJSON(b []byte) (err error) {
81
+	var (
82
+		number json.Number
83
+		f      float64
84
+	)
85
+
86
+	if err = json.Unmarshal(b, &number); err != nil {
87
+		return fmt.Errorf("could not parse NumericData: %w", err)
88
+	}
89
+
90
+	if f, err = number.Float64(); err != nil {
91
+		return fmt.Errorf("could not convert json number value to float: %w", err)
92
+	}
93
+
94
+	n := newNumericDateFromSeconds(f)
95
+	*date = *n
96
+
97
+	return nil
98
+}
99
+
100
+// ClaimStrings is basically just a slice of strings, but it can be either
101
+// serialized from a string array or just a string. This type is necessary,
102
+// since the "aud" claim can either be a single string or an array.
103
+type ClaimStrings []string
104
+
105
+func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
106
+	var value interface{}
107
+
108
+	if err = json.Unmarshal(data, &value); err != nil {
109
+		return err
110
+	}
111
+
112
+	var aud []string
113
+
114
+	switch v := value.(type) {
115
+	case string:
116
+		aud = append(aud, v)
117
+	case []string:
118
+		aud = ClaimStrings(v)
119
+	case []interface{}:
120
+		for _, vv := range v {
121
+			vs, ok := vv.(string)
122
+			if !ok {
123
+				return ErrInvalidType
124
+			}
125
+			aud = append(aud, vs)
126
+		}
127
+	case nil:
128
+		return nil
129
+	default:
130
+		return ErrInvalidType
131
+	}
132
+
133
+	*s = aud
134
+
135
+	return
136
+}
137
+
138
+func (s ClaimStrings) MarshalJSON() (b []byte, err error) {
139
+	// This handles a special case in the JWT RFC. If the string array, e.g.
140
+	// used by the "aud" field, only contains one element, it MAY be serialized
141
+	// as a single string. This may or may not be desired based on the ecosystem
142
+	// of other JWT library used, so we make it configurable by the variable
143
+	// MarshalSingleStringAsArray.
144
+	if len(s) == 1 && !MarshalSingleStringAsArray {
145
+		return json.Marshal(s[0])
146
+	}
147
+
148
+	return json.Marshal([]string(s))
149
+}

+ 316
- 0
vendor/github.com/golang-jwt/jwt/v5/validator.go View File

@@ -0,0 +1,316 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto/subtle"
5
+	"fmt"
6
+	"time"
7
+)
8
+
9
+// ClaimsValidator is an interface that can be implemented by custom claims who
10
+// wish to execute any additional claims validation based on
11
+// application-specific logic. The Validate function is then executed in
12
+// addition to the regular claims validation and any error returned is appended
13
+// to the final validation result.
14
+//
15
+//	type MyCustomClaims struct {
16
+//	    Foo string `json:"foo"`
17
+//	    jwt.RegisteredClaims
18
+//	}
19
+//
20
+//	func (m MyCustomClaims) Validate() error {
21
+//	    if m.Foo != "bar" {
22
+//	        return errors.New("must be foobar")
23
+//	    }
24
+//	    return nil
25
+//	}
26
+type ClaimsValidator interface {
27
+	Claims
28
+	Validate() error
29
+}
30
+
31
+// Validator is the core of the new Validation API. It is automatically used by
32
+// a [Parser] during parsing and can be modified with various parser options.
33
+//
34
+// The [NewValidator] function should be used to create an instance of this
35
+// struct.
36
+type Validator struct {
37
+	// leeway is an optional leeway that can be provided to account for clock skew.
38
+	leeway time.Duration
39
+
40
+	// timeFunc is used to supply the current time that is needed for
41
+	// validation. If unspecified, this defaults to time.Now.
42
+	timeFunc func() time.Time
43
+
44
+	// requireExp specifies whether the exp claim is required
45
+	requireExp bool
46
+
47
+	// verifyIat specifies whether the iat (Issued At) claim will be verified.
48
+	// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
49
+	// only specifies the age of the token, but no validation check is
50
+	// necessary. However, if wanted, it can be checked if the iat is
51
+	// unrealistic, i.e., in the future.
52
+	verifyIat bool
53
+
54
+	// expectedAud contains the audience this token expects. Supplying an empty
55
+	// string will disable aud checking.
56
+	expectedAud string
57
+
58
+	// expectedIss contains the issuer this token expects. Supplying an empty
59
+	// string will disable iss checking.
60
+	expectedIss string
61
+
62
+	// expectedSub contains the subject this token expects. Supplying an empty
63
+	// string will disable sub checking.
64
+	expectedSub string
65
+}
66
+
67
+// NewValidator can be used to create a stand-alone validator with the supplied
68
+// options. This validator can then be used to validate already parsed claims.
69
+//
70
+// Note: Under normal circumstances, explicitly creating a validator is not
71
+// needed and can potentially be dangerous; instead functions of the [Parser]
72
+// class should be used.
73
+//
74
+// The [Validator] is only checking the *validity* of the claims, such as its
75
+// expiration time, but it does NOT perform *signature verification* of the
76
+// token.
77
+func NewValidator(opts ...ParserOption) *Validator {
78
+	p := NewParser(opts...)
79
+	return p.validator
80
+}
81
+
82
+// Validate validates the given claims. It will also perform any custom
83
+// validation if claims implements the [ClaimsValidator] interface.
84
+//
85
+// Note: It will NOT perform any *signature verification* on the token that
86
+// contains the claims and expects that the [Claim] was already successfully
87
+// verified.
88
+func (v *Validator) Validate(claims Claims) error {
89
+	var (
90
+		now  time.Time
91
+		errs []error = make([]error, 0, 6)
92
+		err  error
93
+	)
94
+
95
+	// Check, if we have a time func
96
+	if v.timeFunc != nil {
97
+		now = v.timeFunc()
98
+	} else {
99
+		now = time.Now()
100
+	}
101
+
102
+	// We always need to check the expiration time, but usage of the claim
103
+	// itself is OPTIONAL by default. requireExp overrides this behavior
104
+	// and makes the exp claim mandatory.
105
+	if err = v.verifyExpiresAt(claims, now, v.requireExp); err != nil {
106
+		errs = append(errs, err)
107
+	}
108
+
109
+	// We always need to check not-before, but usage of the claim itself is
110
+	// OPTIONAL.
111
+	if err = v.verifyNotBefore(claims, now, false); err != nil {
112
+		errs = append(errs, err)
113
+	}
114
+
115
+	// Check issued-at if the option is enabled
116
+	if v.verifyIat {
117
+		if err = v.verifyIssuedAt(claims, now, false); err != nil {
118
+			errs = append(errs, err)
119
+		}
120
+	}
121
+
122
+	// If we have an expected audience, we also require the audience claim
123
+	if v.expectedAud != "" {
124
+		if err = v.verifyAudience(claims, v.expectedAud, true); err != nil {
125
+			errs = append(errs, err)
126
+		}
127
+	}
128
+
129
+	// If we have an expected issuer, we also require the issuer claim
130
+	if v.expectedIss != "" {
131
+		if err = v.verifyIssuer(claims, v.expectedIss, true); err != nil {
132
+			errs = append(errs, err)
133
+		}
134
+	}
135
+
136
+	// If we have an expected subject, we also require the subject claim
137
+	if v.expectedSub != "" {
138
+		if err = v.verifySubject(claims, v.expectedSub, true); err != nil {
139
+			errs = append(errs, err)
140
+		}
141
+	}
142
+
143
+	// Finally, we want to give the claim itself some possibility to do some
144
+	// additional custom validation based on a custom Validate function.
145
+	cvt, ok := claims.(ClaimsValidator)
146
+	if ok {
147
+		if err := cvt.Validate(); err != nil {
148
+			errs = append(errs, err)
149
+		}
150
+	}
151
+
152
+	if len(errs) == 0 {
153
+		return nil
154
+	}
155
+
156
+	return joinErrors(errs...)
157
+}
158
+
159
+// verifyExpiresAt compares the exp claim in claims against cmp. This function
160
+// will succeed if cmp < exp. Additional leeway is taken into account.
161
+//
162
+// If exp is not set, it will succeed if the claim is not required,
163
+// otherwise ErrTokenRequiredClaimMissing will be returned.
164
+//
165
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
166
+// the wrong type, an ErrTokenUnverifiable error will be returned.
167
+func (v *Validator) verifyExpiresAt(claims Claims, cmp time.Time, required bool) error {
168
+	exp, err := claims.GetExpirationTime()
169
+	if err != nil {
170
+		return err
171
+	}
172
+
173
+	if exp == nil {
174
+		return errorIfRequired(required, "exp")
175
+	}
176
+
177
+	return errorIfFalse(cmp.Before((exp.Time).Add(+v.leeway)), ErrTokenExpired)
178
+}
179
+
180
+// verifyIssuedAt compares the iat claim in claims against cmp. This function
181
+// will succeed if cmp >= iat. Additional leeway is taken into account.
182
+//
183
+// If iat is not set, it will succeed if the claim is not required,
184
+// otherwise ErrTokenRequiredClaimMissing will be returned.
185
+//
186
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
187
+// the wrong type, an ErrTokenUnverifiable error will be returned.
188
+func (v *Validator) verifyIssuedAt(claims Claims, cmp time.Time, required bool) error {
189
+	iat, err := claims.GetIssuedAt()
190
+	if err != nil {
191
+		return err
192
+	}
193
+
194
+	if iat == nil {
195
+		return errorIfRequired(required, "iat")
196
+	}
197
+
198
+	return errorIfFalse(!cmp.Before(iat.Add(-v.leeway)), ErrTokenUsedBeforeIssued)
199
+}
200
+
201
+// verifyNotBefore compares the nbf claim in claims against cmp. This function
202
+// will return true if cmp >= nbf. Additional leeway is taken into account.
203
+//
204
+// If nbf is not set, it will succeed if the claim is not required,
205
+// otherwise ErrTokenRequiredClaimMissing will be returned.
206
+//
207
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
208
+// the wrong type, an ErrTokenUnverifiable error will be returned.
209
+func (v *Validator) verifyNotBefore(claims Claims, cmp time.Time, required bool) error {
210
+	nbf, err := claims.GetNotBefore()
211
+	if err != nil {
212
+		return err
213
+	}
214
+
215
+	if nbf == nil {
216
+		return errorIfRequired(required, "nbf")
217
+	}
218
+
219
+	return errorIfFalse(!cmp.Before(nbf.Add(-v.leeway)), ErrTokenNotValidYet)
220
+}
221
+
222
+// verifyAudience compares the aud claim against cmp.
223
+//
224
+// If aud is not set or an empty list, it will succeed if the claim is not required,
225
+// otherwise ErrTokenRequiredClaimMissing will be returned.
226
+//
227
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
228
+// the wrong type, an ErrTokenUnverifiable error will be returned.
229
+func (v *Validator) verifyAudience(claims Claims, cmp string, required bool) error {
230
+	aud, err := claims.GetAudience()
231
+	if err != nil {
232
+		return err
233
+	}
234
+
235
+	if len(aud) == 0 {
236
+		return errorIfRequired(required, "aud")
237
+	}
238
+
239
+	// use a var here to keep constant time compare when looping over a number of claims
240
+	result := false
241
+
242
+	var stringClaims string
243
+	for _, a := range aud {
244
+		if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
245
+			result = true
246
+		}
247
+		stringClaims = stringClaims + a
248
+	}
249
+
250
+	// case where "" is sent in one or many aud claims
251
+	if stringClaims == "" {
252
+		return errorIfRequired(required, "aud")
253
+	}
254
+
255
+	return errorIfFalse(result, ErrTokenInvalidAudience)
256
+}
257
+
258
+// verifyIssuer compares the iss claim in claims against cmp.
259
+//
260
+// If iss is not set, it will succeed if the claim is not required,
261
+// otherwise ErrTokenRequiredClaimMissing will be returned.
262
+//
263
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
264
+// the wrong type, an ErrTokenUnverifiable error will be returned.
265
+func (v *Validator) verifyIssuer(claims Claims, cmp string, required bool) error {
266
+	iss, err := claims.GetIssuer()
267
+	if err != nil {
268
+		return err
269
+	}
270
+
271
+	if iss == "" {
272
+		return errorIfRequired(required, "iss")
273
+	}
274
+
275
+	return errorIfFalse(iss == cmp, ErrTokenInvalidIssuer)
276
+}
277
+
278
+// verifySubject compares the sub claim against cmp.
279
+//
280
+// If sub is not set, it will succeed if the claim is not required,
281
+// otherwise ErrTokenRequiredClaimMissing will be returned.
282
+//
283
+// Additionally, if any error occurs while retrieving the claim, e.g., when its
284
+// the wrong type, an ErrTokenUnverifiable error will be returned.
285
+func (v *Validator) verifySubject(claims Claims, cmp string, required bool) error {
286
+	sub, err := claims.GetSubject()
287
+	if err != nil {
288
+		return err
289
+	}
290
+
291
+	if sub == "" {
292
+		return errorIfRequired(required, "sub")
293
+	}
294
+
295
+	return errorIfFalse(sub == cmp, ErrTokenInvalidSubject)
296
+}
297
+
298
+// errorIfFalse returns the error specified in err, if the value is true.
299
+// Otherwise, nil is returned.
300
+func errorIfFalse(value bool, err error) error {
301
+	if value {
302
+		return nil
303
+	} else {
304
+		return err
305
+	}
306
+}
307
+
308
+// errorIfRequired returns an ErrTokenRequiredClaimMissing error if required is
309
+// true. Otherwise, nil is returned.
310
+func errorIfRequired(required bool, claim string) error {
311
+	if required {
312
+		return newError(fmt.Sprintf("%s claim is required", claim), ErrTokenRequiredClaimMissing)
313
+	} else {
314
+		return nil
315
+	}
316
+}

+ 4
- 4
vendor/modules.txt View File

@@ -16,7 +16,7 @@ github.com/ergochat/confusables
16 16
 # github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
17 17
 ## explicit; go 1.18
18 18
 github.com/ergochat/go-ident
19
-# github.com/ergochat/irc-go v0.4.0
19
+# github.com/ergochat/irc-go v0.5.0-rc1
20 20
 ## explicit; go 1.15
21 21
 github.com/ergochat/irc-go/ircfmt
22 22
 github.com/ergochat/irc-go/ircmsg
@@ -30,9 +30,9 @@ github.com/go-sql-driver/mysql
30 30
 # github.com/gofrs/flock v0.8.1
31 31
 ## explicit
32 32
 github.com/gofrs/flock
33
-# github.com/golang-jwt/jwt v3.2.2+incompatible
34
-## explicit
35
-github.com/golang-jwt/jwt
33
+# github.com/golang-jwt/jwt/v5 v5.2.0
34
+## explicit; go 1.18
35
+github.com/golang-jwt/jwt/v5
36 36
 # github.com/gorilla/websocket v1.4.2 => github.com/ergochat/websocket v1.4.2-oragono1
37 37
 ## explicit; go 1.12
38 38
 github.com/gorilla/websocket

Loading…
Cancel
Save