token-validator: done many challenges
This commit is contained in:
parent
91edced44d
commit
133244897b
253
token-validator/challenge.go
Normal file
253
token-validator/challenge.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
)
|
||||||
|
|
||||||
|
const IPgwDMZ = "172.23.200.1"
|
||||||
|
|
||||||
|
type Challenge struct {
|
||||||
|
Accessible []func(*Student, *http.Request) error
|
||||||
|
Check func(*Student, *givenToken, int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restrictions */
|
||||||
|
|
||||||
|
func noAccessRestriction(*Student, *http.Request) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessFrom(ip string) func(_ *Student, r *http.Request) error {
|
||||||
|
return func(_ *Student, r *http.Request) error {
|
||||||
|
if r.Header.Get("X-Forwarded-By") != ip {
|
||||||
|
return errors.New("This challenge is not accessible this way.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notAccessFrom(ip string) func(_ *Student, r *http.Request) error {
|
||||||
|
return func(_ *Student, r *http.Request) error {
|
||||||
|
if r.Header.Get("X-Forwarded-By") == ip {
|
||||||
|
return errors.New("This challenge is not accessible this way.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxProxy(nb int) func(_ *Student, r *http.Request) error {
|
||||||
|
return func(_ *Student, r *http.Request) error {
|
||||||
|
if len(strings.Split(r.Header.Get("X-Forwarded-For"), ",")) > nb {
|
||||||
|
return errors.New("This challenge is not accessible this way.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sslOnly(_ *Student, r *http.Request) error {
|
||||||
|
if r.Header.Get("X-Forwarded-Proto") != "https" {
|
||||||
|
return errors.New("This challenge should be performed over TLS.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Challenges */
|
||||||
|
|
||||||
|
func challenge42(s *Student, t *givenToken, chid int) error {
|
||||||
|
pkey := s.GetPKey()
|
||||||
|
if expectedToken, err := GenerateToken(pkey, chid, []byte("42")); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ! hmac.Equal(expectedToken, t.token) {
|
||||||
|
return errors.New("This is not the expected token.")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func challengeDNS(s *Student, t *givenToken, chid int) error {
|
||||||
|
pkey := s.GetPKey()
|
||||||
|
if expectedToken, err := GenerateToken(pkey, chid, []byte("8dde678132d6c558fc6adaeb9f1d53bf6ec7b876308cf98c48604caa9138523c1ce58b672c87c7e7d9b7248b81804d3940dbf20bf263eeb683244f7c1143712d")); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ! hmac.Equal(expectedToken, t.token) {
|
||||||
|
return errors.New("This is not the expected token.")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func challengeTime(s *Student, t *givenToken, chid int) error {
|
||||||
|
pkey := s.GetPKey()
|
||||||
|
if expectedToken, err := GenerateToken(pkey, chid, []byte(t.Data[0])); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ! hmac.Equal(expectedToken, t.token) {
|
||||||
|
return errors.New("This is not the expected token.")
|
||||||
|
} else if t, err := strconv.ParseInt(t.Data[0], 10, 64); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
var rt time.Time
|
||||||
|
if t > 3000000000 {
|
||||||
|
rt = time.Unix(t / 1000000000, t % 1000000000)
|
||||||
|
} else {
|
||||||
|
rt = time.Unix(t, 0)
|
||||||
|
}
|
||||||
|
if rt.After(time.Now()) {
|
||||||
|
return errors.New("You seem to live in the future...")
|
||||||
|
} else if d := rt.Sub(time.Now()); d.Minutes() < -10 {
|
||||||
|
return errors.New(fmt.Sprintf("Your time is %.0f minutes from us", d.Minutes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func challengeDisk(s *Student, t *givenToken, chid int) error {
|
||||||
|
pkey := s.GetPKey()
|
||||||
|
|
||||||
|
n1, err := strconv.Atoi(t.Token[0:2])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n2, err := strconv.Atoi(t.Token[2:4])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := make([]byte, hex.DecodedLen(len(t.Token[4:])))
|
||||||
|
if _, err := hex.Decode(t.token, []byte(t.Token[4:])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedToken := blake2b.Sum512([]byte(pkey[n1:n2]))
|
||||||
|
|
||||||
|
if ! hmac.Equal(expectedToken[:], sum) {
|
||||||
|
return errors.New("This is not the expected token.")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var challenges []Challenge
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
challenges = []Challenge{
|
||||||
|
/* Challenge 1 : 42 */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
|
||||||
|
Check: challenge42,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Challenge 2 : 42 from DMZ */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ)},
|
||||||
|
Check: challenge42,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Challenge 3 : ssl (+ ntp) */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
|
||||||
|
Check: challengeTime,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Challenge 4 : disk */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
|
||||||
|
Check: challengeDisk,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Challenge 5 : DNS TXT */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
|
||||||
|
Check: challengeDNS,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Challenge 6 : time net */
|
||||||
|
Challenge{
|
||||||
|
Accessible: []func(*Student, *http.Request) error{maxProxy(1)},
|
||||||
|
Check: challengeTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
router.GET("/challenge", apiHandler(getChallengeList))
|
||||||
|
router.GET("/challenge/:chid", rawHandler(accessibleChallenge))
|
||||||
|
router.POST("/challenge", rawHandler(receiveToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
type givenToken struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
Challenge int `json:"challenge"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
token []byte
|
||||||
|
Data []string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChallengeList(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
|
var ret []int
|
||||||
|
for i := range challenges {
|
||||||
|
ret = append(ret, i)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func accessibleChallenge(r *http.Request, ps httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
|
if chid, err := strconv.Atoi(string(ps.ByName("chid"))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if chid == 0 || chid >= len(challenges) {
|
||||||
|
return nil, errors.New("This challenge doesn't exist")
|
||||||
|
} else {
|
||||||
|
for _, a := range challenges[chid - 1].Accessible {
|
||||||
|
if err := a(nil, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveToken(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
|
||||||
|
if _, err := accessibleChallenge(r, ps, body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var gt givenToken
|
||||||
|
if err := json.Unmarshal(body, >); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gt.token = make([]byte, hex.DecodedLen(len(gt.Token)))
|
||||||
|
if _, err := hex.Decode(gt.token, []byte(gt.Token)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chid, err := strconv.Atoi(string(ps.ByName("chid"))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if chid == 0 || chid > len(challenges) {
|
||||||
|
return nil, errors.New("This challenge doesn't exist")
|
||||||
|
} else if std, err := getStudentByLogin(gt.Login); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if err := challenges[chid - 1].Check(&std, >, chid); err != nil {
|
||||||
|
log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := std.UnlockNewChallenge(chid, gt.Token); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s just unlock ch#%d\n", std.Login, chid)
|
||||||
|
return "Success", nil
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httproute
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
func rawHandler(f func(*http.Request, httprouter.Params, []byte) (interface{}, error)) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
||||||
r.RemoteAddr = addr
|
r.RemoteAddr = addr
|
||||||
@ -69,7 +69,7 @@ func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, htt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err = f(ps, body)
|
ret, err = f(r, ps, body)
|
||||||
|
|
||||||
// Format response
|
// Format response
|
||||||
resStatus := http.StatusOK
|
resStatus := http.StatusOK
|
||||||
@ -99,6 +99,10 @@ func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, htt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||||
|
return rawHandler(func (_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) })
|
||||||
|
}
|
||||||
|
|
||||||
func studentHandler(f func(Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
func studentHandler(f func(Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
||||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -10,19 +12,21 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
router.GET("/api/students/", apiHandler(
|
router.GET("/api/students/", apiHandler(
|
||||||
func(httprouter.Params,[]byte) (interface{}, error) {
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
return getStudents() }))
|
return getStudents()
|
||||||
|
}))
|
||||||
router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent)))
|
router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent)))
|
||||||
router.GET("/api/students/:sid/", apiHandler(studentHandler(
|
router.GET("/api/students/:sid/", apiHandler(studentHandler(
|
||||||
func(std Student, _ []byte) (interface{}, error) {
|
func(std Student, _ []byte) (interface{}, error) {
|
||||||
return std, nil })))
|
return std, nil
|
||||||
|
})))
|
||||||
router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent))))
|
router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent))))
|
||||||
router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(
|
router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(
|
||||||
func(std Student, _ []byte) (interface{}, error) {
|
func(std Student, _ []byte) (interface{}, error) {
|
||||||
return std.Delete() }))))
|
return std.Delete()
|
||||||
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Student struct {
|
type Student struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
@ -70,6 +74,10 @@ func NewStudent(login string) (Student, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Student) GetPKey() []byte {
|
||||||
|
return hmac.New(sha512.New512_224, []byte(sharedSecret)).Sum([]byte(s.Login))
|
||||||
|
}
|
||||||
|
|
||||||
func (s Student) Update() (int64, error) {
|
func (s Student) Update() (int64, error) {
|
||||||
if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil {
|
if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -100,7 +108,6 @@ func ClearStudents() (int64, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
|
func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
var std Student
|
var std Student
|
||||||
if err := json.Unmarshal(body, &std); err != nil {
|
if err := json.Unmarshal(body, &std); err != nil {
|
||||||
@ -120,3 +127,20 @@ func updateStudent(current Student, body []byte) (interface{}, error) {
|
|||||||
current.Time = new.Time
|
current.Time = new.Time
|
||||||
return current.Update()
|
return current.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UnlockedChallenge struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
IdStudent int64 `json:"id_student"`
|
||||||
|
Challenge int `json:"challenge"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) {
|
||||||
|
if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil {
|
||||||
|
return UnlockedChallenge{}, err
|
||||||
|
} else if utid, err := res.LastInsertId(); err != nil {
|
||||||
|
return UnlockedChallenge{}, err
|
||||||
|
} else {
|
||||||
|
return UnlockedChallenge{utid, s.Id, challenge, time.Now()}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
24
token-validator/token.go
Normal file
24
token-validator/token.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateToken(pkey []byte, id int, a... []byte) ([]byte, error) {
|
||||||
|
h, err := blake2b.New(blake2b.Size, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Write(pkey)
|
||||||
|
h.Write([]byte(fmt.Sprintf(":%d", id)))
|
||||||
|
|
||||||
|
for _, v := range a {
|
||||||
|
h.Write([]byte(":"))
|
||||||
|
h.Write(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
Reference in New Issue
Block a user