challenges: prevent unbounded growth of stored cookies by bundling all state onto a single JWT token

This commit is contained in:
WeebDataHoarder
2025-05-03 17:30:39 +02:00
parent 2cb5972371
commit 0e62f80f9b
19 changed files with 273 additions and 177 deletions

View File

@@ -29,6 +29,7 @@ func (a Block) Handle(logger *slog.Logger, w http.ResponseWriter, r *http.Reques
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Connection", "close")
data.ResponseHeaders(w)
w.WriteHeader(a.Code)
_, _ = w.Write([]byte(fmt.Errorf("access blocked: blocked by administrative rule %s/%s", data.Id.String(), a.RuleHash).Error()))

View File

@@ -42,6 +42,8 @@ type CodeSettings struct {
type Code int
func (a Code) Handle(logger *slog.Logger, w http.ResponseWriter, r *http.Request, done func() (backend http.Handler)) (next bool, err error) {
challenge.RequestDataFromContext(r.Context()).ResponseHeaders(w)
w.WriteHeader(int(a))
return false, nil
}

View File

@@ -2,7 +2,6 @@ package cookie
import (
"git.gammaspectra.live/git/go-away/lib/challenge"
"git.gammaspectra.live/git/go-away/utils"
"github.com/goccy/go-yaml/ast"
"net/http"
"time"
@@ -18,18 +17,15 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
reg.Class = challenge.ClassBlocking
reg.IssueChallenge = func(w http.ResponseWriter, r *http.Request, key challenge.Key, expiry time.Time) challenge.VerifyResult {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, nil, expiry, true)
if err != nil {
return challenge.VerifyResultFail
}
utils.SetCookie(challenge.RequestDataFromContext(r.Context()).CookiePrefix+reg.Name, token, expiry, w, r)
data := challenge.RequestDataFromContext(r.Context())
data.IssueChallengeToken(reg, key, nil, expiry, true)
uri, err := challenge.RedirectUrl(r, reg)
if err != nil {
return challenge.VerifyResultFail
}
data.ResponseHeaders(w)
http.Redirect(w, r, uri.String(), http.StatusTemporaryRedirect)
return challenge.VerifyResultNone
}

View File

@@ -1,6 +1,7 @@
package challenge
import (
"bytes"
http_cel "codeberg.org/gone/http-cel"
"context"
"crypto/rand"
@@ -9,10 +10,13 @@ import (
"errors"
"fmt"
"git.gammaspectra.live/git/go-away/utils"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/traits"
"maps"
unsaferand "math/rand/v2"
"net/http"
"net/netip"
"net/textproto"
@@ -38,13 +42,17 @@ func (id RequestId) String() string {
}
type RequestData struct {
Id RequestId
Time time.Time
ChallengeVerify map[Id]VerifyResult
ChallengeState map[Id]VerifyState
Id RequestId
Time time.Time
ChallengeVerify map[Id]VerifyResult
ChallengeState map[Id]VerifyState
ChallengeMap TokenChallengeMap
challengeMapModified bool
RemoteAddress netip.AddrPort
State StateInterface
CookiePrefix string
cookieName string
issuedChallenge string
ExtraHeaders http.Header
@@ -84,6 +92,11 @@ func CreateRequestData(r *http.Request, state StateInterface) (*http.Request, *R
}
q := r.URL.Query()
if q.Has(QueryArgChallenge) {
data.issuedChallenge = q.Get(QueryArgChallenge)
}
// delete query parameters that were set by go-away
for k := range q {
if strings.HasPrefix(k, QueryArgPrefix) {
@@ -95,19 +108,12 @@ func CreateRequestData(r *http.Request, state StateInterface) (*http.Request, *R
data.header = http_cel.NewMIMEMap(textproto.MIMEHeader(r.Header))
data.opts = make(map[string]string)
sum := sha256.New()
sum.Write([]byte(r.Host))
sum.Write([]byte{0})
sum.Write(data.NetworkPrefix().AsSlice())
sum.Write([]byte{0})
sum.Write(state.PublicKey())
sum.Write([]byte{0})
data.CookiePrefix = utils.CookiePrefix + hex.EncodeToString(sum.Sum(nil)[:6]) + "-"
r = r.WithContext(context.WithValue(r.Context(), requestDataContextKey{}, &data))
r = utils.SetRemoteAddress(r, data.RemoteAddress)
data.r = r
data.cookieName = utils.DefaultCookiePrefix + hex.EncodeToString(data.cookieHostKey()) + "-state"
return r, &data
}
@@ -194,18 +200,70 @@ func (d *RequestData) BackendHost() (http.Handler, string) {
return d.State.GetBackend(host), host
}
func (d *RequestData) EvaluateChallenges(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
var issuedChallenge string
if q.Has(QueryArgChallenge) {
issuedChallenge = q.Get(QueryArgChallenge)
func (d *RequestData) ClearChallengeToken(reg *Registration) {
delete(d.ChallengeMap, reg.Name)
d.challengeMapModified = true
}
func (d *RequestData) IssueChallengeToken(reg *Registration, key Key, result []byte, until time.Time, ok bool) {
d.ChallengeMap[reg.Name] = TokenChallenge{
Key: key[:],
Result: result,
Ok: ok,
Expiry: jwt.NumericDate(until.Unix()),
IssuedAt: jwt.NumericDate(time.Now().UTC().Unix()),
}
for _, reg := range d.State.GetChallenges() {
key := GetChallengeKeyForRequest(d.State, reg, d.Expiration(reg.Duration), r)
verifyResult, verifyState, err := reg.VerifyChallengeToken(d.State.PublicKey(), key, r)
d.challengeMapModified = true
}
var ErrVerifyKeyMismatch = errors.New("verify: key mismatch")
var ErrVerifyVerifyMismatch = errors.New("verify: verification mismatch")
var ErrTokenExpired = errors.New("token: expired")
func (d *RequestData) VerifyChallengeToken(reg *Registration, token TokenChallenge, expectedKey Key) (VerifyResult, VerifyState, error) {
if token.Expiry.Time().Compare(time.Now()) < 0 {
return VerifyResultFail, VerifyStateNone, ErrTokenExpired
}
if token.NotBefore.Time().Compare(time.Now()) > 0 {
return VerifyResultFail, VerifyStateNone, errors.New("token not valid yet")
}
if bytes.Compare(expectedKey[:], token.Key) != 0 {
return VerifyResultFail, VerifyStateNone, ErrVerifyKeyMismatch
}
if reg.Verify != nil {
if unsaferand.Float64() < reg.VerifyProbability {
// random spot check
if ok, err := reg.Verify(expectedKey, token.Result, d.r); err != nil {
return VerifyResultFail, VerifyStateFull, err
} else if ok == VerifyResultNotOK {
return VerifyResultNotOK, VerifyStateFull, nil
} else if !ok.Ok() {
return ok, VerifyStateFull, ErrVerifyVerifyMismatch
} else {
return ok, VerifyStateFull, nil
}
}
}
if !token.Ok {
return VerifyResultNotOK, VerifyStateBrief, nil
}
return VerifyResultOK, VerifyStateBrief, nil
}
func (d *RequestData) verifyChallenge(reg *Registration, key Key) (verifyResult VerifyResult, verifyState VerifyState, err error) {
token, ok := d.ChallengeMap[reg.Name]
if !ok {
verifyResult = VerifyResultFail
verifyState = VerifyStateNone
} else {
verifyResult, verifyState, err = d.VerifyChallengeToken(reg, token, key)
if err != nil && !errors.Is(err, http.ErrNoCookie) {
// clear invalid cookie
utils.ClearCookie(d.CookiePrefix+reg.Name, w, r)
// clear invalid state
d.ClearChallengeToken(reg)
}
// prevent evaluating the challenge if not solved
@@ -213,20 +271,46 @@ func (d *RequestData) EvaluateChallenges(w http.ResponseWriter, r *http.Request)
out, _, err := reg.Condition.Eval(d)
// verify eligibility
if err != nil {
d.State.Logger(r).Error(err.Error(), "challenge", reg.Name)
d.State.Logger(d.r).Error(err.Error(), "challenge", reg.Name)
} else if out != nil && out.Type() == types.BoolType {
if out.Equal(types.True) != types.True {
// skip challenge match due to precondition!
verifyResult = VerifyResultSkip
continue
return verifyResult, verifyState, err
}
}
}
}
if !verifyResult.Ok() && issuedChallenge == reg.Name {
// we issued the challenge, must skip to prevent loops
verifyResult = VerifyResultSkip
if !verifyResult.Ok() && d.issuedChallenge == reg.Name {
// we issued the challenge, must skip to prevent loops
verifyResult = VerifyResultSkip
}
return verifyResult, verifyState, err
}
func (d *RequestData) EvaluateChallenges(w http.ResponseWriter, r *http.Request) {
challengeMap, err := d.verifyChallengeState()
if err != nil {
if !errors.Is(err, http.ErrNoCookie) {
//clear invalid cookie and continue
utils.ClearCookie(d.cookieName, w, r)
}
challengeMap = make(TokenChallengeMap)
}
d.ChallengeMap = challengeMap
for _, reg := range d.State.GetChallenges() {
key := GetChallengeKeyForRequest(d.State, reg, d.Expiration(reg.Duration), r)
verifyResult, verifyState, err := d.verifyChallenge(reg, key)
if err != nil {
// clear invalid state
d.ClearChallengeToken(reg)
}
d.ChallengeVerify[reg.Id()] = verifyResult
d.ChallengeState[reg.Id()] = verifyState
}
@@ -240,13 +324,22 @@ func (d *RequestData) HasValidChallenge(id Id) bool {
return d.ChallengeVerify[id].Ok()
}
func (d *RequestData) ResponseHeaders(headers http.Header) {
func (d *RequestData) ResponseHeaders(w http.ResponseWriter) {
// send these to client so we consistently get the headers
//w.Header().Set("Accept-CH", "Sec-CH-UA, Sec-CH-UA-Platform")
//w.Header().Set("Critical-CH", "Sec-CH-UA, Sec-CH-UA-Platform")
if d.State.Settings().MainName != "" {
headers.Add("Via", fmt.Sprintf("%s %s@%s", d.r.Proto, d.State.Settings().MainName, d.State.Settings().MainVersion))
w.Header().Add("Via", fmt.Sprintf("%s %s@%s", d.r.Proto, d.State.Settings().MainName, d.State.Settings().MainVersion))
}
if d.challengeMapModified {
expiration := d.Expiration(DefaultDuration)
if token, err := d.issueChallengeState(expiration); err == nil {
utils.SetCookie(d.cookieName, token, expiration, w, d.r)
} else {
d.State.Logger(d.r).Error("error while issuing cookie", "error", err)
}
}
}
@@ -282,3 +375,111 @@ func (d *RequestData) RequestHeaders(headers http.Header) {
maps.Copy(headers, d.ExtraHeaders)
}
type Token struct {
State TokenChallengeMap `json:"state"`
Expiry jwt.NumericDate `json:"exp,omitempty"`
NotBefore jwt.NumericDate `json:"nbf,omitempty"`
IssuedAt jwt.NumericDate `json:"iat,omitempty"`
}
type TokenChallengeMap map[string]TokenChallenge
type TokenChallenge struct {
Key []byte `json:"key"`
Result []byte `json:"result,omitempty"`
Ok bool `json:"ok"`
Expiry jwt.NumericDate `json:"exp,omitempty"`
NotBefore jwt.NumericDate `json:"nbf,omitempty"`
IssuedAt jwt.NumericDate `json:"iat,omitempty"`
}
func (d *RequestData) verifyChallengeState() (TokenChallengeMap, error) {
cookie, err := d.r.Cookie(d.cookieName)
if err != nil {
return nil, err
}
if cookie == nil {
return nil, http.ErrNoCookie
}
encryptedToken, err := jwt.ParseSignedAndEncrypted(cookie.Value,
[]jose.KeyAlgorithm{jose.DIRECT},
[]jose.ContentEncryption{jose.A256GCM},
[]jose.SignatureAlgorithm{jose.EdDSA},
)
if err != nil {
return nil, err
}
signedToken, err := encryptedToken.Decrypt(d.cookieKey())
if err != nil {
return nil, err
}
var i Token
err = signedToken.Claims(d.State.PublicKey(), &i)
if err != nil {
return nil, err
}
if i.Expiry.Time().Compare(time.Now()) < 0 {
return nil, ErrTokenExpired
}
if i.NotBefore.Time().Compare(time.Now()) > 0 {
return nil, errors.New("token not valid yet")
}
return i.State, nil
}
func (d *RequestData) issueChallengeState(until time.Time) (string, error) {
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.EdDSA,
Key: d.State.PrivateKey(),
}, nil)
if err != nil {
return "", err
}
encrypter, err := jose.NewEncrypter(jose.A256GCM, jose.Recipient{
Algorithm: jose.DIRECT,
Key: d.cookieKey(),
}, (&jose.EncrypterOptions{
Compression: jose.DEFLATE,
}).WithContentType("JWT"))
if err != nil {
return "", err
}
return jwt.SignedAndEncrypted(signer, encrypter).Claims(Token{
State: d.ChallengeMap,
Expiry: jwt.NumericDate(until.Unix()),
NotBefore: jwt.NumericDate(time.Now().UTC().AddDate(0, 0, -1).Unix()),
IssuedAt: jwt.NumericDate(time.Now().UTC().Unix()),
}).Serialize()
}
func (d *RequestData) cookieKey() []byte {
sum := sha256.New()
sum.Write([]byte(d.r.Host))
sum.Write([]byte{0})
sum.Write(d.NetworkPrefix().AsSlice())
sum.Write([]byte{0})
sum.Write(d.State.PrivateKey())
sum.Write([]byte{0})
// version/compressor
sum.Write([]byte("1.0/DEFLATE"))
sum.Write([]byte{0})
return sum.Sum(nil)
}
func (d *RequestData) cookieHostKey() []byte {
sum := sha256.New()
sum.Write([]byte(d.r.Host))
sum.Write([]byte{0})
sum.Write(d.NetworkPrefix().AsSlice())
sum.Write([]byte{0})
return sum.Sum(nil)[:6]
}

View File

@@ -125,18 +125,10 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
}
if result.Bad() {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, nil, expiry, false)
if err != nil {
return challenge.VerifyResultFail
}
utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
data.IssueChallengeToken(reg, key, nil, expiry, false)
return challenge.VerifyResultNotOK
} else {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, nil, expiry, true)
if err != nil {
return challenge.VerifyResultFail
}
utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
data.IssueChallengeToken(reg, key, nil, expiry, true)
return challenge.VerifyResultOK
}
}

View File

@@ -138,6 +138,8 @@ func VerifyHandlerChallengeResponseFunc(state StateInterface, data *RequestData,
}
reqUri.RawQuery = q.Encode()
data.ResponseHeaders(w)
http.Redirect(w, r, reqUri.String(), http.StatusTemporaryRedirect)
return
}
@@ -147,6 +149,7 @@ func VerifyHandlerChallengeResponseFunc(state StateInterface, data *RequestData,
state.ErrorPage(w, r, http.StatusForbidden, fmt.Errorf("access denied: failed challenge"), redirect)
return
}
data.ResponseHeaders(w)
http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
}
@@ -175,18 +178,12 @@ func VerifyHandlerFunc(state StateInterface, reg *Registration, verify VerifyFun
if err != nil {
return err
} else if !verifyResult.Ok() {
utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
state.ChallengeFailed(r, reg, nil, redirect, nil)
responseFunc(state, data, w, r, verifyResult, nil, redirect)
return nil
}
challengeToken, err := reg.IssueChallengeToken(state.PrivateKey(), key, []byte(token), expiration, true)
if err != nil {
utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
} else {
utils.SetCookie(data.CookiePrefix+reg.Name, challengeToken, expiration, w, r)
}
data.IssueChallengeToken(reg, key, []byte(token), expiration, true)
data.ChallengeVerify[reg.id] = verifyResult
state.ChallengePassed(r, reg, redirect, nil)
@@ -194,7 +191,6 @@ func VerifyHandlerFunc(state StateInterface, reg *Registration, verify VerifyFun
return nil
}()
if err != nil {
utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
state.ChallengeFailed(r, reg, err, redirect, nil)
responseFunc(state, data, w, r, VerifyResultFail, fmt.Errorf("access denied: error in challenge %s: %w", reg.Name, err), redirect)
return

View File

@@ -5,7 +5,6 @@ import (
"crypto/subtle"
"errors"
"git.gammaspectra.live/git/go-away/lib/challenge"
"git.gammaspectra.live/git/go-away/utils"
"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"io"
@@ -140,18 +139,10 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
data := challenge.RequestDataFromContext(r.Context())
if response.StatusCode != params.HttpCode {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, false)
if err != nil {
return challenge.VerifyResultFail
}
utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
data.IssueChallengeToken(reg, key, sum, expiry, false)
return challenge.VerifyResultNotOK
} else {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, true)
if err != nil {
return challenge.VerifyResultFail
}
utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
data.IssueChallengeToken(reg, key, sum, expiry, true)
return challenge.VerifyResultOK
}
}

View File

@@ -66,7 +66,7 @@ func GetChallengeKeyForRequest(state StateInterface, reg *Registration, until ti
hasher.Write([]byte{0})
_ = binary.Write(hasher, binary.LittleEndian, until.UTC().Unix())
hasher.Write([]byte{0})
hasher.Write(state.PublicKey())
hasher.Write(state.PrivateKeyFingerprint())
hasher.Write([]byte{0})
sum := Key(hasher.Sum(nil))

View File

@@ -110,6 +110,9 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
}
verifyResult, _ := verifier(key, []byte(token), r)
data.ResponseHeaders(w)
if !verifyResult.Ok() {
w.WriteHeader(http.StatusUnauthorized)
} else {

View File

@@ -1,18 +1,12 @@
package challenge
import (
"bytes"
http_cel "codeberg.org/gone/http-cel"
"crypto/ed25519"
"errors"
"fmt"
"git.gammaspectra.live/git/go-away/lib/policy"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/goccy/go-yaml/ast"
"github.com/google/cel-go/cel"
"io"
"math/rand/v2"
"net/http"
"path"
"strings"
@@ -145,104 +139,10 @@ type Registration struct {
type VerifyFunc func(key Key, token []byte, r *http.Request) (VerifyResult, error)
type Token struct {
Name string `json:"name"`
Key []byte `json:"key"`
Result []byte `json:"result,omitempty"`
Ok bool `json:"ok"`
Expiry jwt.NumericDate `json:"exp,omitempty"`
NotBefore jwt.NumericDate `json:"nbf,omitempty"`
IssuedAt jwt.NumericDate `json:"iat,omitempty"`
}
func (reg Registration) Id() Id {
return reg.id
}
func (reg Registration) IssueChallengeToken(privateKey ed25519.PrivateKey, key Key, result []byte, until time.Time, ok bool) (token string, err error) {
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.EdDSA,
Key: privateKey,
}, nil)
if err != nil {
return "", err
}
token, err = jwt.Signed(signer).Claims(Token{
Name: reg.Name,
Key: key[:],
Result: result,
Ok: ok,
Expiry: jwt.NumericDate(until.Unix()),
NotBefore: jwt.NumericDate(time.Now().UTC().AddDate(0, 0, -1).Unix()),
IssuedAt: jwt.NumericDate(time.Now().UTC().Unix()),
}).Serialize()
if err != nil {
return "", err
}
return token, nil
}
var ErrVerifyKeyMismatch = errors.New("verify: key mismatch")
var ErrVerifyVerifyMismatch = errors.New("verify: verification mismatch")
var ErrTokenExpired = errors.New("token: expired")
func (reg Registration) VerifyChallengeToken(publicKey ed25519.PublicKey, expectedKey Key, r *http.Request) (VerifyResult, VerifyState, error) {
cookie, err := r.Cookie(RequestDataFromContext(r.Context()).CookiePrefix + reg.Name)
if err != nil {
return VerifyResultNone, VerifyStateNone, err
}
if cookie == nil {
return VerifyResultNone, VerifyStateNone, http.ErrNoCookie
}
token, err := jwt.ParseSigned(cookie.Value, []jose.SignatureAlgorithm{jose.EdDSA})
if err != nil {
return VerifyResultFail, VerifyStateNone, err
}
var i Token
err = token.Claims(publicKey, &i)
if err != nil {
return VerifyResultFail, VerifyStateNone, err
}
if i.Name != reg.Name {
return VerifyResultFail, VerifyStateNone, errors.New("token invalid name")
}
if i.Expiry.Time().Compare(time.Now()) < 0 {
return VerifyResultFail, VerifyStateNone, ErrTokenExpired
}
if i.NotBefore.Time().Compare(time.Now()) > 0 {
return VerifyResultFail, VerifyStateNone, errors.New("token not valid yet")
}
if bytes.Compare(expectedKey[:], i.Key) != 0 {
return VerifyResultFail, VerifyStateNone, ErrVerifyKeyMismatch
}
if reg.Verify != nil {
if rand.Float64() < reg.VerifyProbability {
// random spot check
if ok, err := reg.Verify(expectedKey, i.Result, r); err != nil {
return VerifyResultFail, VerifyStateFull, err
} else if ok == VerifyResultNotOK {
return VerifyResultNotOK, VerifyStateFull, nil
} else if !ok.Ok() {
return ok, VerifyStateFull, ErrVerifyVerifyMismatch
} else {
return ok, VerifyStateFull, nil
}
}
}
if !i.Ok {
return VerifyResultNotOK, VerifyStateBrief, nil
}
return VerifyResultOK, VerifyStateBrief, nil
}
type FillRegistration func(state StateInterface, reg *Registration, parameters ast.Node) error
var Runtimes = make(map[string]FillRegistration)

View File

@@ -45,6 +45,9 @@ func FillRegistrationHeader(state challenge.StateInterface, reg *challenge.Regis
//TODO: add other types inside css that need to be loaded!
w.Header().Set("Content-Type", "text/css; charset=utf-8")
w.Header().Set("Content-Length", "0")
data.ResponseHeaders(w)
if !verifyResult.Ok() {
w.WriteHeader(http.StatusForbidden)
} else {

View File

@@ -23,7 +23,7 @@ func ServeChallengeScript(w http.ResponseWriter, r *http.Request, reg *Registrat
//TODO: log
panic(err)
}
data.ResponseHeaders(w)
w.WriteHeader(http.StatusOK)
err = scriptTemplate.Execute(w, map[string]any{

View File

@@ -89,6 +89,7 @@ type StateInterface interface {
RegisterCondition(operator string, conditions ...string) (cel.Program, error)
Client() *http.Client
PrivateKeyFingerprint() []byte
PrivateKey() ed25519.PrivateKey
PublicKey() ed25519.PublicKey

View File

@@ -267,10 +267,13 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
cookies := r.Cookies()
r.Header.Del("Cookie")
for _, c := range cookies {
if !strings.HasPrefix(c.Name, utils.CookiePrefix) {
if !strings.HasPrefix(c.Name, utils.DefaultCookiePrefix) {
r.AddCookie(c)
}
}
// set response headers
data.ResponseHeaders(w)
}
for _, rule := range state.rules {
@@ -323,7 +326,5 @@ func (state *State) ServeHTTP(w http.ResponseWriter, r *http.Request) {
data.EvaluateChallenges(w, r)
data.ResponseHeaders(w.Header())
state.Mux.ServeHTTP(w, r)
}

View File

@@ -26,6 +26,10 @@ func (state *State) PrivateKey() ed25519.PrivateKey {
return state.privateKey
}
func (state *State) PrivateKeyFingerprint() []byte {
return state.privateKeyFingerprint
}
func (state *State) PublicKey() ed25519.PublicKey {
return state.publicKey
}

View File

@@ -29,7 +29,6 @@ type RuleState struct {
}
func NewRuleState(state challenge.StateInterface, r policy.Rule, replacer *strings.Replacer, parent *RuleState) (RuleState, error) {
fp := sha256.Sum256(state.PrivateKey())
hasher := sha256.New()
if parent != nil {
hasher.Write([]byte(parent.Name))
@@ -38,7 +37,7 @@ func NewRuleState(state challenge.StateInterface, r policy.Rule, replacer *strin
}
hasher.Write([]byte(r.Name))
hasher.Write([]byte{0})
hasher.Write(fp[:])
hasher.Write(state.PrivateKeyFingerprint())
sum := hasher.Sum(nil)
rule := RuleState{

View File

@@ -35,8 +35,9 @@ type State struct {
programEnv *cel.Env
publicKey ed25519.PublicKey
privateKey ed25519.PrivateKey
publicKey ed25519.PublicKey
privateKey ed25519.PrivateKey
privateKeyFingerprint []byte
opt settings.Settings
settings policy.StateSettings
@@ -101,6 +102,9 @@ func NewState(p policy.Policy, opt settings.Settings, settings policy.StateSetti
}
}
fp := sha256.Sum256(state.privateKey)
state.privateKeyFingerprint = fp[:]
if templates["challenge-"+state.opt.ChallengeTemplate+".gohtml"] == nil {
if data, err := os.ReadFile(state.opt.ChallengeTemplate); err == nil && len(data) > 0 {

View File

@@ -107,6 +107,7 @@ func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status
if err != nil {
state.ErrorPage(w, r, http.StatusInternalServerError, err, "")
} else {
data.ResponseHeaders(w)
w.WriteHeader(status)
_, _ = w.Write(buf.Bytes())
}
@@ -141,6 +142,7 @@ func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int
// nested errors!
panic(err2)
} else {
data.ResponseHeaders(w)
w.WriteHeader(status)
_, _ = w.Write(buf.Bytes())
}