package webshared import ( "encoding/base64" "net/http" "net/url" "strings" "github.com/rs/zerolog/log" "git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/shared" ) // Generate the signed string of the headers, method and target // and sign it using the given RSA key. Returns the base64 encoded // result func CreateSignatureRSA( method string, target *url.URL, headers http.Header, privateKeyBytes []byte, ) (string, []string, error) { message, usedHeaders := genPreSignatureString(method, target, headers) log.Error().Str("message", message) signed, err := shared.Sign(message, privateKeyBytes, true) if err != nil { return "", nil, err } encoded := base64.StdEncoding.EncodeToString(signed) log.Trace(). Str("raw-message", message). Bytes("signed", signed). Str("encoded", encoded). Strs("header-order", usedHeaders). Msg("Signing complete") return encoded, usedHeaders, nil } // Generate the signed string of the headers, method and target // and sign it using the given ED25519 key. Returns the base64 // encoded result func CreateSignatureED( method string, target *url.URL, headers http.Header, privateKeyBytes []byte, ) (string, []string, error) { message, usedHeaders := genPreSignatureString(method, target, headers) signed, err := shared.Sign(message, privateKeyBytes, false) if err != nil { return "", nil, err } return base64.StdEncoding.EncodeToString(signed), usedHeaders, nil } func genPreSignatureString( method string, target *url.URL, headers http.Header, ) (string, []string) { usedHeaders := []string{"(request-target)", "host"} usedHeaders = append(usedHeaders, "date", "accept", "content-type") if headers.Get("Digest") != "" { usedHeaders = append(usedHeaders, "digest") } return GenerateStringToSign(method, target.Host, target.Path, headers, usedHeaders), usedHeaders } func GenerateStringToSign( method string, host string, path string, headers http.Header, headerOrder []string, ) string { dataBuilder := strings.Builder{} for _, v := range headerOrder { v = strings.ToLower(v) switch v { case "(request-target)": dataBuilder.WriteString(v + ": " + strings.ToLower(method) + " " + path + "\n") case "host": dataBuilder.WriteString(v + ": " + host + "\n") default: dataBuilder.WriteString(strings.ToLower(v) + ": " + headers.Get(v) + "\n") } // dataBuilder.WriteString(k + ": " + v + "\n") // usedHeaders = append(usedHeaders, k) } tmp := strings.TrimSuffix(dataBuilder.String(), "\n") log.Debug().Str("Raw signature string", tmp).Send() return tmp } // Generate the content of the "Signature" header based on // The user who's key was used, the hashed and base64 encoded // signed string, as returned by CreateSignatureED/RSA func CreateSignatureHeaderContent(userId string, hash string, headerNames ...string) string { builder := strings.Builder{} builder.WriteString("keyId=\"") builder.WriteString(config.GlobalConfig.General.GetFullPublicUrl()) builder.WriteString("/api/activitypub/user/") builder.WriteString(userId) builder.WriteString("\",headers=\"") for i, header := range headerNames { builder.WriteString(header) if i+1 < len(headerNames) { builder.WriteRune(' ') } } if config.GlobalConfig.Experimental.UseEd25519Keys { builder.WriteString("\",algorithm=\"ed-sha512\",signature=\"") } else { builder.WriteString("\",algorithm=\"rsa-sha256\",signature=\"") } builder.WriteString(hash) builder.WriteRune('"') return builder.String() }