66 lines
2.4 KiB
Go
66 lines
2.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
)
|
|
|
|
// CSRFFieldName ist der Name des versteckten Formularfelds in den Templates.
|
|
const CSRFFieldName = "csrf_token"
|
|
|
|
// CSRFHeaderName erlaubt es, das Token bei einem fetch()-Aufruf im Header statt
|
|
// im Formular zu schicken.
|
|
const CSRFHeaderName = "X-CSRF-Token"
|
|
|
|
// CSRFToken berechnet das Token zu einer Sitzung.
|
|
//
|
|
// # Wogegen schützt das?
|
|
//
|
|
// CSRF ("Cross-Site Request Forgery") nutzt aus, dass der Browser Cookies
|
|
// automatisch mitschickt. Eine fremde Seite kann ein Formular auf unseren Server
|
|
// abschicken lassen, und für den Server sieht es aus wie eine Aktion des
|
|
// angemeldeten Nutzers:
|
|
//
|
|
// <form method="POST" action="https://munin.intern/projects/x/delete">
|
|
// <script>document.forms[0].submit()</script>
|
|
//
|
|
// Das Token unterbindet das: Die fremde Seite kennt es nicht und kann es wegen
|
|
// der Same-Origin-Policy des Browsers auch nicht auslesen.
|
|
//
|
|
// Das Präfix "csrf:" sorgt für Domänentrennung — dasselbe Geheimnis signiert auch
|
|
// Session-Cookies, und eine Signatur aus dem einen Zusammenhang darf im anderen
|
|
// nicht gelten.
|
|
func (s *Signer) CSRFToken(subject string) string {
|
|
return s.sign("csrf:" + subject)
|
|
}
|
|
|
|
// ValidCSRFToken prüft ein übermitteltes Token gegen die Sitzung.
|
|
func (s *Signer) ValidCSRFToken(token, subject string) bool {
|
|
expected := s.CSRFToken(subject)
|
|
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
|
|
}
|
|
|
|
// CSRFTokenFromRequest holt das Token aus dem Formular oder dem Header.
|
|
// SignHint signiert eine Erfolgsmeldung, die über einen Redirect in der
|
|
// Adresszeile mitreist (siehe withHint in internal/web). Ohne Signatur ließe
|
|
// sich mit einem präparierten Link ein beliebiger Text als Meldung in die
|
|
// Oberfläche schmuggeln — etwa „Bitte den Key an … schicken".
|
|
//
|
|
// Das Präfix "hint:" trennt diese Signaturen von Sitzungen und CSRF-Tokens:
|
|
// Eine signierte Meldung taugt nie als gültige Sitzung oder umgekehrt.
|
|
func (s *Signer) SignHint(message string) string {
|
|
return s.sign("hint:" + message)
|
|
}
|
|
|
|
// ValidHint prüft, ob signature zu message passt — in konstanter Zeit, wie bei
|
|
// den anderen Vergleichen mit Geheimnissen.
|
|
func (s *Signer) ValidHint(message, signature string) bool {
|
|
return subtle.ConstantTimeCompare([]byte(signature), []byte(s.SignHint(message))) == 1
|
|
}
|
|
|
|
func CSRFTokenFromRequest(r *http.Request) string {
|
|
if token := r.Header.Get(CSRFHeaderName); token != "" {
|
|
return token
|
|
}
|
|
return r.FormValue(CSRFFieldName)
|
|
}
|