Auth fetch verification (cavage) works now
All checks were successful
/ docker (push) Successful in 4m14s
All checks were successful
/ docker (push) Successful in 4m14s
- Verifying inbound requests signed with Cavage are now checked as expected - Fixed a bug where the signature header is not generated correctly - Extended config to include settings for what requests to verify - Fixed new server in main not using internal port from config
This commit is contained in:
parent
271acc8d29
commit
627926460c
8 changed files with 90 additions and 36 deletions
|
@ -52,7 +52,9 @@ type ConfigAdmin struct {
|
|||
ProfilingPassword string `toml:"profiling_password"`
|
||||
// Allow registration on the server
|
||||
// If disabled, user must be manually created (currently via the debug server)
|
||||
AllowRegistration bool `toml:"allow_registration"`
|
||||
AllowRegistration bool `toml:"allow_registration"`
|
||||
AuthFetchForNonGet bool `toml:"auth_fetch_for_non_get"`
|
||||
AuthFetchForGet bool `toml:"auth_fetch_for_get"`
|
||||
}
|
||||
|
||||
type ConfigStorage struct {
|
||||
|
@ -119,6 +121,10 @@ type ConfigExperimental struct {
|
|||
// Both are created and stored for each local user. If this flag is enabled,
|
||||
// Linstrom shares the ED25519 key on request, otherwise the RSA key
|
||||
UseEd25519Keys bool `toml:"use_ed25519_keys"`
|
||||
// Require authorized fetch signing for requests to the server actor too
|
||||
// The implementation itself is stable, but might cause issues during initial connect
|
||||
// if the other server also requires authorized fetch for the server actor
|
||||
AuthFetchForServerActor bool `toml:"auth_fetch_for_server_actor"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
@ -151,10 +157,12 @@ var defaultConfig Config = Config{
|
|||
AdminMail: nil,
|
||||
},
|
||||
Admin: ConfigAdmin{
|
||||
Username: "server-admin",
|
||||
FirstTimeSetupOTP: "Example otp password",
|
||||
ProfilingPassword: "Example profiling password",
|
||||
AllowRegistration: true,
|
||||
Username: "server-admin",
|
||||
FirstTimeSetupOTP: "Example otp password",
|
||||
ProfilingPassword: "Example profiling password",
|
||||
AllowRegistration: true,
|
||||
AuthFetchForNonGet: true,
|
||||
AuthFetchForGet: false,
|
||||
},
|
||||
Webauthn: ConfigWebAuthn{
|
||||
DisplayName: "Linstrom",
|
||||
|
@ -198,7 +206,8 @@ var defaultConfig Config = Config{
|
|||
UseSSL: false,
|
||||
},
|
||||
Experimental: ConfigExperimental{
|
||||
UseEd25519Keys: false,
|
||||
UseEd25519Keys: false,
|
||||
AuthFetchForServerActor: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
first_time_setup_otp = "Example otp password"
|
||||
profiling_password = ""
|
||||
allow_registration = true
|
||||
auth_fetch_for_get = false
|
||||
auth_fetch_for_non_get = true
|
||||
|
||||
[webauthn]
|
||||
display_name = "Linstrom"
|
||||
|
@ -47,3 +49,4 @@
|
|||
|
||||
[experimental]
|
||||
use_ed25519_keys = false
|
||||
auth_fetch_for_server_actor = false
|
||||
|
|
10
main.go
10
main.go
|
@ -4,6 +4,7 @@ package main
|
|||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
|
@ -163,6 +164,11 @@ func newServer() {
|
|||
}()
|
||||
}
|
||||
log.Info().Msg("Starting public server")
|
||||
public := webpublic.New(":8080", &defaultDuck)
|
||||
public.Start()
|
||||
public := webpublic.New(
|
||||
fmt.Sprintf(":%v", config.GlobalConfig.General.PrivatePort),
|
||||
&defaultDuck,
|
||||
)
|
||||
if err = public.Start(); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start public server")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# .
|
||||
[general]
|
||||
protocol = "https"
|
||||
domain = "serveo.net"
|
||||
subdomain = "38e5543b0fc97680472709952a04622f"
|
||||
subdomain = "5dc9c90be02fecb3b132ad4d4877555a"
|
||||
private_port = 8080
|
||||
public_port = 443
|
||||
|
||||
|
@ -13,6 +14,8 @@
|
|||
first_time_setup_otp = "Example otp password"
|
||||
profiling_password = ""
|
||||
allow_registration = true
|
||||
auth_fetch_for_get = false
|
||||
auth_fetch_for_non_get = true
|
||||
|
||||
[webauthn]
|
||||
display_name = "Linstrom"
|
||||
|
@ -49,3 +52,4 @@
|
|||
|
||||
[experimental]
|
||||
use_ed25519_keys = false
|
||||
auth_fetch_for_server_actor = false
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/web/public/api/activitypub"
|
||||
webmiddleware "git.mstar.dev/mstar/linstrom/web/public/middleware"
|
||||
)
|
||||
|
@ -18,7 +19,10 @@ func BuildApiRouter() http.Handler {
|
|||
"/activitypub",
|
||||
webutils.ChainMiddlewares(
|
||||
activitypub.BuildActivitypubRouter(),
|
||||
webmiddleware.BuildAuthorizedFetchCheck(true, true),
|
||||
webmiddleware.BuildAuthorizedFetchCheck(
|
||||
config.GlobalConfig.Admin.AuthFetchForNonGet,
|
||||
config.GlobalConfig.Admin.AuthFetchForGet,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -5,10 +5,12 @@ import (
|
|||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
|
@ -17,12 +19,13 @@ import (
|
|||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"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_\-\.:@\/\?&=#%\+\[\]!$\(\)\*,;]+)",(?:algorithm="([a-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/.+^`),
|
||||
|
@ -61,12 +64,12 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
// 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
|
||||
// }
|
||||
if !config.GlobalConfig.Experimental.AuthFetchForServerActor &&
|
||||
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")
|
||||
|
@ -78,6 +81,30 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
return
|
||||
}
|
||||
log.Info().Msg("Need signature for AP request")
|
||||
rawDate := r.Header.Get("Date")
|
||||
date, err := http.ParseTime(rawDate)
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-date",
|
||||
"no or invalid date header",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
if time.Since(date) > time.Hour+time.Minute*5 {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer("Request is outdated"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
signatureHeader := r.Header.Get("Signature")
|
||||
if signatureHeader == "" {
|
||||
log.Info().Msg("Received AP request without signature header where one is required")
|
||||
|
@ -117,7 +144,20 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
rawAlgorithm1 := match[2]
|
||||
rawHeaders := match[3]
|
||||
rawAlgorithm2 := match[4]
|
||||
signature := match[5]
|
||||
signature, err := base64.StdEncoding.DecodeString(match[5])
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
"/errors/invalid-auth-signature",
|
||||
"invalid authorization signature",
|
||||
other.IntoPointer(
|
||||
"Signature not decodable as bas64",
|
||||
),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var rawAlgorithm string
|
||||
if rawAlgorithm2 != "" {
|
||||
|
@ -127,7 +167,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
} else {
|
||||
rawAlgorithm = "hs2019"
|
||||
}
|
||||
_, err := url.Parse(rawKeyId)
|
||||
_, err = url.Parse(rawKeyId)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Key id is not an url")
|
||||
webutils.ProblemDetails(
|
||||
|
|
|
@ -3,11 +3,8 @@ package webshared
|
|||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/maputils"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
)
|
||||
|
||||
|
@ -28,18 +25,7 @@ func SignRequest(r *http.Request, keyId string, privateKeyBytes, postBody []byte
|
|||
headers.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
}
|
||||
applyBodyHash(headers, postBody)
|
||||
mappedHeaders := maputils.MapNewKeys(headers, func(k string, v []string) (string, string) {
|
||||
if len(v) > 0 {
|
||||
return strings.ToLower(k), v[0]
|
||||
} else {
|
||||
return strings.ToLower(k), ""
|
||||
}
|
||||
})
|
||||
// Filter for only the date, host, digest and request-target headers
|
||||
mappedHeaders = maputils.FilterMap(mappedHeaders, func(k, v string) bool {
|
||||
k = strings.ToLower(k)
|
||||
return k == "date" || k == "host" || k == "digest" || k == "(request-target)"
|
||||
})
|
||||
var signedString string
|
||||
var usedHeaders []string
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/maputils"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
|
@ -61,7 +60,10 @@ func genPreSignatureString(
|
|||
headers http.Header,
|
||||
) (string, []string) {
|
||||
usedHeaders := []string{"(request-target)", "host"}
|
||||
usedHeaders = append(usedHeaders, maputils.KeysFromMap(headers)...)
|
||||
usedHeaders = append(usedHeaders, "date", "accept", "content-type")
|
||||
if headers.Get("Digest") != "" {
|
||||
usedHeaders = append(usedHeaders, "digest")
|
||||
}
|
||||
return GenerateStringToSign(method, target.Host, target.Path, headers, usedHeaders), usedHeaders
|
||||
}
|
||||
|
||||
|
@ -81,7 +83,7 @@ func GenerateStringToSign(
|
|||
case "host":
|
||||
dataBuilder.WriteString(v + ": " + host + "\n")
|
||||
default:
|
||||
dataBuilder.WriteString(v + ": " + headers.Get(v) + "\n")
|
||||
dataBuilder.WriteString(strings.ToLower(v) + ": " + headers.Get(v) + "\n")
|
||||
}
|
||||
// dataBuilder.WriteString(k + ": " + v + "\n")
|
||||
// usedHeaders = append(usedHeaders, k)
|
||||
|
|
Loading…
Reference in a new issue