linstrom/web/shared/signing.go
mstar 627926460c
All checks were successful
/ docker (push) Successful in 4m14s
Auth fetch verification (cavage) works now
- Verifying inbound requests signed with Cavage are now checked as
  expected
- Fixed a bug where the signature header is not generated correctly
- Extended config to include settings for what requests to verify
- Fixed new server in main not using internal port from config
2025-04-22 15:27:24 +02:00

121 lines
3.4 KiB
Go

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()
}