All checks were successful
/ docker (push) Successful in 4m9s
(except for iceshrimp, but who cares) (well, I do. Would not be nice to not be compatible with a not-so-rarely used software)
151 lines
4 KiB
Go
151 lines
4 KiB
Go
package webshared
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"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"
|
|
)
|
|
|
|
/*
|
|
Links for home:
|
|
- https://pkg.go.dev/github.com/yaronf/httpsign#Client.Do
|
|
- https://www.ietf.org/archive/id/draft-richanna-http-message-signatures-00.html
|
|
- https://github.com/mastodon/mastodon/issues/29905
|
|
- https://github.com/fedify-dev/fedify/issues/208
|
|
- https://github.com/mastodon/mastodon/issues/21429
|
|
- https://github.com/go-ap/fedbox/blob/master/httpsig.go
|
|
- https://swicg.github.io/activitypub-http-signature/
|
|
- https://datatracker.ietf.org/doc/html/rfc9421
|
|
*/
|
|
|
|
// Perform a request, signing it as specified in RFC 9421
|
|
func RequestSignedRFC9421(
|
|
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 signer *httpsign.Signer
|
|
signerFields := httpsign.Headers("@request-target", "content-digest")
|
|
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
|
signer, err = httpsign.NewEd25519Signer(
|
|
privateKeyBytes,
|
|
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,
|
|
httpsign.NewClientConfig().SetSigner(signer).SetSignatureName("sig1"),
|
|
)
|
|
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
|
|
}
|