v0.4.1 merge http and simple token into one file, workflow update
Test and Fuzz / Build, Test, and Fuzz (push) Canceled after 0s
Test and Fuzz / Build, Test, and Fuzz (push) Canceled after 0s
This commit is contained in:
1 parent
2bf8715e93
commit
5cf0a93676
6 files changed
+139
-250
No files matched your search
+55
-16
@@ -1,27 +1,66 @@
|
|||||||
name: test
|
name: Test and Fuzz
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [ main, master, develop, 'claude/*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches: [ main, master, develop ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
name: Build, Test, and Fuzz
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- name: Checkout code
|
||||||
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
go-version: '1.26.x'
|
- name: Set up Go
|
||||||
- run: go vet ./...
|
uses: actions/setup-go@v5
|
||||||
- run: go test -race -count=1 ./...
|
with:
|
||||||
- name: Bounded fuzz smoke tests
|
go-version: '1.27.1'
|
||||||
env:
|
cache: true
|
||||||
GOMAXPROCS: '2'
|
|
||||||
run: |
|
- name: Download dependencies
|
||||||
set -eu
|
run: go mod download
|
||||||
for target in FuzzParsePHC FuzzVerifyPassword FuzzImportCredential FuzzValidateToken FuzzParseBearerToken FuzzScramServerMessages FuzzScramClientMessages; do
|
|
||||||
go test -run '^$' -fuzz "^${target}$" -fuzztime=10s -parallel=2
|
- name: Vet code
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Build code
|
||||||
|
run: go build ./...
|
||||||
|
|
||||||
|
- name: Run tests with race detector
|
||||||
|
run: go test -race -v -count=1 ./...
|
||||||
|
env:
|
||||||
|
# Halt immediately on race condition and output logs to files prefixed with "race"
|
||||||
|
GORACE: "halt_on_error=1 log_path=race"
|
||||||
|
|
||||||
|
- name: Upload race detector logs on failure
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: race-detector-logs
|
||||||
|
path: race.*
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Dynamic Bounded Fuzz Smoke Tests
|
||||||
|
env:
|
||||||
|
GOMAXPROCS: '2'
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
# Loop over all packages in the module
|
||||||
|
go list ./... | while read -r pkg; do
|
||||||
|
# Dynamically find all fuzz targets in the current package
|
||||||
|
targets=$(go test -list '^Fuzz' "$pkg" | grep '^Fuzz' || true)
|
||||||
|
|
||||||
|
for target in $targets; do
|
||||||
|
echo "Running fuzz test: $target in $pkg"
|
||||||
|
go test -run '^$' -fuzz "^${target}$" -fuzztime=10s -parallel=2 "$pkg"
|
||||||
done
|
done
|
||||||
|
done
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
# Security audit and integration notes
|
|
||||||
|
|
||||||
Audit date: 2026-09-23. Baseline: `e4eb41658be8b1d4f46cb695837ebfe564cd2f25`.
|
|
||||||
Scope: all production code and tests in `lixenwraith/auth`, plus current auth
|
|
||||||
call sites in chess and planned use in vi-fighter/logwisp. This is a source audit
|
|
||||||
with regression, race, and bounded fuzz testing, not a formal protocol proof or
|
|
||||||
a production penetration test.
|
|
||||||
|
|
||||||
## Findings and corrections
|
|
||||||
|
|
||||||
| Area | Finding | Correction |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| SCRAM replay | Verification cleared its atomic claim before deleting the handshake. A concurrent request could claim and successfully verify the same proof again. | Retain the claim through deletion; regression requires exactly one success under concurrent replay. |
|
|
||||||
| SCRAM client | Challenges did not need an active handshake or a nonce that extends the client's nonce. Restart retained old authentication material; final messages were reusable. | Explicit ordered states, nonce binding, one-use challenges/finals, terminal errors, and reset on restart. Recheck exported password length immediately before the KDF. |
|
|
||||||
| KDF downgrade | A hostile server could choose a tiny but valid KDF and collect a cheaply guessable client proof. | Clients default to a minimum of 64 MiB/3 iterations; an explicit local policy option supports trusted alternate profiles. |
|
|
||||||
| Resource exhaustion | Username/nonce/salt/proof inputs could allocate or retain unbounded data. Direct derivation and hash options could request unbounded Argon2 work; separate maxima allowed costly combinations. | Pre-decode byte limits, credential validation, common KDF limits at every entry point, and a combined memory×iterations ceiling. Retain the existing handshake-count cap and timeout. |
|
|
||||||
| Credential ownership | Server retained caller-owned credential pointers/slices without validation. Mutation could change challenges or keys, or race authentication. There was no account-removal API. | Validate and deep-copy credentials; add `Credential.Validate`, error-returning `AddCredential`, and `RemoveCredential`. Replacement/removal invalidates pending handshakes; successful final verification checks the active credential. |
|
|
||||||
| Account enumeration | Decoy salts changed on restart/replica boundaries. Last-added credential determined the unknown-user profile, allowing mixed profiles to disclose existence. | Add a constructor with a persistent shared decoy key; enforce one profile per server. Perform decoy HMAC work on known-user requests too. Document residual timing/application signals and load-before-serving requirements. |
|
|
||||||
| PHC migration | The common 32-byte digest branch admitted short salts that SCRAM import rejected. | Validate SCRAM salt length on both migration paths; reject the record consistently. Existing PHC verification remains available for legacy salts. |
|
|
||||||
| JWT policy | Signed tokens without a subject were accepted; future issued-at values were ignored. Unbounded token parsing and noncanonical signature encodings were accepted. | Require a nonempty subject and expiry; check issued-at; bound tokens; reject CR/LF, nonzero base64 pad bits, and unsupported critical headers. Keep algorithm pinning and configured issuer/audience checks. |
|
|
||||||
| Key/config ownership | HMAC secrets, RSA keys, and audience slices were borrowed. Invalid/weak RSA keys were not checked at construction. | Copy constructor inputs; validate RSA private material/public parameters and require at least 2048 bits. Accept PKCS#1 public PEM as well as PKIX. |
|
|
||||||
| Opaque tokens / HTTP | The allowlist could authenticate an explicitly stored empty token and its zero value panicked on insertion. Bearer parsing accepted embedded whitespace and required case-sensitive schemes. | Reject empty/oversized tokens, support the zero value, and enforce RFC 6750 Bearer syntax. Remove the unused password-header parser, its errors/tests/docs, and the generic scheme extractor. |
|
|
||||||
|
|
||||||
Canonical base64 checks also cover PHC records and imported SCRAM credentials.
|
|
||||||
Import accepts common JSON, YAML, and TOML numeric representations (`float64`,
|
|
||||||
`json.Number`, `int`, `int64`, unsigned integers) with exact range checks. Explicit
|
|
||||||
regressions reject NaN, infinities, fractions, negatives, and overflow. Transient
|
|
||||||
key buffers are cleared where practical; this is not a guarantee of memory erasure.
|
|
||||||
|
|
||||||
## Compatibility and rollout
|
|
||||||
|
|
||||||
Chess at `e3e465c9a2a81e4f5904a0e646c9812caa071c31` uses `HashPassword`,
|
|
||||||
`VerifyPassword`, `ValidatePHCHashFormat`, `GenerateHS256Token`, and
|
|
||||||
`ValidateHS256Token`. These signatures and JWT `extra` structure remain intact.
|
|
||||||
The one-off validator is a compatibility adapter to the common hardened parser;
|
|
||||||
it retains the existing five-minute leeway and lack of issuer/audience constraints.
|
|
||||||
Existing well-formed chess tokens remain valid. Its service tests and CLI package
|
|
||||||
were checked against this checkout using a temporary module replacement; no
|
|
||||||
changes to chess are included in this PR.
|
|
||||||
|
|
||||||
The inspected vi-fighter (`6fd727f848ac05ff6b76b85c8152aad995d9426e`) and logwisp
|
|
||||||
(`72c0a43c9f2ba017bf2fd62319077b3655ee4514`) revisions do not yet import auth.
|
|
||||||
Their future deployment integration is outside this package PR.
|
|
||||||
|
|
||||||
SCRAM message JSON fields and the valid transcript encoding stay unchanged.
|
|
||||||
`NewScramClient` accepts optional local minimum-cost policy options; ordinary
|
|
||||||
two-argument calls retain their syntax, with stronger default challenge policy.
|
|
||||||
`AddCredential` now returns an error; callers should check it. Calls used as
|
|
||||||
statements still compile, but interfaces requiring the old no-result signature
|
|
||||||
must be updated (none occur in the inspected consumers). Intentionally rejected
|
|
||||||
inputs include malformed identities/nonces, mixed SCRAM profiles, noncanonical
|
|
||||||
base64, oversized tokens, and KDF parameters above the new budget. Short-salt PHC
|
|
||||||
records must be re-enrolled before SCRAM migration. A server's profile remains
|
|
||||||
fixed after removing its last credential to keep decoy responses stable.
|
|
||||||
|
|
||||||
### Follow-up: legacy HS256 adapter removal (v0.5.0, 2026-09-26)
|
|
||||||
|
|
||||||
Chess was the only consumer of `GenerateHS256Token` and `ValidateHS256Token`.
|
|
||||||
It now constructs a scoped `NewJWT` manager (issuer `chess-server`, audience
|
|
||||||
`chess-api`, lifetime equal to its session TTL, zero leeway) and parses headers
|
|
||||||
with `ParseBearerToken`. The adapters, their shared unscoped parser, their tests,
|
|
||||||
and the `FuzzValidateHS256Token` target were removed; `FuzzValidateToken` now
|
|
||||||
fuzzes a scoped manager. The unused `ErrSaltGenerationFailed` was also removed:
|
|
||||||
`crypto/rand.Read` cannot return an error on supported Go versions, so no path
|
|
||||||
produced it. `HashPassword`, `VerifyPassword`, and `ValidatePHCHashFormat` are
|
|
||||||
general password APIs and remain unchanged.
|
|
||||||
|
|
||||||
Removing the adapters is a breaking API change for any out-of-tree caller. The
|
|
||||||
five-minute default leeway of `NewJWT` itself is unchanged for compatibility;
|
|
||||||
services should still set `WithLeeway` explicitly.
|
|
||||||
|
|
||||||
## Integration boundaries
|
|
||||||
|
|
||||||
- **Transport:** this Argon2/JSON protocol is not standard SASL SCRAM-SHA-256 and
|
|
||||||
has no channel binding. Authenticated TLS/WSS and connection-bound exchange
|
|
||||||
handling belong to the caller. Always verify the server final before trusting
|
|
||||||
the login result or a token delivered with it.
|
|
||||||
- **Registration/storage:** generate salts with `NewCredential`; protect both
|
|
||||||
credential keys and the shared decoy secret. Do not retain the old PHC salted
|
|
||||||
password after migration unless a separate authentication path requires it.
|
|
||||||
- **Denial of service:** enforce request/frame limits before JSON decoding,
|
|
||||||
deadlines, rate limits, and a RAM-appropriate cap on simultaneous KDF calls.
|
|
||||||
Package ceilings are per operation. Credential count and opaque-token count
|
|
||||||
are application-controlled and are not capped by the package.
|
|
||||||
- **Authorization/revocation:** scope JWT issuer/audience to the receiving
|
|
||||||
service. HS256 verifiers can issue tokens; use RS256 when services should only
|
|
||||||
verify. Check session state when logout/rotation needs immediate revocation.
|
|
||||||
Removing a SCRAM credential cannot retract sessions already issued by the app.
|
|
||||||
- **Privacy:** map authentication failures to suitable public responses; avoid
|
|
||||||
logging passwords, proofs, keys, or bearer tokens. The decoy mechanism reduces
|
|
||||||
enumeration signals but does not promise constant-time network behavior.
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
`NewCredential` performs one Argon2 derivation; provisioning through
|
|
||||||
`HashPassword` followed by `MigrateFromPHC` performs two. Existing PHC migration
|
|
||||||
continues to reuse its verification KDF for the normal 32-byte digest.
|
|
||||||
|
|
||||||
JWT managers now construct their immutable parser once. No password-strength
|
|
||||||
defaults were reduced. RSA key validation/copying occurs at construction. The server's proof path performs
|
|
||||||
bounded hash/HMAC operations, with no Argon2 work.
|
|
||||||
|
|
||||||
On Go 1.27.1/linux-amd64, three local benchmark samples measured direct
|
|
||||||
provisioning at 6.61–7.54 ms and ~8.39 MB allocated, versus 13.10–13.72 ms and
|
|
||||||
~16.79 MB for hash-then-migrate (8 MiB/1-iteration **test** profile). JWT
|
|
||||||
sign-plus-verify allocations dropped from 88 to 85 per operation (~5133 to
|
|
||||||
4973 bytes); median time was 14.67 µs before and 13.73 µs after. These short local
|
|
||||||
samples are indicative, not a production capacity claim.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Local validation on Go 1.27.1/linux-amd64 passed `go vet ./...` and
|
|
||||||
`go test -race -count=1 -coverprofile=... ./...`, with **98.1% statement coverage**.
|
|
||||||
All seven fuzz targets passed 10-second, two-worker campaigns (over 1.8 million
|
|
||||||
executions combined); the client target was rerun after adding minimum-cost
|
|
||||||
policy. These are smoke campaigns, not exhaustive fuzzing. Chess's service tests
|
|
||||||
and CLI compilation also passed after the dependency update.
|
|
||||||
|
|
||||||
`govulncheck v1.8.0` reported zero vulnerabilities in reachable calls or imported
|
|
||||||
packages. Its only remaining module-level advisory is **GO-2026-5932** for
|
|
||||||
`golang.org/x/crypto/openpgp`, which this package does not import and which has no
|
|
||||||
fixed release. The scan used Go 1.27.1; consumers must also keep their Go runtime
|
|
||||||
patched.
|
|
||||||
|
|
||||||
The test suite includes concurrent replay, registration/removal, credential ownership,
|
|
||||||
protocol sequencing, resource ceilings, canonical encoding, RSA validation,
|
|
||||||
JWT trust scopes, and the compiled end-to-end example. Seven fuzz targets cover
|
|
||||||
PHC parsing/verification, credential import, JWT verification, Bearer parsing,
|
|
||||||
and both SCRAM peer inputs. CI runs vet, race tests, and bounded fuzz smoke tests.
|
|
||||||
|
|
||||||
Dependencies were updated to `golang.org/x/crypto v0.57.0` and its
|
|
||||||
`golang.org/x/sys v0.48.0` requirement without raising Go 1.26. The Argon2 source
|
|
||||||
is unchanged from v0.54.0. The update includes fixes for SSH advisories
|
|
||||||
GO-2026-6303, GO-2026-6354, and GO-2026-6355 in unused sibling packages.
|
|
||||||
|
|
||||||
## Primary references
|
|
||||||
|
|
||||||
- [RFC 5802, SCRAM exchange and security considerations](https://www.rfc-editor.org/rfc/rfc5802.html)
|
|
||||||
- [RFC 9106, Argon2 inputs and recommended parameters](https://www.rfc-editor.org/rfc/rfc9106.html)
|
|
||||||
- [RFC 8725, JWT best current practices](https://www.rfc-editor.org/rfc/rfc8725.html)
|
|
||||||
- [RFC 6750, Bearer token syntax and transport](https://www.rfc-editor.org/rfc/rfc6750.html)
|
|
||||||
- [Go vulnerability database](https://vuln.go.dev/)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// ParseBearerToken parses an RFC 6750 Bearer authorization value. The scheme is
|
|
||||||
// case-insensitive; tokens must use the b64token alphabet and contain no spaces.
|
|
||||||
// Parsing does not validate the token's signature or grant authorization.
|
|
||||||
func ParseBearerToken(header string) (string, error) {
|
|
||||||
// Bound before scanning, including an allowance for separator spaces.
|
|
||||||
if len(header) > MaxTokenLen+16 {
|
|
||||||
return "", ErrTokenTooLong
|
|
||||||
}
|
|
||||||
scheme, token, ok := strings.Cut(header, " ")
|
|
||||||
if !ok || !strings.EqualFold(scheme, "Bearer") {
|
|
||||||
return "", ErrAuthInvalidBearerFormat
|
|
||||||
}
|
|
||||||
token = strings.TrimLeft(token, " ")
|
|
||||||
if token == "" {
|
|
||||||
return "", ErrAuthEmptyBearerToken
|
|
||||||
}
|
|
||||||
if len(token) > MaxTokenLen {
|
|
||||||
return "", ErrTokenTooLong
|
|
||||||
}
|
|
||||||
padding := false
|
|
||||||
for i, ch := range []byte(token) {
|
|
||||||
if ch == '=' && i > 0 {
|
|
||||||
padding = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if padding || !(ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || strings.ContainsRune("-._~+/", rune(ch))) {
|
|
||||||
return "", ErrAuthInvalidBearerFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseBearerToken(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
header, token string
|
|
||||||
want error
|
|
||||||
}{
|
|
||||||
{"Bearer abc.def-_/+~==", "abc.def-_/+~==", nil},
|
|
||||||
{"bEaReR token", "token", nil},
|
|
||||||
{"Bearer ", "", ErrAuthEmptyBearerToken},
|
|
||||||
{"Bearer ", "", ErrAuthEmptyBearerToken},
|
|
||||||
{"Bearer", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer x y", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer x ", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer x\r\n", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer x\ty", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer =", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer a=b", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Other x", "", ErrAuthInvalidBearerFormat},
|
|
||||||
{"Bearer " + strings.Repeat("x", MaxTokenLen+1), "", ErrTokenTooLong},
|
|
||||||
} {
|
|
||||||
token, err := ParseBearerToken(tc.header)
|
|
||||||
if tc.want != nil {
|
|
||||||
errIs(t, err, tc.want, tc.header)
|
|
||||||
} else {
|
|
||||||
noErr(t, err, tc.header)
|
|
||||||
}
|
|
||||||
eq(t, token, tc.token, "token")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzParseBearerToken(f *testing.F) {
|
|
||||||
for _, seed := range []string{"Bearer token", "bearer abc+/==", "Bearer x\n", "Bearer a=b", ""} {
|
|
||||||
f.Add(seed)
|
|
||||||
}
|
|
||||||
f.Fuzz(func(t *testing.T, header string) {
|
|
||||||
token, err := ParseBearerToken(header)
|
|
||||||
if err != nil {
|
|
||||||
if token != "" {
|
|
||||||
t.Fatal("token with error")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if token == "" || len(token) > MaxTokenLen || strings.ContainsAny(token, " \t\r\n") {
|
|
||||||
t.Fatal("invalid token accepted")
|
|
||||||
}
|
|
||||||
again, err := ParseBearerToken("Bearer " + token)
|
|
||||||
if err != nil || again != token {
|
|
||||||
t.Fatal("noncanonical roundtrip")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,3 +58,35 @@ func (v *SimpleTokenValidator) RemoveToken(token string) {
|
|||||||
defer v.mu.Unlock()
|
defer v.mu.Unlock()
|
||||||
delete(v.tokens, h)
|
delete(v.tokens, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseBearerToken parses an RFC 6750 Bearer authorization value. The scheme is
|
||||||
|
// case-insensitive; tokens must use the b64token alphabet and contain no spaces.
|
||||||
|
// Parsing does not validate the token's signature or grant authorization.
|
||||||
|
func ParseBearerToken(header string) (string, error) {
|
||||||
|
// Bound before scanning, including an allowance for separator spaces.
|
||||||
|
if len(header) > MaxTokenLen+16 {
|
||||||
|
return "", ErrTokenTooLong
|
||||||
|
}
|
||||||
|
scheme, token, ok := strings.Cut(header, " ")
|
||||||
|
if !ok || !strings.EqualFold(scheme, "Bearer") {
|
||||||
|
return "", ErrAuthInvalidBearerFormat
|
||||||
|
}
|
||||||
|
token = strings.TrimLeft(token, " ")
|
||||||
|
if token == "" {
|
||||||
|
return "", ErrAuthEmptyBearerToken
|
||||||
|
}
|
||||||
|
if len(token) > MaxTokenLen {
|
||||||
|
return "", ErrTokenTooLong
|
||||||
|
}
|
||||||
|
padding := false
|
||||||
|
for i, ch := range []byte(token) {
|
||||||
|
if ch == '=' && i > 0 {
|
||||||
|
padding = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if padding || !(ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || strings.ContainsRune("-._~+/", rune(ch))) {
|
||||||
|
return "", ErrAuthInvalidBearerFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
@@ -96,3 +96,54 @@ func TestSimpleTokenValidatorConcurrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
eq(t, len(v.tokens), n/2, "surviving entries")
|
eq(t, len(v.tokens), n/2, "surviving entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseBearerToken(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
header, token string
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{"Bearer abc.def-_/+~==", "abc.def-_/+~==", nil},
|
||||||
|
{"bEaReR token", "token", nil},
|
||||||
|
{"Bearer ", "", ErrAuthEmptyBearerToken},
|
||||||
|
{"Bearer ", "", ErrAuthEmptyBearerToken},
|
||||||
|
{"Bearer", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer x y", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer x ", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer x\r\n", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer x\ty", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer =", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer a=b", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Other x", "", ErrAuthInvalidBearerFormat},
|
||||||
|
{"Bearer " + strings.Repeat("x", MaxTokenLen+1), "", ErrTokenTooLong},
|
||||||
|
} {
|
||||||
|
token, err := ParseBearerToken(tc.header)
|
||||||
|
if tc.want != nil {
|
||||||
|
errIs(t, err, tc.want, tc.header)
|
||||||
|
} else {
|
||||||
|
noErr(t, err, tc.header)
|
||||||
|
}
|
||||||
|
eq(t, token, tc.token, "token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzParseBearerToken(f *testing.F) {
|
||||||
|
for _, seed := range []string{"Bearer token", "bearer abc+/==", "Bearer x\n", "Bearer a=b", ""} {
|
||||||
|
f.Add(seed)
|
||||||
|
}
|
||||||
|
f.Fuzz(func(t *testing.T, header string) {
|
||||||
|
token, err := ParseBearerToken(header)
|
||||||
|
if err != nil {
|
||||||
|
if token != "" {
|
||||||
|
t.Fatal("token with error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == "" || len(token) > MaxTokenLen || strings.ContainsAny(token, " \t\r\n") {
|
||||||
|
t.Fatal("invalid token accepted")
|
||||||
|
}
|
||||||
|
again, err := ParseBearerToken("Bearer " + token)
|
||||||
|
if err != nil || again != token {
|
||||||
|
t.Fatal("noncanonical roundtrip")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user