-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy patherrors.go
More file actions
168 lines (144 loc) · 5.17 KB
/
Copy patherrors.go
File metadata and controls
168 lines (144 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package samesame
import (
"errors"
"fmt"
"log/slog"
"net/http"
"github.com/dunglas/httpsfv"
)
// Outcome is the result of verifying a request (protocol draft Appendix C.1).
type Outcome int
const (
// OutcomeUnverified means the verifier could not get enough information
// to decide: the request is unsigned, discovery failed, or the key is
// unknown. It is neither proof of identity nor proof of forgery.
OutcomeUnverified Outcome = iota
// OutcomeInvalid means a signature, its covered components, its key, or
// its freshness failed to verify.
OutcomeInvalid
// OutcomeVerified means a signature verified.
OutcomeVerified
)
func (o Outcome) String() string {
switch o {
case OutcomeUnverified:
return "unverified"
case OutcomeInvalid:
return "invalid"
case OutcomeVerified:
return "verified"
default:
return fmt.Sprintf("Outcome(%d)", int(o))
}
}
// Errors wrapped by VerifyError. Use errors.Is to check for them.
var (
ErrNoSignature = errors.New("samesame: no web-bot-auth signature")
ErrMalformedSignature = errors.New("samesame: malformed signature headers")
ErrInsecureTransport = errors.New("samesame: signature sent over plaintext HTTP")
ErrProfile = errors.New("samesame: signature does not follow the web-bot-auth profile")
ErrNotYetValid = errors.New("samesame: signature created in the future")
ErrExpired = errors.New("samesame: signature expired")
ErrValidityTooLong = errors.New("samesame: signature validity window too long")
ErrBadSignature = errors.New("samesame: signature does not verify")
ErrKeyUnknown = errors.New("samesame: signing key unknown")
ErrDiscovery = errors.New("samesame: key discovery failed")
ErrNonceRequired = errors.New("samesame: signature has no nonce")
ErrReplay = errors.New("samesame: nonce already used")
ErrNonceStore = errors.New("samesame: nonce store unavailable")
ErrTooManySignatures = errors.New("samesame: too many signatures")
)
// VerifyError describes why a request did not verify.
type VerifyError struct {
Outcome Outcome
Label string // signature label, empty when not specific to one
Err error
}
func (e *VerifyError) Error() string {
if e.Label == "" {
return fmt.Sprintf("%s: %v", e.Outcome, e.Err)
}
return fmt.Sprintf("%s: signature %q: %v", e.Outcome, e.Label, e.Err)
}
func (e *VerifyError) Unwrap() error { return e.Err }
func (e *VerifyError) LogValue() slog.Value {
return slog.GroupValue(
slog.String("outcome", e.Outcome.String()),
slog.String("label", e.Label),
slog.String("err", e.Err.Error()),
)
}
func unverified(label string, err error) *VerifyError {
return &VerifyError{Outcome: OutcomeUnverified, Label: label, Err: err}
}
func invalid(label string, err error) *VerifyError {
return &VerifyError{Outcome: OutcomeInvalid, Label: label, Err: err}
}
// OutcomeOf returns the outcome carried by err: OutcomeVerified for nil,
// the VerifyError's outcome, or OutcomeUnverified for any other error.
func OutcomeOf(err error) Outcome {
if err == nil {
return OutcomeVerified
}
var ve *VerifyError
if errors.As(err, &ve) {
return ve.Outcome
}
return OutcomeUnverified
}
// StatusCode suggests an HTTP status code for rejecting a request that failed
// with err (protocol draft Sections 5.3 and 5.4): 400 for unparseable
// signature headers, 429 for a replayed nonce, and 403 otherwise. Whether to
// reject an unverified request at all is the caller's policy.
func StatusCode(err error) int {
switch {
case err == nil:
return http.StatusOK
case errors.Is(err, ErrMalformedSignature), errors.Is(err, ErrMalformedSignatureAgent):
return http.StatusBadRequest
case errors.Is(err, ErrReplay):
return http.StatusTooManyRequests
default:
return http.StatusForbidden
}
}
// ChallengeOptions controls the Accept-Signature header WriteChallenge sends.
type ChallengeOptions struct {
// Label is the requested signature label and Signature-Agent member key.
// Defaults to DefaultLabel.
Label string
// Nonce, when set, asks the agent to sign with this exact nonce. The
// caller is responsible for remembering and checking it.
Nonce string
}
// WriteChallenge sets an Accept-Signature header asking for a web-bot-auth
// signature (protocol draft Section 5.3). It does not write a status code;
// use StatusCode.
func WriteChallenge(w http.ResponseWriter, opts ChallengeOptions) error {
if opts.Label == "" {
opts.Label = DefaultLabel
}
if !isSFKey(opts.Label) {
return fmt.Errorf("samesame: label %q is not a structured field key", opts.Label)
}
agent := httpsfv.NewItem("signature-agent")
agent.Params.Add("key", opts.Label)
components := httpsfv.InnerList{
Items: []httpsfv.Item{httpsfv.NewItem("@authority"), agent},
Params: httpsfv.NewParams(),
}
components.Params.Add("created", true)
components.Params.Add("expires", true)
components.Params.Add("tag", TagWebBotAuth)
if opts.Nonce != "" {
components.Params.Add("nonce", opts.Nonce)
}
dict := httpsfv.NewDictionary()
dict.Add(opts.Label, components)
value, err := httpsfv.Marshal(dict)
if err != nil {
return fmt.Errorf("samesame: can't serialize Accept-Signature: %w", err)
}
w.Header().Set("Accept-Signature", value)
return nil
}