package webshared import ( "bytes" "crypto/sha256" "crypto/x509" "encoding/base64" "net/http" "slices" "time" "github.com/yaronf/httpsign" "git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/shared" "git.mstar.dev/mstar/linstrom/storage-new/models" ) /* 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, actor *models.User, ) (*http.Response, error) { req, err := NewRequest(method, target, nil) if err != nil { return nil, err } req.Header.Add("Accept", "application/activity+json") var keyBytes []byte if config.GlobalConfig.Experimental.UseEd25519Keys { keyBytes = actor.PrivateKeyEd } else { keyBytes = actor.PrivateKeyRsa } // Sign and send err = SignRequest( req, actor.ID+"#main-key", keyBytes, body, ) // err = webshared.SignRequestWithHttpsig(req, linstromActor.ID+"#main-key", keyBytes, nil) if err != nil { return nil, err } 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 }