summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go.mod4
-rw-r--r--go.sum4
-rw-r--r--internal/pwhash/pwhash.go44
-rw-r--r--roseh.moe.go89
-rw-r--r--templates/login.html.template1
-rw-r--r--tools/finditers/finditers.go42
-rw-r--r--tools/hashpw/hashpw.go32
-rw-r--r--tools/secret-key/secret-key.go14
8 files changed, 38 insertions, 192 deletions
diff --git a/go.mod b/go.mod
index 5e702ba..4e975bb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,3 @@
module gitlab.com/rhogenson/roseh.moe
go 1.24.0
-
-require golang.org/x/term v0.35.0
-
-require golang.org/x/sys v0.36.0 // indirect
diff --git a/go.sum b/go.sum
index a7fae6a..e69de29 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +0,0 @@
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
-golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
diff --git a/internal/pwhash/pwhash.go b/internal/pwhash/pwhash.go
deleted file mode 100644
index c3c11ff..0000000
--- a/internal/pwhash/pwhash.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package pwhash
-
-import (
- "crypto/pbkdf2"
- "crypto/rand"
- "crypto/sha512"
- "crypto/subtle"
- "errors"
-)
-
-const defaultIter = 12504615 // from tools/finditers
-
-func HashIter(pw string, salt []byte, iter int) ([]byte, error) {
- return pbkdf2.Key(sha512.New, pw, salt, iter, 32)
-}
-
-func Hash(pw string) ([]byte, error) {
- buf := make([]byte, 40)
- salt := buf[32:]
- rand.Read(salt)
- hash, err := HashIter(pw, salt, defaultIter)
- if err != nil {
- return nil, err
- }
- copy(buf, hash)
- return buf, nil
-}
-
-var errBadPassword = errors.New("bad password")
-
-func Check(pwHash []byte, pw string) error {
- if len(pwHash) < 32 {
- return errBadPassword
- }
- wantHash, salt := pwHash[:32], pwHash[32:]
- gotHash, err := HashIter(pw, salt, defaultIter)
- if err != nil {
- return err
- }
- if subtle.ConstantTimeCompare(gotHash, wantHash) == 0 {
- return errBadPassword
- }
- return nil
-}
diff --git a/roseh.moe.go b/roseh.moe.go
index 77c78c1..9b5c1a6 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -9,6 +9,7 @@ import (
"embed"
"encoding/base64"
"encoding/binary"
+ "encoding/hex"
"flag"
"fmt"
"html/template"
@@ -19,8 +20,6 @@ import (
"strings"
"sync"
"time"
-
- "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
)
var (
@@ -40,12 +39,12 @@ 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 {
- notepadPassword, err = base64.RawURLEncoding.AppendDecode(nil, pw)
+ notepadPassword, err = hex.AppendDecode(nil, pw)
if err != nil {
return err
}
} else if key, ok := bytes.CutPrefix(line, []byte("secret-key=")); ok {
- secretKey, err = base64.RawURLEncoding.AppendDecode(nil, key)
+ secretKey, err = hex.AppendDecode(nil, key)
if err != nil {
return err
}
@@ -135,9 +134,10 @@ type wormholeTemplateArgs struct {
}
func wormhole(w http.ResponseWriter, r *http.Request) {
- buf := make([]byte, 20)
+ const nWords = 10
+ buf := make([]byte, 2*nWords)
rand.Read(buf)
- words := make([]string, 10)
+ words := make([]string, nWords)
for i := range words {
words[i] = wordList[binary.NativeEndian.Uint16(buf[2*i:])&0x1fff]
}
@@ -247,24 +247,22 @@ func wormholeReady(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "event: ready\ndata:\n\n")
}
-func sign(msg []byte, info string) []byte {
+func mac(msg []byte) []byte {
mac := hmac.New(sha256.New, secretKey)
- mac.Write(binary.AppendVarint(nil, int64(len(info))))
- io.WriteString(mac, info)
mac.Write(msg)
- return mac.Sum(msg)
+ return mac.Sum(nil)
+}
+
+func sign(msg []byte) []byte {
+ return append(msg, mac(msg)...)
}
-func verify(msg []byte, info string) ([]byte, bool) {
+func verify(msg []byte) ([]byte, bool) {
if len(msg) < sha256.Size {
return nil, false
}
msg, messageMAC := msg[:len(msg)-sha256.Size], msg[len(msg)-sha256.Size:]
- mac := hmac.New(sha256.New, secretKey)
- mac.Write(binary.AppendVarint(nil, int64(len(info))))
- io.WriteString(mac, info)
- mac.Write(msg)
- expectedMAC := mac.Sum(nil)
+ expectedMAC := mac(msg)
if !hmac.Equal(messageMAC, expectedMAC) {
return nil, false
}
@@ -276,27 +274,11 @@ func makeToken() (string, error) {
if err != nil {
return "", err
}
- return base64.RawURLEncoding.EncodeToString(sign(b, "auth")), nil
+ return base64.RawURLEncoding.EncodeToString(sign(b)), nil
}
const cookieExpiration = 180 * 24 * time.Hour
-func checkToken(token string) bool {
- authCookie, err := base64.RawURLEncoding.DecodeString(token)
- if err != nil {
- return false
- }
- msg, ok := verify(authCookie, "auth")
- if !ok {
- return false
- }
- var t time.Time
- if err := t.UnmarshalBinary(msg); err != nil {
- return false
- }
- return time.Since(t) < cookieExpiration
-}
-
func attachCookie(w http.ResponseWriter) error {
token, err := makeToken()
if err != nil {
@@ -339,10 +321,25 @@ func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
if err != nil {
return csrfToken, false
}
- if !checkToken(cookie.Value) {
+ authCookie, err := base64.RawURLEncoding.DecodeString(cookie.Value)
+ if err != nil {
return csrfToken, false
}
- attachCookie(w)
+ msg, ok := verify(authCookie)
+ if !ok {
+ return csrfToken, false
+ }
+ var t time.Time
+ if err := t.UnmarshalBinary(msg); err != nil {
+ return csrfToken, false
+ }
+ cookieAge := time.Since(t)
+ if cookieAge > cookieExpiration {
+ return csrfToken, false
+ }
+ if cookieAge > 24*time.Hour {
+ attachCookie(w)
+ }
return csrfToken, true
}
@@ -367,15 +364,11 @@ var (
type loginTemplateArgs struct {
CSRFToken string
- Redirect string
Error bool
}
-func executeLoginTemplate(w io.Writer, csrfToken, redirect string) {
- if err := loginTemplate.Execute(w, loginTemplateArgs{
- Redirect: base64.RawURLEncoding.EncodeToString(sign([]byte(redirect), "redirect")),
- CSRFToken: csrfToken,
- }); err != nil {
+func executeLoginTemplate(w io.Writer, csrfToken string) {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{CSRFToken: csrfToken}); err != nil {
log.Printf("Warning: login: %s", err)
}
}
@@ -385,10 +378,10 @@ func login(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad CSRF token", http.StatusBadRequest)
return
}
- if err := pwhash.Check(notepadPassword, r.FormValue("password")); err != nil {
+ hash := sha256.Sum256([]byte(r.FormValue("password")))
+ if subtle.ConstantTimeCompare(notepadPassword, hash[:]) == 0 {
if err := loginTemplate.Execute(w, loginTemplateArgs{
Error: true,
- Redirect: r.FormValue("redirect"),
CSRFToken: r.FormValue("csrf-token"),
}); err != nil {
log.Printf("Warning: login: %s", err)
@@ -396,13 +389,7 @@ func login(w http.ResponseWriter, r *http.Request) {
return
}
attachCookie(w)
- redirect := "/"
- if b, err := base64.RawURLEncoding.DecodeString(r.FormValue("redirect")); err == nil {
- if r, ok := verify(b, "redirect"); ok {
- redirect = string(r)
- }
- }
- http.Redirect(w, r, redirect, http.StatusSeeOther)
+ http.Redirect(w, r, "/notepad", http.StatusSeeOther)
}
var (
@@ -422,7 +409,7 @@ type notepadTemplateArgs struct {
func notepad(w http.ResponseWriter, r *http.Request) {
csrfToken, ok := cookieAuth(w, r)
if !ok {
- executeLoginTemplate(w, csrfToken, "/notepad")
+ executeLoginTemplate(w, csrfToken)
return
}
notepadContentsMu.Lock()
diff --git a/templates/login.html.template b/templates/login.html.template
index a197e8e..f08caf7 100644
--- a/templates/login.html.template
+++ b/templates/login.html.template
@@ -6,7 +6,6 @@
{{end}}
<form class="password-form" action="/login" method="post">
<input type="hidden" name="csrf-token" value="{{.CSRFToken}}">
- <input type="hidden" name="redirect" value="{{.Redirect}}">
<label class="password-label" for="password">Enter password</label>
<input id="password" class="password" type="password" name="password" autofocus>
</form>
diff --git a/tools/finditers/finditers.go b/tools/finditers/finditers.go
deleted file mode 100644
index 31a7e33..0000000
--- a/tools/finditers/finditers.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package main
-
-import (
- "encoding/hex"
- "fmt"
- "sort"
- "testing"
- "time"
-
- "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
-)
-
-func mustHex(t testing.TB, s string) []byte {
- t.Helper()
- b, err := hex.DecodeString(s)
- if err != nil {
- t.Fatalf("Invalid hex %q: %s", s, err)
- }
- return b
-}
-
-var iterations int
-
-func BenchmarkHashIter(b *testing.B) {
- const pw = "atypical evasion foyer roulette throng awning stability exchange humorless vowed"
- salt := mustHex(b, "4250af599e07cde7")
- for b.Loop() {
- pwhash.HashIter(pw, salt, iterations)
- }
-}
-
-func main() {
- const targetDuration = 5 * time.Second
- for iterations = 4096; time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) < targetDuration; iterations *= 2 {
- }
- lo := iterations / 2
- hi := iterations
- fmt.Println(lo + sort.Search(hi-lo, func(i int) bool {
- iterations = lo + i
- return time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) > targetDuration
- }))
-}
diff --git a/tools/hashpw/hashpw.go b/tools/hashpw/hashpw.go
deleted file mode 100644
index c668622..0000000
--- a/tools/hashpw/hashpw.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package main
-
-import (
- "encoding/base64"
- "fmt"
- "os"
-
- "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
- "golang.org/x/term"
-)
-
-func hashpw() error {
- fmt.Fprint(os.Stderr, "Enter password: ")
- password, err := term.ReadPassword(int(os.Stdin.Fd()))
- fmt.Fprintln(os.Stderr)
- if err != nil {
- return err
- }
- hash, err := pwhash.Hash(string(password))
- if err != nil {
- return err
- }
- fmt.Printf("notepad-password=%s\n", base64.RawURLEncoding.EncodeToString(hash))
- return nil
-}
-
-func main() {
- if err := hashpw(); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
-}
diff --git a/tools/secret-key/secret-key.go b/tools/secret-key/secret-key.go
deleted file mode 100644
index 969828a..0000000
--- a/tools/secret-key/secret-key.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package main
-
-import (
- "crypto/rand"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
-)
-
-func main() {
- buf := make([]byte, sha256.BlockSize)
- rand.Read(buf)
- fmt.Printf("secret-key=%s\n", base64.RawURLEncoding.EncodeToString(buf))
-}