More work on getting auth fetch verification working
This commit is contained in:
parent
7eac1db475
commit
9957ba8302
12 changed files with 434 additions and 205 deletions
|
@ -1,15 +1,28 @@
|
|||
package webmiddleware
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
const signatureRegexString = `keyId=([a-zA-Z0-9_\-\.:@/\?&=#%\+\[\]!$\(\)\*,;]+),headers="([a-z0-9-_\(\) ]+)",(?:algorithm="([a-z0-9-])+",)?signature="(.+)"`
|
||||
const signatureRegexString = `keyId="([a-zA-Z0-9_\-\.:@\/\?&=#%\+\[\]!$\(\)\*,;]+)",(?:algorithm="([a-z0-9-]+)",)?headers="([a-z0-9-_\(\) ]+)",(?:algorithm="([a-z0-9-]+)",)?signature="(.+)"`
|
||||
|
||||
var publicPaths = []*regexp.Regexp{
|
||||
regexp.MustCompile(`/\.well-known/.+^`),
|
||||
|
@ -27,6 +40,9 @@ var signatureRegex = regexp.MustCompile(signatureRegexString)
|
|||
func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuilder {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
log.Info().Msg("AuthFetch middleware")
|
||||
defer log.Info().Msg("AuhFetch completed")
|
||||
path := r.URL.Path
|
||||
// Check always open path first
|
||||
for _, re := range publicPaths {
|
||||
|
@ -35,17 +51,36 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
return
|
||||
}
|
||||
}
|
||||
// WARN: This check could potentially pose a security risk where an AP request
|
||||
// could get around providing a valid signature by using the server actor's
|
||||
// ID somewhere in the path.
|
||||
// However, I suspect that this risk is mostly mitigated by Go's router
|
||||
// already cleaning paths up and the only other target being one specific
|
||||
// note that most likely won't ever even exist due to the large random value space
|
||||
// offered by UUIDs
|
||||
// So either you get the server actor, which is intended behaviour, or access to
|
||||
// one specific note that most likely won't even exist
|
||||
|
||||
// FIXME: Re-enable once implementation of actual verification is stable
|
||||
// if strings.Contains(path, storage.ServerActorId) {
|
||||
// log.Info().Msg("Server actor requested, no auth")
|
||||
// h.ServeHTTP(w, r)
|
||||
// return
|
||||
// }
|
||||
// Not an always open path, check methods
|
||||
if r.Method == "GET" && !forGet {
|
||||
log.Info().Msg("Get request to AP resources don't need signature")
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
} else if !forGet && !forNonGet {
|
||||
log.Info().Msg("Requests to AP resources don't need signature")
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// TODO: Perform check here
|
||||
log.Info().Msg("Need signature for AP request")
|
||||
signatureHeader := r.Header.Get("Signature")
|
||||
if signatureHeader == "" {
|
||||
log.Info().Msg("Received AP request without signature header where one is required")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
|
@ -56,8 +91,14 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
)
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Str("signature-header", signatureHeader).
|
||||
Msg("Signature header of inbound AP request")
|
||||
match := signatureRegex.FindStringSubmatch(signatureHeader)
|
||||
if len(match) <= 1 {
|
||||
log.Info().
|
||||
Str("header", signatureHeader).
|
||||
Msg("Received signature with invalid pattern")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
|
@ -73,12 +114,22 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
}
|
||||
// fullMatch = match[0]
|
||||
rawKeyId := match[1]
|
||||
rawHeaders := match[2]
|
||||
rawAlgorithm := match[3]
|
||||
signature := match[4]
|
||||
rawAlgorithm1 := match[2]
|
||||
rawHeaders := match[3]
|
||||
rawAlgorithm2 := match[4]
|
||||
signature := match[5]
|
||||
|
||||
var rawAlgorithm string
|
||||
if rawAlgorithm2 != "" {
|
||||
rawAlgorithm = rawAlgorithm2
|
||||
} else if rawAlgorithm1 != "" {
|
||||
rawAlgorithm = rawAlgorithm1
|
||||
} else {
|
||||
rawAlgorithm = "hs2019"
|
||||
}
|
||||
_, err := url.Parse(rawKeyId)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Key id is not an url")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
|
@ -90,15 +141,111 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
return
|
||||
}
|
||||
|
||||
if rawAlgorithm == "" {
|
||||
rawAlgorithm = "hs2019"
|
||||
// w.Header().Add("X-Algorithm-Hint", "")
|
||||
}
|
||||
|
||||
_ = rawHeaders
|
||||
_ = signature
|
||||
_ = rawAlgorithm
|
||||
panic("not implemented")
|
||||
stringToCheck := buildStringToCheck(r, rawHeaders)
|
||||
log.Warn().Str("string-to-check", stringToCheck).Send()
|
||||
|
||||
requestingActor, err := getRequestingActor(rawKeyId)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get requesting actor")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer(
|
||||
"Failed to get the signing account for signature verification",
|
||||
),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Str("id", requestingActor.ID).
|
||||
Str("username", requestingActor.Username).
|
||||
Msg("Got requesting actor")
|
||||
|
||||
key, err := x509.ParsePKCS1PublicKey(requestingActor.PublicKeyRsa)
|
||||
if err != nil {
|
||||
rawKey, err := x509.ParsePKIXPublicKey(requestingActor.PublicKeyRsa)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to parse public key of requesting actor")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer("Key is not a valid PKCS1 marshalled key"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
key, ok = rawKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
log.Warn().Msg("Received public key is not rsa")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer("Received public key is not rsa"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
hash := sha256.Sum256([]byte(stringToCheck))
|
||||
err = rsa.VerifyPKCS1v15(key, crypto.SHA256, hash[:], []byte(signature))
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Signature verification failed")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer(
|
||||
"Verification of the given signature with the user's public key failed",
|
||||
),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildStringToCheck(r *http.Request, rawSignedHeaders string) string {
|
||||
headersToUse := strings.Split(rawSignedHeaders, " ")
|
||||
// Doesn't matter here if convert failed as path doesn't have prefix in that case
|
||||
path, ok := r.Context().Value((FullPathContextKey)).(string)
|
||||
if !ok {
|
||||
path = r.URL.Path
|
||||
}
|
||||
return webshared.GenerateStringToSign(
|
||||
r.Method,
|
||||
config.GlobalConfig.General.GetFullDomain(),
|
||||
path,
|
||||
r.Header,
|
||||
headersToUse,
|
||||
)
|
||||
}
|
||||
|
||||
func getRequestingActor(keyId string) (*models.User, error) {
|
||||
// Cut away key id to get AP url
|
||||
keyId = strings.Split(keyId, "#")[0]
|
||||
acc, err := dbgen.User.GetRemoteAccountByApUrl(keyId)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
acc, err = activitypub.ImportRemoteAccountByAPUrl(keyId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return acc, nil
|
||||
case nil:
|
||||
return acc, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue