linstrom/web/shared/client.go
mstar 59dd8d82cf
All checks were successful
/ docker (push) Successful in 4m9s
More attempt at getting this shit to work
2025-04-14 17:00:11 +02:00

136 lines
3.7 KiB
Go

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"
)
// No init needed, zero value is good
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.
// If the request is POST, the postBody must contain the raw body of
// the request and the Digest header will also be added
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)
}
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 {
tmp, tmp2, err := CreateSignatureED(method, r.URL.Path, mappedHeaders, privateKeyBytes)
if err != nil {
return err
}
signedString = tmp
usedHeaders = tmp2
} else {
tmp, tmp2, err := CreateSignatureRSA(method, r.URL.Path, mappedHeaders, privateKeyBytes)
if err != nil {
return err
}
signedString = tmp
usedHeaders = tmp2
}
signature := CreateSignatureHeaderContent(
keyId,
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 {
return nil, err
}
req.Header.Add(
"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())
return req, nil
}