(except for iceshrimp, but who cares) (well, I do. Would not be nice to not be compatible with a not-so-rarely used software)
This commit is contained in:
parent
59dd8d82cf
commit
5e13817563
7 changed files with 167 additions and 93 deletions
|
@ -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)
|
||||
}
|
||||
|
|
1
go.mod
1
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[general]
|
||||
protocol = "https"
|
||||
domain = "serveo.net"
|
||||
subdomain = "29a62d7d1dd05bd000b8f9138244df2c"
|
||||
subdomain = "cc45720a387f04ba6a748a2627327a77"
|
||||
private_port = 8080
|
||||
public_port = 443
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue