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 }