package fic import ( "bytes" "errors" "fmt" "regexp" "time" "golang.org/x/crypto/blake2b" ) // FlagKey represents a flag's challenge, stored as hash. type FlagKey struct { Id int64 `json:"id"` // IdExercice is the identifier of the underlying challenge IdExercice int64 `json:"idExercice"` // Label is the title of the flag as displayed to players Label string `json:"label"` // Help is a small piece of text that aims to add useful information like flag format, ... Help string `json:"help"` // IgnoreCase indicates if the case is sensitive to case or not IgnoreCase bool `json:"ignorecase"` // ValidatorRegexp extracts a subset of the player's answer, that will be checked. ValidatorRegexp *string `json:"validator_regexp"` // Checksum is the expected hashed flag Checksum []byte `json:"value"` // ChoicesCost is the number of points lost to display choices. ChoicesCost int64 `json:"choices_cost"` } // GetFlagKeys returns a list of key's flags comming with the challenge. func (e Exercice) GetFlagKeys() ([]FlagKey, error) { if rows, err := DBQuery("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { return nil, err } else { defer rows.Close() var flags = make([]FlagKey, 0) for rows.Next() { var k FlagKey k.IdExercice = e.Id if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil { return nil, err } flags = append(flags, k) } if err := rows.Err(); err != nil { return nil, err } return flags, nil } } // GetFlagKey returns a list of flags comming with the challenge. func GetFlagKey(id int64) (k FlagKey, err error) { err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) return } // GetFlagKeyByLabel returns a flag matching the given label. func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) { err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) return } // getHashedFlag calculates the expected checksum for the given raw_value. func getHashedFlag(raw_value []byte) [blake2b.Size]byte { hash := blake2b.Sum512(raw_value) return hash } func ExecValidatorRegexp(vre string, val []byte) ([]byte, error) { if re, err := regexp.Compile(vre); err != nil { return val, err } else if res := re.FindSubmatch(val); res == nil { return val, errors.New("Expected flag doesn't pass through the validator_regexp") } else { return bytes.Join(res[1:], []byte("+")), nil } } // AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database. func (e Exercice) AddRawFlagKey(name string, help string, ignorecase bool, validator_regexp *string, raw_value []byte, choicescost int64) (FlagKey, error) { if ignorecase { raw_value = bytes.ToLower(raw_value) } // Check that raw value passes through the regexp if validator_regexp != nil { var err error if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value); err != nil { return FlagKey{}, err } } hash := getHashedFlag(raw_value) return e.AddFlagKey(name, help, ignorecase, validator_regexp, hash[:], choicescost) } // AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database. func (e Exercice) AddFlagKey(name string, help string, ignorecase bool, validator_regexp *string, checksum []byte, choicescost int64) (FlagKey, error) { // Check the regexp compile if validator_regexp != nil { if _, err := regexp.Compile(*validator_regexp); err != nil { return FlagKey{}, err } } if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?)", e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost); err != nil { return FlagKey{}, err } else if kid, err := res.LastInsertId(); err != nil { return FlagKey{}, err } else { return FlagKey{kid, e.Id, name, help, ignorecase, validator_regexp, checksum, choicescost}, nil } } // Update applies modifications back to the database. func (k FlagKey) Update() (int64, error) { if k.ValidatorRegexp != nil { if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil { return 0, err } } if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, type = ?, help = ?, ignorecase = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Help, k.IgnoreCase, k.ValidatorRegexp, k.Checksum, k.ChoicesCost, k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // Delete the flag from the database. func (k FlagKey) Delete() (int64, error) { if _, err := DBExec("DELETE FROM exercice_files_deps WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_flag_dep = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag = ? OR id_flag_dep = ?", k.Id, k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // AddDepend insert a new dependency to a given flag. func (k FlagKey) AddDepend(j Flag) (err error) { if d, ok := j.(FlagKey); ok { _, err = DBExec("INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)", k.Id, d.Id) } else if d, ok := j.(MCQ); ok { _, err = DBExec("INSERT INTO exercice_flags_omcq_deps (id_flag, id_mcq_dep) VALUES (?, ?)", k.Id, d.Id) } else { err = errors.New(fmt.Sprintf("Dependancy type for key (%T) not implemented for this flag.", j)) } return } // GetDepends retrieve the flag's dependency list. func (k FlagKey) GetDepends() ([]Flag, error) { var deps = make([]Flag, 0) if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_flags_deps WHERE id_flag = ?", k.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int64 if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, FlagKey{Id: d, IdExercice: k.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int64 if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, MCQ{Id: d, IdExercice: k.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } return deps, nil } // Check if the given val is the expected one for this flag. func (k FlagKey) Check(v interface{}) int { var val []byte if va, ok := v.([]byte); !ok { return -1 } else { val = va } if k.IgnoreCase { val = bytes.ToLower(val) } // Check that raw value passes through the regexp if k.ValidatorRegexp != nil { val, _ = ExecValidatorRegexp(*k.ValidatorRegexp, val) } // Check that the value is not empty if len(val) == 0 { return 1 } hash := getHashedFlag(val) if len(k.Checksum) != len(hash) { return 1 } for i := range hash { if k.Checksum[i] != hash[i] { return 1 } } return 0 } // FoundBy registers in the database that the given Team solved the flag. func (k FlagKey) FoundBy(t Team) { DBExec("INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now()) }