From 5e13817563a3289351d7772d760a19552d75826b Mon Sep 17 00:00:00 2001 From: mStar Date: Mon, 14 Apr 2025 23:14:30 +0200 Subject: [PATCH] IT FUCKING WORKS (except for iceshrimp, but who cares) (well, I do. Would not be nice to not be compatible with a not-so-rarely used software) --- activitypub/import.go | 71 +++++++++++++++++-------- go.mod | 1 + go.sum | 2 + temp.toml | 2 +- web/shared/client.go | 64 ++-------------------- web/shared/clientRfc9421.go | 103 ++++++++++++++++++++++++++++++++++-- web/shared/signing.go | 17 ++++-- 7 files changed, 167 insertions(+), 93 deletions(-) diff --git a/activitypub/import.go b/activitypub/import.go index 2dc1c84..37d9cdb 100644 --- a/activitypub/import.go +++ b/activitypub/import.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "io" + "net/http" "time" "git.mstar.dev/mstar/goutils/other" @@ -12,6 +13,7 @@ import ( "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" ) @@ -54,35 +56,35 @@ func ImportRemoteAccount(targetName string) (string, error) { return t.Relation == "self" }) if len(selfLinks) == 0 { - return "", errors.New("No self link") + return "", errors.New("no self link") } APLink := selfLinks[0] - req, err := webshared.NewRequest("GET", *APLink.Href, nil) - if err != nil { - return "", err - } - req.Header.Add("Accept", "application/activity+json") - // Server actor key for signing linstromActor, err := dbgen.User.Where(dbgen.User.Username.Eq("linstrom")).First() if err != nil { return "", err } - var keyBytes []byte - if config.GlobalConfig.Experimental.UseEd25519Keys { - keyBytes = linstromActor.PrivateKeyEd - } else { - keyBytes = linstromActor.PrivateKeyRsa - } - - // Sign and send - err = webshared.SignRequest(req, linstromActor.ID+"#main-key", keyBytes, nil) - // err = webshared.SignRequestWithHttpsig(req, linstromActor.ID+"#main-key", keyBytes, nil) - if err != nil { - return "", err - } - log.Debug().Str("Signature", req.Header.Get("Signature")).Msg("Post sign signature") - response, err := webshared.RequestClient.Do(req) + var response *http.Response + // if config.GlobalConfig.Experimental.UseEd25519Keys { + // response, err = webshared.RequestSignedCavage( + // "GET", + // *APLink.Href, + // nil, + // config.GlobalConfig.General.GetFullPublicUrl()+"/api/activitypub/user/"+linstromActor.ID+"#main-key", + // linstromActor.PrivateKeyEd, + // true, + // ) + // } else { + // response, err = webshared.RequestSignedCavage( + // "GET", + // *APLink.Href, + // nil, + // config.GlobalConfig.General.GetFullPublicUrl()+"/api/activitypub/user/"+linstromActor.ID+"#main-key", + // linstromActor.PrivateKeyRsa, + // false, + // ) + // } + response, err = customReq(*APLink.Href, linstromActor) if err != nil { return "", err } @@ -94,7 +96,7 @@ func ImportRemoteAccount(targetName string) (string, error) { Any("headers", response.Header). Msg("Response information") if response.StatusCode != 200 { - return "", errors.New("Bad status") + return "", errors.New("bad status") } var data InboundUser err = json.Unmarshal(body, &data) @@ -104,3 +106,26 @@ func ImportRemoteAccount(targetName string) (string, error) { log.Info().Any("received-data", data).Msg("Response data") return "", nil } + +func customReq(target string, linstromActor *models.User) (*http.Response, error) { + req, err := webshared.NewRequest("GET", target, nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept", "application/activity+json") + + var keyBytes []byte + if config.GlobalConfig.Experimental.UseEd25519Keys { + keyBytes = linstromActor.PrivateKeyEd + } else { + keyBytes = linstromActor.PrivateKeyRsa + } + + // Sign and send + err = webshared.SignRequest(req, linstromActor.ID+"#main-key", keyBytes, nil) + // err = webshared.SignRequestWithHttpsig(req, linstromActor.ID+"#main-key", keyBytes, nil) + if err != nil { + return nil, err + } + return webshared.RequestClient.Do(req) +} diff --git a/go.mod b/go.mod index e3947c8..9e228b5 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.7.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-webauthn/x v0.1.14 // indirect diff --git a/go.sum b/go.sum index 208da4d..03f670d 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,8 @@ github.com/gen2brain/avif v0.3.2 h1:XUR0CBl5n4ISFJE8/pc1RMEKt5KUVoW8InctN+M7+DQ= github.com/gen2brain/avif v0.3.2/go.mod h1:tdL2sV6oOJXBZZvT5iP55VEM1X2c3/yJmYKMJTl8fXg= github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf h1:Ab5yBsD/dXhFmgf2hX7T/YYr+VK0Df7SrIxyNztT9YE= github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf/go.mod h1:+4SUDMvPlRMUPW5PlMTbxj3U5a4fWasBIbakUw7Kp6c= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/temp.toml b/temp.toml index 8e1f717..6a71c2d 100644 --- a/temp.toml +++ b/temp.toml @@ -1,7 +1,7 @@ [general] protocol = "https" domain = "serveo.net" - subdomain = "29a62d7d1dd05bd000b8f9138244df2c" + subdomain = "cc45720a387f04ba6a748a2627327a77" private_port = 8080 public_port = 443 diff --git a/web/shared/client.go b/web/shared/client.go index 7302e42..0cd770f 100644 --- a/web/shared/client.go +++ b/web/shared/client.go @@ -1,18 +1,12 @@ package webshared import ( - "crypto/ed25519" - "crypto/sha256" - "crypto/x509" - "encoding/base64" "io" "net/http" "strings" "time" "git.mstar.dev/mstar/goutils/maputils" - "github.com/go-ap/httpsig" - "github.com/rs/zerolog/log" "git.mstar.dev/mstar/linstrom/config" ) @@ -21,8 +15,6 @@ import ( var RequestClient = http.Client{} -const xRandomHeader = "X-Auth-Random" - // Sign a given outbound request for authorized fetch. // At the end, the Signature header will have the signature needed, // nothing else is modified. @@ -31,19 +23,9 @@ const xRandomHeader = "X-Auth-Random" func SignRequest(r *http.Request, keyId string, privateKeyBytes, postBody []byte) error { method := r.Method headers := r.Header - var nowString string if dateString := headers.Get("Date"); dateString != "" { - nowString = dateString } else { - nowString = time.Now().Format("Mon, 02 Jan 2006 15:04:05 MST") - headers.Set("Date", nowString) - } - var host string - if hostString := headers.Get("Host"); hostString != "" { - host = hostString - } else { - host = config.GlobalConfig.General.GetFullDomain() - headers.Set("Host", host) + headers.Set("Date", time.Now().UTC().Format(http.TimeFormat)) } applyBodyHash(headers, postBody) mappedHeaders := maputils.MapNewKeys(headers, func(k string, v []string) (string, string) { @@ -61,14 +43,14 @@ func SignRequest(r *http.Request, keyId string, privateKeyBytes, postBody []byte var signedString string var usedHeaders []string if config.GlobalConfig.Experimental.UseEd25519Keys { - tmp, tmp2, err := CreateSignatureED(method, r.URL.Path, mappedHeaders, privateKeyBytes) + tmp, tmp2, err := CreateSignatureED(method, r.URL, mappedHeaders, privateKeyBytes) if err != nil { return err } signedString = tmp usedHeaders = tmp2 } else { - tmp, tmp2, err := CreateSignatureRSA(method, r.URL.Path, mappedHeaders, privateKeyBytes) + tmp, tmp2, err := CreateSignatureRSA(method, r.URL, mappedHeaders, privateKeyBytes) if err != nil { return err } @@ -80,47 +62,10 @@ func SignRequest(r *http.Request, keyId string, privateKeyBytes, postBody []byte signedString, usedHeaders..., ) - log.Debug().Str("signature-header", signature).Send() headers.Set("Signature", signature) return nil } -func SignRequestWithHttpsig( - r *http.Request, - keyId string, - privateKeyBytes, postBody []byte, -) error { - keyId = config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/user/" + keyId - if config.GlobalConfig.Experimental.UseEd25519Keys { - key := ed25519.PrivateKey(privateKeyBytes) - signer := httpsig.NewEd25519Signer(keyId, key, nil) - if err := signer.Sign(r); err != nil { - return err - } - } else { - key, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) - if err != nil { - return err - } - signer := httpsig.NewRSASHA256Signer(keyId, key, nil) - if err = signer.Sign(r); err != nil { - return err - } - // r.Header.Add("Signature", strings.TrimPrefix(r.Header.Get("Authorization"), "Signature ")) - } - return nil -} - -func applyBodyHash(headers http.Header, body []byte) error { - if body == nil { - return nil - } - hash := sha256.Sum256(body) - based := base64.StdEncoding.EncodeToString(hash[:]) - headers.Set("Digest", "SHA-256="+based) - return nil -} - func NewRequest(method string, url string, body io.Reader) (*http.Request, error) { req, err := http.NewRequest(method, url, body) if err != nil { @@ -130,7 +75,6 @@ func NewRequest(method string, url string, body io.Reader) (*http.Request, error "User-Agent", "Linstrom v0.0.0-pre-alpha ("+config.GlobalConfig.General.GetFullDomain()+")", ) - req.Header.Add("Date", time.Now().Format(time.RFC1123)) - req.Header.Add("Host", config.GlobalConfig.General.GetFullDomain()) + req.Header.Add("Date", time.Now().UTC().Format(http.TimeFormat)) return req, nil } diff --git a/web/shared/clientRfc9421.go b/web/shared/clientRfc9421.go index c842dee..65ee8b8 100644 --- a/web/shared/clientRfc9421.go +++ b/web/shared/clientRfc9421.go @@ -1,13 +1,22 @@ package webshared import ( + "bytes" + "crypto" + "crypto/ed25519" + "crypto/sha256" "crypto/x509" - "io" + "encoding/base64" "net/http" + "slices" + "time" + "github.com/go-fed/httpsig" + "github.com/rs/zerolog/log" "github.com/yaronf/httpsign" "git.mstar.dev/mstar/linstrom/config" + "git.mstar.dev/mstar/linstrom/shared" ) /* @@ -22,16 +31,19 @@ Links for home: - https://datatracker.ietf.org/doc/html/rfc9421 */ -func RequestSigned( +// Perform a request, signing it as specified in RFC 9421 +func RequestSignedRFC9421( method, target string, - body io.Reader, + body []byte, keyId string, privateKeyBytes []byte, + useEd bool, ) (*http.Response, error) { - req, err := http.NewRequest(method, target, body) + req, err := http.NewRequest(method, target, bytes.NewBuffer(slices.Clone(body))) if err != nil { return nil, err } + applyDefaultHeaders(req) var signer *httpsign.Signer signerFields := httpsign.Headers("@request-target", "content-digest") if config.GlobalConfig.Experimental.UseEd25519Keys { @@ -40,12 +52,18 @@ func RequestSigned( httpsign.NewSignConfig(), signerFields, ) + if err != nil { + return nil, err + } } else { key, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) if err != nil { return nil, err } signer, err = httpsign.NewRSASigner(*key, httpsign.NewSignConfig(), signerFields) + if err != nil { + return nil, err + } } client := httpsign.NewClient( RequestClient, @@ -54,3 +72,80 @@ func RequestSigned( res, err := client.Do(req) return res, err } + +// Perform a request, signing it as specified in the cavage proposal +// (https://swicg.github.io/activitypub-http-signature/#how-to-sign-a-request) +func RequestSignedCavage( + method, target string, + body []byte, + keyId string, + privateKeyBytes []byte, + useEd bool, +) (*http.Response, error) { + req, err := http.NewRequest(method, target, bytes.NewBuffer(slices.Clone(body))) + if err != nil { + return nil, err + } + applyDefaultHeaders(req) + var prefs []httpsig.Algorithm + var key crypto.PrivateKey + if useEd { + log.Debug().Msg("Using ed25519 cavage") + prefs = append(prefs, httpsig.ED25519) + key = ed25519.PrivateKey(privateKeyBytes) + } else { + log.Debug().Msg("Using RSA cavage") + // prefs = append(prefs, httpsig.RSA_SHA512, httpsig.RSA_SHA256) + prefs = append(prefs, httpsig.RSA_SHA256) + tempKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) + if err != nil { + return nil, err + } + key = tempKey + } + digestAlgorithm := httpsig.DigestSha256 + headersToSign := []string{httpsig.RequestTarget, "date", "host", "user-agent"} + if len(body) > 0 { + headersToSign = append(headersToSign, "digest") + log.Debug().Msg("Non-empty body, adding digest") + } else { + // Just to ensure the signer doesn't fuck up + body = nil + } + signer, chosenAlgorithm, err := httpsig.NewSigner( + prefs, + digestAlgorithm, + headersToSign, + httpsig.Signature, + int64(time.Hour), + ) + if err != nil { + return nil, err + } + log.Debug().Any("algorithm", chosenAlgorithm).Msg("Signer chose algorithm") + if err = signer.SignRequest(key, keyId, req, body); err != nil { + return nil, err + } + log.Debug().Any("headers", req.Header).Msg("Request post signing") + return RequestClient.Do(req) +} + +func applyDefaultHeaders(r *http.Request) { + r.Header.Add( + "User-Agent", + "Linstrom "+shared.Version+" ("+config.GlobalConfig.General.GetFullDomain()+")", + ) + r.Header.Add("Date", time.Now().UTC().Format(http.TimeFormat)) + r.Header.Add("Host", config.GlobalConfig.General.GetFullDomain()) + r.Header.Add("Accept", "application/activity+json") +} + +func applyBodyHash(headers http.Header, body []byte) error { + if len(body) == 0 { + return nil + } + hash := sha256.Sum256(body) + based := base64.StdEncoding.EncodeToString(hash[:]) + headers.Set("Digest", "SHA-256="+based) + return nil +} diff --git a/web/shared/signing.go b/web/shared/signing.go index 582f0a3..3eb03c1 100644 --- a/web/shared/signing.go +++ b/web/shared/signing.go @@ -2,6 +2,7 @@ package webshared import ( "encoding/base64" + "net/url" "strings" "github.com/rs/zerolog/log" @@ -15,7 +16,7 @@ import ( // result func CreateSignatureRSA( method string, - target string, + target *url.URL, headers map[string]string, privateKeyBytes []byte, ) (string, []string, error) { @@ -40,7 +41,7 @@ func CreateSignatureRSA( // encoded result func CreateSignatureED( method string, - target string, + target *url.URL, headers map[string]string, privateKeyBytes []byte, ) (string, []string, error) { @@ -52,14 +53,20 @@ func CreateSignatureED( return base64.StdEncoding.EncodeToString(signed), usedHeaders, nil } -func genPreSignatureString(method, target string, headers map[string]string) (string, []string) { +func genPreSignatureString( + method string, + target *url.URL, + headers map[string]string, +) (string, []string) { dataBuilder := strings.Builder{} dataBuilder.WriteString("(request-target): ") dataBuilder.WriteString(strings.ToLower(method) + " ") - dataBuilder.WriteString(target + "\n") + dataBuilder.WriteString(target.Path + "\n") + dataBuilder.WriteString("host: ") + dataBuilder.WriteString(target.Host + "\n") // dataBuilder.WriteString("algorithm: rsa-sha256\n") // usedHeaders := []string{"(request-target)", "algorithm"} - usedHeaders := []string{"(request-target)"} + usedHeaders := []string{"(request-target)", "host"} for k, v := range headers { dataBuilder.WriteString(k + ": " + v + "\n") usedHeaders = append(usedHeaders, k)