diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2fdc5c2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2024 NAME HERE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/auth_check.go b/auth_check.go new file mode 100644 index 0000000..20e368b --- /dev/null +++ b/auth_check.go @@ -0,0 +1,29 @@ +package csrf + +import ( + "crypto/md5" + "fmt" + "strconv" + "time" +) + +// authenticated routes + +// compare two. possibly change this into middleware? +func (c CSRF) AuthCheck(userID string, routeName string, givenToken string) bool { + minfactor := strconv.Itoa(time.Now().Hour()) + minfactor_1 := strconv.Itoa(time.Now().Hour() - 1) + comp := fmt.Sprintf("%x", md5.Sum([]byte(userID+routeName+c.CSRFKey+minfactor))) + comp2 := fmt.Sprintf("%x", md5.Sum([]byte(userID+routeName+c.CSRFKey+minfactor_1))) + // comp := sha256.New() + // comp.Write([]byte(userID + routeName + CSRFKey + strconv.Itoa(time.Now().Hour()))) + // be charitable: + //comp2 := sha256.New() + //comp2.Write([]byte(userID + routeName + CSRFKey + strconv.Itoa(time.Now().Hour()-1))) + if comp == givenToken { + return true + } + // second comparison for last hour: + return comp2 == givenToken + //return fmt.Sprintf("%x", comp2.Sum(nil)) == givenToken +} diff --git a/auth_make.go b/auth_make.go new file mode 100644 index 0000000..5fffbdd --- /dev/null +++ b/auth_make.go @@ -0,0 +1,18 @@ +package csrf + +import ( + "crypto/md5" + "fmt" + "strconv" + "time" +) + +// a CSRF key is valid for the current time, route, and user. +func (c CSRF) AuthMake(userID string, routeName string) string { + // h := sha256.New() + // h.Write([]byte(userID + routeName + CSRFKey + strconv.Itoa(time.Now().Hour()))) + // return fmt.Sprintf("%x", h.Sum(nil)) + minfactor := strconv.Itoa(time.Now().Hour()) + res := fmt.Sprintf("%x", md5.Sum([]byte(userID+routeName+c.CSRFKey+minfactor))) + return res +} diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..3a83861 --- /dev/null +++ b/example_test.go @@ -0,0 +1,23 @@ +package csrf_test + +import ( + "fmt" + "net/http" + + "git.bivouac.wiki/use/csrf" +) + +func denied(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + fmt.Println("404: " + r.URL.Path) + w.WriteHeader(403) + w.Write([]byte("CSRF invalid")) +} + +func ExampleCSRF() { + k := csrf.SessionRandB64(16) + c := csrf.New(k, denied) + val := c.AuthMake("alice", "/home") + c.AuthCheck("alice", "/home", val) + // can add in the httptest examples for the middleware in a bit. +} diff --git a/guestgen.go b/guestgen.go deleted file mode 100644 index a556ab8..0000000 --- a/guestgen.go +++ /dev/null @@ -1 +0,0 @@ -package csrf diff --git a/guestvalid.go b/guestvalid.go deleted file mode 100644 index 25c9559..0000000 --- a/guestvalid.go +++ /dev/null @@ -1,3 +0,0 @@ -package csrf - -// guest routes diff --git a/init.go b/init.go new file mode 100644 index 0000000..a1babc4 --- /dev/null +++ b/init.go @@ -0,0 +1,23 @@ +package csrf + +import "net/http" + +type CSRF struct { + CSRFKey string + DeniedFn DenyFN +} +type DenyFN func(w http.ResponseWriter, r *http.Request) + +// NewCSRFRand makes a new CSRF with a random key. +func NewRand(deniedfn DenyFN) CSRF { + k := SessionRandB64(16) + return New(k, deniedfn) +} + +// NewCSRF creates a CSRF with a defined key +func New(key string, deniedfn DenyFN) CSRF { + return CSRF{ + CSRFKey: key, + DeniedFn: deniedfn, + } +} diff --git a/middleware.go b/middleware.go index a556ab8..718e52d 100644 --- a/middleware.go +++ b/middleware.go @@ -1 +1,29 @@ package csrf + +import "net/http" + +type CtxKey string + +const ContextUserId CtxKey = "userid" + +func (c CSRF) MiddleAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // requires WhoIsThis middleware to set this context key beforehand... + userID := r.Context().Value(ContextUserId).(string) + if c.AuthCheck(userID, r.RequestURI, r.FormValue("csrf")) { + c.DeniedFn(w, r) + return + } + next.ServeHTTP(w, r) + }) +} + +func (c CSRF) MiddleUnauth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !c.UnauthCheck(r.RequestURI, r.FormValue("csrf")) { + c.DeniedFn(w, r) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/rand.go b/rand.go new file mode 100644 index 0000000..ee15891 --- /dev/null +++ b/rand.go @@ -0,0 +1,28 @@ +package csrf + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "math" +) + +func urlRandomBase64String(l int) string { + buff := make([]byte, int(math.Ceil(float64(l)/float64(1.33333333333)))) + rand.Read(buff) + str := base64.RawURLEncoding.EncodeToString(buff) + return str[:l] // strip 1 extra character we get from odd length results +} + +func randChars(len int) []byte { + val := make([]byte, len) + if _, err := rand.Read(val); err != nil { + fmt.Println(err) + } + return val +} +func SessionRandB64(len int) string { + val := randChars(len) + res := base64.StdEncoding.EncodeToString(val) + return res +} diff --git a/unauth_check.go b/unauth_check.go new file mode 100644 index 0000000..4ebb1d0 --- /dev/null +++ b/unauth_check.go @@ -0,0 +1,28 @@ +package csrf + +import ( + "crypto/md5" + "fmt" + "strconv" + "time" +) + +// guest routes +func (c CSRF) UnauthCheck(routename string, givenToken string) bool { + //comp := sha256.New() + minfactor := strconv.Itoa(time.Now().Minute() / 10) + minfactor_1 := strconv.Itoa((time.Now().Minute() / 10) - 1) + comp := fmt.Sprintf("%x", md5.Sum([]byte(routename+c.CSRFKey+minfactor))) + //comp.Write([]byte(routename + CSRFKey + minfactor)) + // be charitable: + comp2 := fmt.Sprintf("%x", md5.Sum([]byte(routename+c.CSRFKey+minfactor_1))) + // comp2 := sha256.New() + // comp2.Write([]byte(routename + CSRFKey + minfactor_1)) + if comp == givenToken { + return true + } + // second comparison for last hour: + return comp2 == givenToken + //return fmt.Sprintf("%x", comp2.Sum(nil)) == givenToken + +} diff --git a/unauth_make.go b/unauth_make.go new file mode 100644 index 0000000..b430cc6 --- /dev/null +++ b/unauth_make.go @@ -0,0 +1,20 @@ +package csrf + +import ( + "crypto/md5" + "fmt" + "strconv" + "time" +) + +// userless CSRF +func (c CSRF) UnauthMake(routename string) string { + //shorter time scales + // h := sha256.New() + // get time in 10 minute chunks + minfactor := strconv.Itoa(time.Now().Minute() / 10) + res := fmt.Sprintf("%x", md5.Sum([]byte(routename+c.CSRFKey+minfactor))) + // h.Write([]byte(routename + CSRFKey + minfactor)) + // res := fmt.Sprintf("%x", h.Sum(nil)) + return res +} diff --git a/usergen.go b/usergen.go deleted file mode 100644 index a556ab8..0000000 --- a/usergen.go +++ /dev/null @@ -1 +0,0 @@ -package csrf diff --git a/uservalid.go b/uservalid.go deleted file mode 100644 index c234108..0000000 --- a/uservalid.go +++ /dev/null @@ -1,3 +0,0 @@ -package csrf - -// authenticated routes