mirror of
https://github.com/bluenviron/mediamtx.git
synced 2026-01-09 11:52:02 -08:00
* Add argon2 credential hash support * update README, tests and documentation --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
123 lines
3.2 KiB
Go
123 lines
3.2 KiB
Go
package conf
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/matthewhartstonge/argon2"
|
|
)
|
|
|
|
var (
|
|
rePlainCredential = regexp.MustCompile(`^[a-zA-Z0-9!\$\(\)\*\+\.;<=>\[\]\^_\-\{\}@#&]+$`)
|
|
reBase64 = regexp.MustCompile(`^sha256:[a-zA-Z0-9\+/=]+$`)
|
|
)
|
|
|
|
const plainCredentialSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,\",\",@,#,&"
|
|
|
|
// Credential is a parameter that is used as username or password.
|
|
type Credential struct {
|
|
value string
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (d Credential) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(d.value)
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (d *Credential) UnmarshalJSON(b []byte) error {
|
|
var in string
|
|
if err := json.Unmarshal(b, &in); err != nil {
|
|
return err
|
|
}
|
|
|
|
*d = Credential{
|
|
value: in,
|
|
}
|
|
|
|
return d.validateConfig()
|
|
}
|
|
|
|
// UnmarshalEnv implements env.Unmarshaler.
|
|
func (d *Credential) UnmarshalEnv(_ string, v string) error {
|
|
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
|
}
|
|
|
|
// GetValue returns the value of the credential.
|
|
func (d *Credential) GetValue() string {
|
|
return d.value
|
|
}
|
|
|
|
// IsEmpty returns true if the credential is not configured.
|
|
func (d *Credential) IsEmpty() bool {
|
|
return d.value == ""
|
|
}
|
|
|
|
// IsSha256 returns true if the credential is a sha256 hash.
|
|
func (d *Credential) IsSha256() bool {
|
|
return d.value != "" && strings.HasPrefix(d.value, "sha256:")
|
|
}
|
|
|
|
// IsArgon2 returns true if the credential is an argon2 hash.
|
|
func (d *Credential) IsArgon2() bool {
|
|
return d.value != "" && strings.HasPrefix(d.value, "argon2:")
|
|
}
|
|
|
|
// IsHashed returns true if the credential is a sha256 or argon2 hash.
|
|
func (d *Credential) IsHashed() bool {
|
|
return d.IsSha256() || d.IsArgon2()
|
|
}
|
|
|
|
func sha256Base64(in string) string {
|
|
h := sha256.New()
|
|
h.Write([]byte(in))
|
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
// Check returns true if the given value matches the credential.
|
|
func (d *Credential) Check(guess string) bool {
|
|
if d.IsSha256() {
|
|
return d.value[len("sha256:"):] == sha256Base64(guess)
|
|
}
|
|
if d.IsArgon2() {
|
|
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
|
// https://go-review.googlesource.com/c/crypto/+/502515
|
|
ok, err := argon2.VerifyEncoded([]byte(guess), []byte(d.value[len("argon2:"):]))
|
|
return ok && err == nil
|
|
}
|
|
if d.IsEmpty() {
|
|
// when no credential is set, any value is valid
|
|
return true
|
|
}
|
|
|
|
return d.value == guess
|
|
}
|
|
|
|
func (d *Credential) validateConfig() error {
|
|
if d.IsEmpty() {
|
|
return nil
|
|
}
|
|
|
|
switch {
|
|
case d.IsSha256():
|
|
if !reBase64.MatchString(d.value) {
|
|
return fmt.Errorf("credential contains unsupported characters, sha256 hash must be base64 encoded")
|
|
}
|
|
case d.IsArgon2():
|
|
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
|
// https://go-review.googlesource.com/c/crypto/+/502515
|
|
_, err := argon2.Decode([]byte(d.value[len("argon2:"):]))
|
|
if err != nil {
|
|
return fmt.Errorf("invalid argon2 hash: %w", err)
|
|
}
|
|
default:
|
|
if !rePlainCredential.MatchString(d.value) {
|
|
return fmt.Errorf("credential contains unsupported characters. Supported are: %s", plainCredentialSupportedChars)
|
|
}
|
|
}
|
|
return nil
|
|
}
|