feat: add resilient updates and access controls
Container-Image veröffentlichen / container (push) Has been cancelled
Container-Image veröffentlichen / container (push) Has been cancelled
This commit is contained in:
1 parent
a000152423
commit
f02ec9977c
41 files changed
+3277
-151
No files matched your search
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
@@ -21,6 +22,14 @@ const (
|
||||
argonTime = 3
|
||||
argonThreads = 2
|
||||
argonKeyLen = 32
|
||||
|
||||
PasswordMinLength = 15
|
||||
PasswordMaxLength = 128
|
||||
|
||||
// PasswordTimingHash is a non-secret, precomputed Argon2id hash used only
|
||||
// to equalize failed-login work when a username does not exist. Keeping it
|
||||
// static avoids allocating the full Argon2 workspace during every startup.
|
||||
PasswordTimingHash = "argon2id$v=19$m=65536,t=3,p=2$qrFe78U1tJk+AQzTg0+BHw$n4NjN4mcmJmVrJa7xB3M2AYMDX3PBs+5Y7iYXb/dR5Q"
|
||||
)
|
||||
|
||||
type Cipher struct {
|
||||
@@ -129,8 +138,30 @@ func HashToken(token string) string {
|
||||
}
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
if len(password) < 12 {
|
||||
return "", errors.New("password must contain at least 12 characters")
|
||||
return hashPassword(password, PasswordMinLength)
|
||||
}
|
||||
|
||||
// HashProvisionedPassword hashes the initial password chosen by an
|
||||
// administrator when creating another account. Product policy deliberately
|
||||
// leaves its length to that administrator while still rejecting empty,
|
||||
// overlong, or invalid UTF-8 input.
|
||||
func HashProvisionedPassword(password string) (string, error) {
|
||||
return hashPassword(password, 1)
|
||||
}
|
||||
|
||||
func hashPassword(password string, minimumLength int) (string, error) {
|
||||
if !utf8.ValidString(password) {
|
||||
return "", errors.New("Passwort muss gültiger UTF-8-Text sein")
|
||||
}
|
||||
length := utf8.RuneCountInString(password)
|
||||
if length < minimumLength {
|
||||
if minimumLength == 1 {
|
||||
return "", errors.New("Passwort darf nicht leer sein")
|
||||
}
|
||||
return "", fmt.Errorf("Passwort muss mindestens %d Zeichen enthalten", minimumLength)
|
||||
}
|
||||
if length > PasswordMaxLength {
|
||||
return "", fmt.Errorf("Passwort darf höchstens %d Zeichen enthalten", PasswordMaxLength)
|
||||
}
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
@@ -142,6 +173,9 @@ func HashPassword(password string) (string, error) {
|
||||
}
|
||||
|
||||
func VerifyPassword(encoded, password string) bool {
|
||||
if !utf8.ValidString(password) || utf8.RuneCountInString(password) > PasswordMaxLength {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(encoded, "$")
|
||||
if len(parts) != 5 || parts[0] != "argon2id" || parts[1] != "v=19" {
|
||||
return false
|
||||
@@ -152,12 +186,18 @@ func VerifyPassword(encoded, password string) bool {
|
||||
if _, err := fmt.Sscanf(parts[2], "m=%d,t=%d,p=%d", &memory, &iterations, &threads); err != nil {
|
||||
return false
|
||||
}
|
||||
if parts[2] != fmt.Sprintf("m=%d,t=%d,p=%d", memory, iterations, threads) || memory != argonMemory || iterations != argonTime || threads != argonThreads {
|
||||
return false
|
||||
}
|
||||
if len(parts[3]) != base64.RawStdEncoding.EncodedLen(16) || len(parts[4]) != base64.RawStdEncoding.EncodedLen(argonKeyLen) {
|
||||
return false
|
||||
}
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||
if err != nil {
|
||||
if err != nil || len(salt) != 16 {
|
||||
return false
|
||||
}
|
||||
want, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
if err != nil || len(want) != argonKeyLen {
|
||||
return false
|
||||
}
|
||||
got := argon2.IDKey([]byte(password), salt, iterations, memory, threads, uint32(len(want)))
|
||||
|
||||
Reference in new issue
Block a user