4
4
package main
5
5
6
6
import (
7
- "crypto/sha256"
8
- "crypto/subtle"
9
7
"encoding/base64"
10
8
"errors"
11
9
"log"
@@ -21,29 +19,39 @@ type tparams struct {
21
19
22
20
func main () {
23
21
log .Fatal (http .ListenAndServe (listenAddr , http .HandlerFunc (func (writer http.ResponseWriter , request * http.Request ) {
22
+ // Static resources for powxy itself.
24
23
if strings .HasPrefix (request .URL .Path , "/.powxy/" ) {
25
24
http .StripPrefix ("/.powxy/" , http .FileServer (http .FS (resourcesFS ))).ServeHTTP (writer , request )
26
25
return
27
26
}
28
27
28
+ // We attempt to fetch the powxy cookie. Its non-existence
29
+ // does not matter here; if the cookie does not exist, it
30
+ // will be nil, so validation will simply fail and the user
31
+ // will be prompted to solve the PoW challenge.
29
32
cookie , err := request .Cookie ("powxy" )
30
- if err != nil {
31
- if ! errors .Is (err , http .ErrNoCookie ) {
32
- log .Println ("COOKIE_ERR" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
33
- http .Error (writer , "error fetching cookie" , http .StatusInternalServerError )
34
- return
35
- }
33
+ if err != nil && ! errors .Is (err , http .ErrNoCookie ) {
34
+ log .Println ("COOKIE_ERR" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
35
+ http .Error (writer , "error fetching cookie" , http .StatusInternalServerError )
36
+ return
36
37
}
37
38
39
+ // We generate the identifier that identifies the client,
40
+ // and the expected HMAC that the cookie should include.
38
41
identifier , expectedMAC := makeIdentifierMAC (request )
39
42
43
+ // If the cookie exists and is valid, we simply proxy the
44
+ // request.
40
45
if validateCookie (cookie , expectedMAC ) {
41
46
log .Println ("PROXY" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
42
47
proxyRequest (writer , request )
43
48
return
44
49
}
45
50
46
- authPage := func (message string ) {
51
+ // A convenience function to render the challenge page,
52
+ // since all parameters but the message are constant at this
53
+ // point.
54
+ challengePage := func (message string ) {
47
55
err := tmpl .Execute (writer , tparams {
48
56
Identifier : base64 .StdEncoding .EncodeToString (identifier ),
49
57
Message : message ,
@@ -54,46 +62,59 @@ func main() {
54
62
}
55
63
}
56
64
65
+ // This generally shouldn't happen, at least not for web
66
+ // browesrs.
57
67
if request .ParseForm () != nil {
58
68
log .Println ("MALFORMED" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
59
- authPage ("You submitted a malformed form." )
69
+ challengePage ("You submitted a malformed form." )
60
70
return
61
71
}
62
72
63
73
formValues , ok := request .PostForm ["powxy" ]
64
74
if ! ok {
75
+ // If there's simply no form value, the user is probably
76
+ // just visiting the site for the first time or with an
77
+ // expired cookie.
65
78
log .Println ("CHALLENGE" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
66
- authPage ("" )
79
+ challengePage ("" )
67
80
return
68
81
} else if len (formValues ) != 1 {
82
+ // This should never happen, at least not for web
83
+ // browsers.
69
84
log .Println ("FORM_VALUES" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
70
- authPage ("You submitted an invalid number of form values." )
85
+ challengePage ("You submitted an invalid number of form values." )
71
86
return
72
87
}
73
88
74
- nonce , err := base64 .StdEncoding .DecodeString (formValues [0 ])
75
- if err != nil {
76
- log .Println ("BASE64" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
77
- authPage ("Your submission was improperly encoded." )
89
+ // We validate that the length is reasonable before even
90
+ // decoding it with base64.
91
+ if len (formValues [0 ]) > 43 {
92
+ log .Println ("TOO_LONG" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
93
+ challengePage ("Your submission was too long." )
78
94
return
79
95
}
80
96
81
- if len (nonce ) > 32 {
82
- log .Println ("TOO_LONG" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
83
- authPage ("Your submission was too long." )
97
+ // Actually decode the base64 value.
98
+ nonce , err := base64 .StdEncoding .DecodeString (formValues [0 ])
99
+ if err != nil {
100
+ log .Println ("BASE64" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
101
+ challengePage ("Your submission was improperly encoded." )
84
102
return
85
103
}
86
104
87
- h := sha256 .New ()
88
- h .Write (identifier )
89
- h .Write (nonce )
90
- ck := h .Sum (nil )
91
- if ! validateBitZeros (ck , global .NeedBits ) {
105
+ // Validate the nonce.
106
+ if ! validateNonce (identifier , nonce ) {
92
107
log .Println ("WRONG" , getRemoteIP (request ), request .RequestURI , request .Header .Get ("User-Agent" ))
93
- authPage ("Your submission was incorrect, or your session has expired while submitting." )
108
+ challengePage ("Your submission was incorrect, or your session has expired while submitting." )
94
109
return
95
110
}
96
111
112
+ // Everything starting here: the nonce is valid, and we
113
+ // can set the cookie and redirect them. The redirection is
114
+ // needed as their "normal" request is most definitely
115
+ // different from one to expect after solving the PoW
116
+ // challenge.
117
+
97
118
http .SetCookie (writer , & http.Cookie {
98
119
Name : "powxy" ,
99
120
Value : base64 .StdEncoding .EncodeToString (expectedMAC ),
@@ -105,30 +126,3 @@ func main() {
105
126
http .Redirect (writer , request , "" , http .StatusSeeOther )
106
127
})))
107
128
}
108
-
109
- func validateCookie (cookie * http.Cookie , expectedMAC []byte ) bool {
110
- if cookie == nil {
111
- return false
112
- }
113
-
114
- gotMAC , err := base64 .StdEncoding .DecodeString (cookie .Value )
115
- if err != nil {
116
- return false
117
- }
118
-
119
- return subtle .ConstantTimeCompare (gotMAC , expectedMAC ) == 1
120
- }
121
-
122
- func getRemoteIP (request * http.Request ) (remoteIP string ) {
123
- if secondary {
124
- remoteIP , _ , _ = strings .Cut (request .Header .Get ("X-Forwarded-For" ), "," )
125
- }
126
- if remoteIP == "" {
127
- remoteIP = request .RemoteAddr
128
- index := strings .LastIndex (remoteIP , ":" )
129
- if index != - 1 {
130
- remoteIP = remoteIP [:index ]
131
- }
132
- }
133
- return
134
- }
0 commit comments