package blossom import ( "crypto/sha256" "encoding/base64" "errors" "strconv" "strings" "git.smesh.lol/torly/pkg/nostr" ) // sha256Bytes returns the blob hash. func sha256Bytes(b []byte) [32]byte { return sha256.Sum256(b) } // verifyAuth validates a BUD-01 Authorization header: "Nostr ". // The event must be kind 24242, unexpired, signed by its claimed author, carry // the requested action in a t tag, and (when hash is non-empty) name the blob // in an x tag. func verifyAuth(header, action, hash string, now int64) ([32]byte, error) { var zero [32]byte if header == "" { return zero, errors.New("missing Authorization header") } fields := strings.Fields(header) if len(fields) != 2 || !strings.EqualFold(fields[0], "Nostr") { return zero, errors.New("Authorization must be 'Nostr '") } raw, err := decodeBase64(fields[1]) if err != nil { return zero, errors.New("Authorization is not valid base64") } ev, err := nostr.ParseEvent(raw) if err != nil { return zero, errors.New("Authorization event is malformed") } if ev.Kind != 24242 { return zero, errors.New("Authorization event must be kind 24242") } if ev.CreatedAt < now-600 || ev.CreatedAt > now+600 { return zero, errors.New("Authorization event timestamp is out of range") } exp, ok := ev.Tags.First("expiration") if !ok { return zero, errors.New("Authorization event has no expiration tag") } expN, err := strconv.ParseInt(exp, 10, 64) if err != nil || expN <= now { return zero, errors.New("Authorization event has expired") } actionOK := false for _, t := range ev.Tags.Values("t") { if t == action { actionOK = true break } } if !actionOK { return zero, errors.New("Authorization event does not grant " + action) } if hash != "" { x, ok := ev.Tags.First("x") if !ok || !strings.EqualFold(x, hash) { return zero, errors.New("Authorization event does not match the blob hash") } } if err := ev.Verify(); err != nil { return zero, err } return ev.PubKey, nil } func decodeBase64(s string) ([]byte, error) { encodings := []*base64.Encoding{ base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding, } var last error for _, enc := range encodings { b, err := enc.DecodeString(s) if err == nil { return b, nil } last = err } return nil, last }