144 lines
3.8 KiB
Go
144 lines
3.8 KiB
Go
package webshared
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.mstar.dev/mstar/goutils/maputils"
|
|
"github.com/go-fed/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), ""
|
|
}
|
|
})
|
|
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 SignWithHttpsig(r *http.Request, keyId string, privateKeyBytes, postBody []byte) error {
|
|
var privateKey crypto.PrivateKey
|
|
var preferredAlgorithm []httpsig.Algorithm
|
|
var digestMethod httpsig.DigestAlgorithm
|
|
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
|
log.Debug().Msg("Using ed25519")
|
|
preferredAlgorithm = []httpsig.Algorithm{httpsig.ED25519}
|
|
privateKey = ed25519.PrivateKey(privateKeyBytes)
|
|
digestMethod = httpsig.DigestSha512
|
|
} else {
|
|
log.Debug().Msg("Using rsa")
|
|
preferredAlgorithm = []httpsig.Algorithm{httpsig.RSA_SHA256}
|
|
key, err := x509.ParsePKCS1PrivateKey(privateKeyBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
privateKey = key
|
|
digestMethod = httpsig.DigestSha256
|
|
}
|
|
headers := []string{httpsig.RequestTarget, "date", "host"}
|
|
if postBody != nil {
|
|
headers = append(headers, "digest")
|
|
}
|
|
signer, _, err := httpsig.NewSigner(
|
|
preferredAlgorithm,
|
|
digestMethod,
|
|
headers,
|
|
httpsig.Signature, time.Now().Add(time.Minute).Unix())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = signer.SignRequest(
|
|
privateKey,
|
|
config.GlobalConfig.General.GetFullPublicUrl()+"/api/activitypub/user/"+keyId, r, postBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func applyBodyHash(headers http.Header, body []byte) error {
|
|
if body == nil {
|
|
return nil
|
|
}
|
|
hash := sha256.Sum256(body)
|
|
headers.Set("Digest", string(hash[:]))
|
|
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
|
|
}
|