diff options
Diffstat (limited to 'roseh.moe.go')
| -rw-r--r-- | roseh.moe.go | 146 |
1 files changed, 36 insertions, 110 deletions
diff --git a/roseh.moe.go b/roseh.moe.go index 3c5e7a9..6624706 100644 --- a/roseh.moe.go +++ b/roseh.moe.go @@ -2,12 +2,7 @@ package main import ( "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/hkdf" - "crypto/hmac" "crypto/rand" - "crypto/sha256" "crypto/subtle" "embed" "encoding/base64" @@ -25,7 +20,7 @@ import ( "sync" "time" - "gitlab.com/rhogenson/roseh.moe/internal/pwhash" + "gitlab.com/rhogenson/roseh.moe/internal/cryptoutil" ) var ( @@ -37,12 +32,11 @@ var ( ) var ( - notepadPassword []byte - notepadPasswordSalt []byte - privateKey []byte + notepadPassword cryptoutil.PasswordHash + secretKey cryptoutil.HMACKey encryptionKeyMu sync.Mutex - encryptionKey []byte + encryptionKey cryptoutil.EncryptionKey ) func loadSecrets() error { @@ -52,20 +46,19 @@ func loadSecrets() error { } for _, line := range bytes.Split(bytes.TrimSuffix(secrets, []byte("\n")), []byte("\n")) { if pw, ok := bytes.CutPrefix(line, []byte("notepad-password=")); ok { - buf := make([]byte, hex.DecodedLen(len(pw))) - if _, err := hex.Decode(buf, pw); err != nil { - return err + if hex.DecodedLen(len(pw)) != cryptoutil.PasswordHashSize { + return fmt.Errorf("invalid PBKDF2-SHA256 hash") } - if len(buf) < pwhash.SaltLen { - return fmt.Errorf("bad password hash") + notepadPassword = make(cryptoutil.PasswordHash, hex.DecodedLen(len(pw))) + if _, err := hex.Decode(notepadPassword, pw); err != nil { + return err } - notepadPasswordSalt, notepadPassword = buf[:pwhash.SaltLen], buf[pwhash.SaltLen:] } else if key, ok := bytes.CutPrefix(line, []byte("secret-key=")); ok { - if hex.DecodedLen(len(key)) != sha256.Size { + if hex.DecodedLen(len(key)) != cryptoutil.HMACKeySize { return fmt.Errorf("invalid HMAC-SHA256 key") } - privateKey = make([]byte, hex.DecodedLen(len(key))) - if _, err := hex.Decode(privateKey, key); err != nil { + secretKey = make(cryptoutil.HMACKey, hex.DecodedLen(len(key))) + if _, err := hex.Decode(secretKey, key); err != nil { return err } } @@ -73,78 +66,6 @@ func loadSecrets() error { return nil } -func sign(msg []byte) []byte { - mac := hmac.New(sha256.New, privateKey) - mac.Write(msg) - return mac.Sum(msg) -} - -func verify(msg []byte) ([]byte, bool) { - if len(msg) < sha256.Size { - return nil, false - } - msg, sig := msg[:len(msg)-sha256.Size], msg[len(msg)-sha256.Size:] - mac := hmac.New(sha256.New, privateKey) - mac.Write(msg) - return msg, hmac.Equal(sig, mac.Sum(nil)) -} - -var errNoKey = errors.New("not logged in") - -const ( - aesKeyLen = 32 - nonceLen = aes.BlockSize - certLen = sha256.Size -) - -func encrypt(msg string) ([]byte, error) { - encryptionKeyMu.Lock() - key := encryptionKey - encryptionKeyMu.Unlock() - if key == nil { - return nil, errNoKey - } - block, err := aes.NewCipher(key[:aesKeyLen]) - if err != nil { - return nil, err - } - buf := make([]byte, nonceLen+len(msg)+certLen) - nonce := buf[:nonceLen] - rand.Read(nonce) - cipher.NewCTR(block, nonce).XORKeyStream(buf[nonceLen:], []byte(msg)) - cipherText := buf[:nonceLen+len(msg)] - mac := hmac.New(sha256.New, key[aesKeyLen:]) - mac.Write(cipherText) - return mac.Sum(cipherText), nil -} - -func decrypt(msg []byte) (string, error) { - encryptionKeyMu.Lock() - key := encryptionKey - encryptionKeyMu.Unlock() - if key == nil { - return "", errNoKey - } - if len(msg) < certLen+nonceLen { - return "", fmt.Errorf("message too short") - } - msg, messageMAC := msg[:len(msg)-certLen], msg[len(msg)-certLen:] - mac := hmac.New(sha256.New, key[aesKeyLen:]) - mac.Write(msg) - expectedMAC := mac.Sum(nil) - if !hmac.Equal(messageMAC, expectedMAC) { - return "", fmt.Errorf("bad signature") - } - block, err := aes.NewCipher(key[:aesKeyLen]) - if err != nil { - return "", err - } - nonce, msg := msg[:nonceLen], msg[nonceLen:] - buf := make([]byte, len(msg)) - cipher.NewCTR(block, nonce).XORKeyStream(buf, msg) - return string(buf), nil -} - var ( //go:embed templates/404.html.template notFoundString string @@ -219,7 +140,7 @@ func attachCookie(w http.ResponseWriter) error { } http.SetCookie(w, &http.Cookie{ Name: "auth", - Value: base64.RawStdEncoding.EncodeToString(sign(nowBytes)), + Value: base64.RawStdEncoding.EncodeToString(secretKey.Sign(nowBytes)), Path: "/notepad", Expires: time.Now().Add(cookieExpiration), Secure: true, @@ -241,7 +162,7 @@ func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) { if err != nil { return "", false } - msg, ok := verify(authCookie) + msg, ok := secretKey.Verify(authCookie) if !ok { return "", false } @@ -284,12 +205,8 @@ type loginTemplateArgs struct { } func login(w http.ResponseWriter, r *http.Request) { - rawKey, pwHash, err := pwhash.Hash(r.FormValue("password"), notepadPasswordSalt) + rawKey, err := cryptoutil.CheckPassword(r.FormValue("password"), notepadPassword) if err != nil { - http.Error(w, fmt.Sprintf("Unable to hash password: %s", err), http.StatusInternalServerError) - return - } - if subtle.ConstantTimeCompare(pwHash[:], notepadPassword) == 0 { if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true}); err != nil { log.Printf("Warning: login: %s", err) } @@ -299,7 +216,7 @@ func login(w http.ResponseWriter, r *http.Request) { currentKey := encryptionKey encryptionKeyMu.Unlock() if currentKey == nil { - key, err := hkdf.Expand(sha256.New, rawKey, "encrypt", aesKeyLen+certLen) + key, err := rawKey.EncryptionKey() if err != nil { http.Error(w, fmt.Sprintf("failed to derive encryption key: %s", err), http.StatusInternalServerError) return @@ -314,16 +231,16 @@ func login(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/notepad", http.StatusSeeOther) } -func readNotepad() (string, error) { +func readNotepad(key cryptoutil.EncryptionKey) (string, error) { encrypted, err := os.ReadFile(*notepadFile) if err != nil { return "", err } - decrypted, err := decrypt(encrypted) + decrypted, err := key.Decrypt(encrypted) if err != nil { return "", err } - return decrypted, nil + return string(decrypted), nil } var ( @@ -345,14 +262,17 @@ func notepad(w http.ResponseWriter, r *http.Request) { } return } - currentContent, err := readNotepad() - if err != nil { - if err == errNoKey { - if err := loginTemplate.Execute(w, loginTemplateArgs{}); err != nil { - log.Printf("Warning: login: %s", err) - } - return + encryptionKeyMu.Lock() + key := encryptionKey + encryptionKeyMu.Unlock() + if key == nil { + if err := loginTemplate.Execute(w, loginTemplateArgs{}); err != nil { + log.Printf("Warning: login: %s", err) } + return + } + currentContent, err := readNotepad(key) + if err != nil { currentContent = fmt.Sprintf("Error reading notepad file: %s", err) } if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent, CSRFToken: csrfToken}); err != nil { @@ -367,7 +287,13 @@ func saveNote(w http.ResponseWriter, r *http.Request) error { if _, ok := cookieAuth(w, r); !ok { return fmt.Errorf("not logged in") } - encrypted, err := encrypt(r.FormValue("content")) + encryptionKeyMu.Lock() + key := encryptionKey + encryptionKeyMu.Unlock() + if key == nil { + return errors.New("not logged in") + } + encrypted, err := key.Encrypt([]byte(r.FormValue("content"))) if err != nil { return err } |
