2021-09-27 13:45:51 +00:00
|
|
|
package conf
|
|
|
|
|
|
|
|
import (
|
2024-01-13 11:49:08 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
2021-09-27 13:45:51 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
2024-01-13 11:49:08 +00:00
|
|
|
|
|
|
|
"github.com/matthewhartstonge/argon2"
|
2021-09-27 13:45:51 +00:00
|
|
|
)
|
|
|
|
|
2024-01-13 11:49:08 +00:00
|
|
|
var (
|
|
|
|
rePlainCredential = regexp.MustCompile(`^[a-zA-Z0-9!\$\(\)\*\+\.;<=>\[\]\^_\-\{\}@#&]+$`)
|
|
|
|
reBase64 = regexp.MustCompile(`^sha256:[a-zA-Z0-9\+/=]+$`)
|
|
|
|
)
|
2021-09-27 13:45:51 +00:00
|
|
|
|
2024-01-13 11:49:08 +00:00
|
|
|
const plainCredentialSupportedChars = "A-Z,0-9,!,$,(,),*,+,.,;,<,=,>,[,],^,_,-,\",\",@,#,&"
|
2021-09-27 13:45:51 +00:00
|
|
|
|
2024-02-20 18:35:35 +00:00
|
|
|
func sha256Base64(in string) string {
|
|
|
|
h := sha256.New()
|
|
|
|
h.Write([]byte(in))
|
|
|
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
2021-09-27 13:45:51 +00:00
|
|
|
|
2024-02-20 18:35:35 +00:00
|
|
|
// Credential is a parameter that is used as username or password.
|
|
|
|
type Credential string
|
|
|
|
|
2022-06-21 11:41:15 +00:00
|
|
|
// MarshalJSON implements json.Marshaler.
|
2021-09-27 13:45:51 +00:00
|
|
|
func (d Credential) MarshalJSON() ([]byte, error) {
|
2024-02-20 18:35:35 +00:00
|
|
|
return json.Marshal(string(d))
|
2021-09-27 13:45:51 +00:00
|
|
|
}
|
|
|
|
|
2022-06-21 11:41:15 +00:00
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
2021-09-27 13:45:51 +00:00
|
|
|
func (d *Credential) UnmarshalJSON(b []byte) error {
|
|
|
|
var in string
|
|
|
|
if err := json.Unmarshal(b, &in); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-20 18:35:35 +00:00
|
|
|
*d = Credential(in)
|
2021-09-27 13:45:51 +00:00
|
|
|
|
2024-01-23 19:52:05 +00:00
|
|
|
return d.validate()
|
2021-09-27 13:45:51 +00:00
|
|
|
}
|
2021-10-11 09:46:40 +00:00
|
|
|
|
2023-10-07 21:32:15 +00:00
|
|
|
// UnmarshalEnv implements env.Unmarshaler.
|
|
|
|
func (d *Credential) UnmarshalEnv(_ string, v string) error {
|
|
|
|
return d.UnmarshalJSON([]byte(`"` + v + `"`))
|
2021-10-11 09:46:40 +00:00
|
|
|
}
|
2024-01-13 11:49:08 +00:00
|
|
|
|
|
|
|
// IsSha256 returns true if the credential is a sha256 hash.
|
2024-02-20 18:35:35 +00:00
|
|
|
func (d Credential) IsSha256() bool {
|
|
|
|
return strings.HasPrefix(string(d), "sha256:")
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsArgon2 returns true if the credential is an argon2 hash.
|
2024-02-20 18:35:35 +00:00
|
|
|
func (d Credential) IsArgon2() bool {
|
|
|
|
return strings.HasPrefix(string(d), "argon2:")
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsHashed returns true if the credential is a sha256 or argon2 hash.
|
2024-02-20 18:35:35 +00:00
|
|
|
func (d Credential) IsHashed() bool {
|
2024-01-13 11:49:08 +00:00
|
|
|
return d.IsSha256() || d.IsArgon2()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check returns true if the given value matches the credential.
|
2024-02-20 18:35:35 +00:00
|
|
|
func (d Credential) Check(guess string) bool {
|
2024-01-13 11:49:08 +00:00
|
|
|
if d.IsSha256() {
|
2024-02-20 18:35:35 +00:00
|
|
|
return string(d)[len("sha256:"):] == sha256Base64(guess)
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
2024-02-20 18:35:35 +00:00
|
|
|
|
2024-01-13 11:49:08 +00:00
|
|
|
if d.IsArgon2() {
|
|
|
|
// TODO: remove matthewhartstonge/argon2 when this PR gets merged into mainline Go:
|
|
|
|
// https://go-review.googlesource.com/c/crypto/+/502515
|
2024-02-20 18:35:35 +00:00
|
|
|
ok, err := argon2.VerifyEncoded([]byte(guess), []byte(string(d)[len("argon2:"):]))
|
2024-01-13 11:49:08 +00:00
|
|
|
return ok && err == nil
|
|
|
|
}
|
2024-02-20 18:35:35 +00:00
|
|
|
|
|
|
|
if d != "" {
|
|
|
|
return string(d) == guess
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
2024-02-20 18:35:35 +00:00
|
|
|
return true
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
|
|
|
|
2024-02-20 18:35:35 +00:00
|
|
|
func (d Credential) validate() error {
|
|
|
|
if d != "" {
|
2024-01-23 19:52:05 +00:00
|
|
|
switch {
|
|
|
|
case d.IsSha256():
|
2024-02-20 18:35:35 +00:00
|
|
|
if !reBase64.MatchString(string(d)) {
|
2024-01-23 19:52:05 +00:00
|
|
|
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
|
2024-02-20 18:35:35 +00:00
|
|
|
_, err := argon2.Decode([]byte(string(d)[len("argon2:"):]))
|
2024-01-23 19:52:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid argon2 hash: %w", err)
|
|
|
|
}
|
|
|
|
default:
|
2024-02-20 18:35:35 +00:00
|
|
|
if !rePlainCredential.MatchString(string(d)) {
|
2024-01-23 19:52:05 +00:00
|
|
|
return fmt.Errorf("credential contains unsupported characters. Supported are: %s", plainCredentialSupportedChars)
|
|
|
|
}
|
2024-01-13 11:49:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|