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
|
@ -53,6 +53,8 @@ type ConfigAdmin struct {
|
||||||
// Allow registration on the server
|
// Allow registration on the server
|
||||||
// If disabled, user must be manually created (currently via the debug 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 {
|
type ConfigStorage struct {
|
||||||
|
@ -119,6 +121,10 @@ type ConfigExperimental struct {
|
||||||
// Both are created and stored for each local user. If this flag is enabled,
|
// 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
|
// Linstrom shares the ED25519 key on request, otherwise the RSA key
|
||||||
UseEd25519Keys bool `toml:"use_ed25519_keys"`
|
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 {
|
type Config struct {
|
||||||
|
@ -155,6 +161,8 @@ var defaultConfig Config = Config{
|
||||||
FirstTimeSetupOTP: "Example otp password",
|
FirstTimeSetupOTP: "Example otp password",
|
||||||
ProfilingPassword: "Example profiling password",
|
ProfilingPassword: "Example profiling password",
|
||||||
AllowRegistration: true,
|
AllowRegistration: true,
|
||||||
|
AuthFetchForNonGet: true,
|
||||||
|
AuthFetchForGet: false,
|
||||||
},
|
},
|
||||||
Webauthn: ConfigWebAuthn{
|
Webauthn: ConfigWebAuthn{
|
||||||
DisplayName: "Linstrom",
|
DisplayName: "Linstrom",
|
||||||
|
@ -199,6 +207,7 @@ var defaultConfig Config = Config{
|
||||||
},
|
},
|
||||||
Experimental: ConfigExperimental{
|
Experimental: ConfigExperimental{
|
||||||
UseEd25519Keys: false,
|
UseEd25519Keys: false,
|
||||||
|
AuthFetchForServerActor: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
first_time_setup_otp = "Example otp password"
|
first_time_setup_otp = "Example otp password"
|
||||||
profiling_password = ""
|
profiling_password = ""
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
|
auth_fetch_for_get = false
|
||||||
|
auth_fetch_for_non_get = true
|
||||||
|
|
||||||
[webauthn]
|
[webauthn]
|
||||||
display_name = "Linstrom"
|
display_name = "Linstrom"
|
||||||
|
@ -47,3 +49,4 @@
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
use_ed25519_keys = false
|
use_ed25519_keys = false
|
||||||
|
auth_fetch_for_server_actor = false
|
||||||
|
|
10
main.go
10
main.go
|
@ -4,6 +4,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -163,6 +164,11 @@ func newServer() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
log.Info().Msg("Starting public server")
|
log.Info().Msg("Starting public server")
|
||||||
public := webpublic.New(":8080", &defaultDuck)
|
public := webpublic.New(
|
||||||
public.Start()
|
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]
|
[general]
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
domain = "serveo.net"
|
domain = "serveo.net"
|
||||||
subdomain = "38e5543b0fc97680472709952a04622f"
|
subdomain = "5dc9c90be02fecb3b132ad4d4877555a"
|
||||||
private_port = 8080
|
private_port = 8080
|
||||||
public_port = 443
|
public_port = 443
|
||||||
|
|
||||||
|
@ -13,6 +14,8 @@
|
||||||
first_time_setup_otp = "Example otp password"
|
first_time_setup_otp = "Example otp password"
|
||||||
profiling_password = ""
|
profiling_password = ""
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
|
auth_fetch_for_get = false
|
||||||
|
auth_fetch_for_non_get = true
|
||||||
|
|
||||||
[webauthn]
|
[webauthn]
|
||||||
display_name = "Linstrom"
|
display_name = "Linstrom"
|
||||||
|
@ -49,3 +52,4 @@
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
use_ed25519_keys = false
|
use_ed25519_keys = false
|
||||||
|
auth_fetch_for_server_actor = false
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
webutils "git.mstar.dev/mstar/goutils/http"
|
webutils "git.mstar.dev/mstar/goutils/http"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/linstrom/config"
|
||||||
"git.mstar.dev/mstar/linstrom/web/public/api/activitypub"
|
"git.mstar.dev/mstar/linstrom/web/public/api/activitypub"
|
||||||
webmiddleware "git.mstar.dev/mstar/linstrom/web/public/middleware"
|
webmiddleware "git.mstar.dev/mstar/linstrom/web/public/middleware"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +19,10 @@ func BuildApiRouter() http.Handler {
|
||||||
"/activitypub",
|
"/activitypub",
|
||||||
webutils.ChainMiddlewares(
|
webutils.ChainMiddlewares(
|
||||||
activitypub.BuildActivitypubRouter(),
|
activitypub.BuildActivitypubRouter(),
|
||||||
webmiddleware.BuildAuthorizedFetchCheck(true, true),
|
webmiddleware.BuildAuthorizedFetchCheck(
|
||||||
|
config.GlobalConfig.Admin.AuthFetchForNonGet,
|
||||||
|
config.GlobalConfig.Admin.AuthFetchForGet,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
webutils "git.mstar.dev/mstar/goutils/http"
|
webutils "git.mstar.dev/mstar/goutils/http"
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
|
@ -17,12 +19,13 @@ import (
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||||
"git.mstar.dev/mstar/linstrom/config"
|
"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/dbgen"
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
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{
|
var publicPaths = []*regexp.Regexp{
|
||||||
regexp.MustCompile(`/\.well-known/.+^`),
|
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
|
// So either you get the server actor, which is intended behaviour, or access to
|
||||||
// one specific note that most likely won't even exist
|
// one specific note that most likely won't even exist
|
||||||
|
|
||||||
// FIXME: Re-enable once implementation of actual verification is stable
|
if !config.GlobalConfig.Experimental.AuthFetchForServerActor &&
|
||||||
// if strings.Contains(path, storage.ServerActorId) {
|
strings.Contains(path, storage.ServerActorId) {
|
||||||
// log.Info().Msg("Server actor requested, no auth")
|
log.Info().Msg("Server actor requested, no auth")
|
||||||
// h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// Not an always open path, check methods
|
// Not an always open path, check methods
|
||||||
if r.Method == "GET" && !forGet {
|
if r.Method == "GET" && !forGet {
|
||||||
log.Info().Msg("Get request to AP resources don't need signature")
|
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
|
return
|
||||||
}
|
}
|
||||||
log.Info().Msg("Need signature for AP request")
|
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")
|
signatureHeader := r.Header.Get("Signature")
|
||||||
if signatureHeader == "" {
|
if signatureHeader == "" {
|
||||||
log.Info().Msg("Received AP request without signature header where one is required")
|
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]
|
rawAlgorithm1 := match[2]
|
||||||
rawHeaders := match[3]
|
rawHeaders := match[3]
|
||||||
rawAlgorithm2 := match[4]
|
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
|
var rawAlgorithm string
|
||||||
if rawAlgorithm2 != "" {
|
if rawAlgorithm2 != "" {
|
||||||
|
@ -127,7 +167,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
||||||
} else {
|
} else {
|
||||||
rawAlgorithm = "hs2019"
|
rawAlgorithm = "hs2019"
|
||||||
}
|
}
|
||||||
_, err := url.Parse(rawKeyId)
|
_, err = url.Parse(rawKeyId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Key id is not an url")
|
log.Warn().Err(err).Msg("Key id is not an url")
|
||||||
webutils.ProblemDetails(
|
webutils.ProblemDetails(
|
||||||
|
|
|
@ -3,11 +3,8 @@ package webshared
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/maputils"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/config"
|
"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))
|
headers.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
applyBodyHash(headers, postBody)
|
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
|
// 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 signedString string
|
||||||
var usedHeaders []string
|
var usedHeaders []string
|
||||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/maputils"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/config"
|
"git.mstar.dev/mstar/linstrom/config"
|
||||||
|
@ -61,7 +60,10 @@ func genPreSignatureString(
|
||||||
headers http.Header,
|
headers http.Header,
|
||||||
) (string, []string) {
|
) (string, []string) {
|
||||||
usedHeaders := []string{"(request-target)", "host"}
|
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
|
return GenerateStringToSign(method, target.Host, target.Path, headers, usedHeaders), usedHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ func GenerateStringToSign(
|
||||||
case "host":
|
case "host":
|
||||||
dataBuilder.WriteString(v + ": " + host + "\n")
|
dataBuilder.WriteString(v + ": " + host + "\n")
|
||||||
default:
|
default:
|
||||||
dataBuilder.WriteString(v + ": " + headers.Get(v) + "\n")
|
dataBuilder.WriteString(strings.ToLower(v) + ": " + headers.Get(v) + "\n")
|
||||||
}
|
}
|
||||||
// dataBuilder.WriteString(k + ": " + v + "\n")
|
// dataBuilder.WriteString(k + ": " + v + "\n")
|
||||||
// usedHeaders = append(usedHeaders, k)
|
// usedHeaders = append(usedHeaders, k)
|
||||||
|
|
Loading…
Reference in a new issue