summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-09-23 23:56:06 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-09-23 23:56:06 -0700
commit83954192ebc1b5987c7a34dcb45d82aafe2ebd83 (patch)
tree57b3469de7cc64df10469fe33dc6442928604c00
parent95df5d765a3a71a4ff75d598fbba1bceb6fa2e6c (diff)
downloadroseh.moe-83954192ebc1b5987c7a34dcb45d82aafe2ebd83.tar.zst
Use HKDF to expand key into both auth and encrypt
I guess having the password hash depend on the encryption key isn't a great idea. Let's make them independent using hkdf
-rw-r--r--internal/pwhash/pwhash.go24
-rw-r--r--roseh.moe.go56
2 files changed, 57 insertions, 23 deletions
diff --git a/internal/pwhash/pwhash.go b/internal/pwhash/pwhash.go
index 97ce199..f343d70 100644
--- a/internal/pwhash/pwhash.go
+++ b/internal/pwhash/pwhash.go
@@ -1,24 +1,36 @@
package pwhash
import (
+ "crypto/hkdf"
"crypto/pbkdf2"
"crypto/sha256"
"crypto/sha512"
+ "fmt"
)
-const SaltLen = 32
+const SaltLen = sha256.Size + sha512.Size
const defaultIterations = 3670016 // from cmd/finditers
func HashIter(password string, salt []byte, iter int) ([]byte, error) {
- return pbkdf2.Key(sha256.New, password, salt, iter, 32)
+ return pbkdf2.Key(sha256.New, password, salt, iter, sha256.Size)
}
-func Hash(password string, salt []byte) (key, pwhash []byte, err error) {
- hashed, err := HashIter(password, salt, defaultIterations)
+func Hash(password string, salt []byte) (key, pwHash []byte, err error) {
+ if len(salt) != SaltLen {
+ return nil, nil, fmt.Errorf("bad salt size")
+ }
+ hashedPassword, err := HashIter(password, salt[:sha256.Size], defaultIterations)
+ if err != nil {
+ return nil, nil, err
+ }
+ key, err = hkdf.Extract(sha512.New, hashedPassword, salt[sha256.Size:])
+ if err != nil {
+ return nil, nil, err
+ }
+ pwHash, err = hkdf.Expand(sha512.New, key, "auth", sha512.Size)
if err != nil {
return nil, nil, err
}
- sha := sha512.Sum512(hashed)
- return hashed, sha[:], nil
+ return key, pwHash, nil
}
diff --git a/roseh.moe.go b/roseh.moe.go
index 99d1bed..c8d61e9 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
+ "crypto/hkdf"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
@@ -54,6 +55,9 @@ func loadSecrets() error {
if _, err := hex.Decode(buf, pw); err != nil {
return err
}
+ if len(buf) < pwhash.SaltLen {
+ return fmt.Errorf("bad password hash")
+ }
notepadPasswordSalt, notepadPassword = buf[:pwhash.SaltLen], buf[pwhash.SaltLen:]
} else if key, ok := bytes.CutPrefix(line, []byte("secret-key=")); ok {
if hex.DecodedLen(len(key)) != sha512.Size256 {
@@ -155,9 +159,25 @@ func attachCookie(w http.ResponseWriter) error {
return nil
}
-func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
+func attachCSRFToken(w http.ResponseWriter) string {
const csrfTokenLen = 32
+ buf := make([]byte, csrfTokenLen)
+ rand.Read(buf)
+ csrfToken := base64.RawStdEncoding.EncodeToString(buf)
+ http.SetCookie(w, &http.Cookie{
+ Name: "csrf-token",
+ Value: csrfToken,
+ Path: "/notepad",
+ Secure: true,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ Partitioned: true,
+ })
+ return csrfToken
+}
+
+func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
cookie, err := r.Cookie("auth")
if err != nil {
return "", false
@@ -188,18 +208,7 @@ func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
if csrfToken, err := r.Cookie("csrf-token"); err == nil {
return csrfToken.Value, true
}
- buf := make([]byte, csrfTokenLen)
- rand.Read(buf)
- csrfToken := base64.RawStdEncoding.EncodeToString(buf)
- http.SetCookie(w, &http.Cookie{
- Name: "csrf-token",
- Value: csrfToken,
- Path: "/notepad",
- Secure: true,
- HttpOnly: true,
- SameSite: http.SameSiteStrictMode,
- Partitioned: true,
- })
+ csrfToken := attachCSRFToken(w)
return csrfToken, true
}
@@ -215,7 +224,9 @@ type loginTemplateArgs struct {
}
func login(w http.ResponseWriter, r *http.Request) {
- key, pwHash, err := pwhash.Hash(r.FormValue("password"), notepadPasswordSalt)
+ const aesKeySize = 32
+
+ rawKey, pwHash, err := pwhash.Hash(r.FormValue("password"), notepadPasswordSalt)
if err != nil {
http.Error(w, fmt.Sprintf("Unable to hash password: %s", err), http.StatusInternalServerError)
return
@@ -227,11 +238,22 @@ func login(w http.ResponseWriter, r *http.Request) {
return
}
encryptionKeyMu.Lock()
- if encryptionKey == nil {
- encryptionKey = key
- }
+ currentKey := encryptionKey
encryptionKeyMu.Unlock()
+ if currentKey == nil {
+ key, err := hkdf.Expand(sha512.New, rawKey, "encrypt", aesKeySize)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Unable to derive encryption key: %s", err), http.StatusInternalServerError)
+ return
+ }
+ encryptionKeyMu.Lock()
+ if encryptionKey == nil {
+ encryptionKey = key
+ }
+ encryptionKeyMu.Unlock()
+ }
attachCookie(w)
+ attachCSRFToken(w)
http.Redirect(w, r, r.FormValue("redirect"), http.StatusSeeOther)
}