Generation of rcv’d activities’ signed-strings

Also edits BODY-CONTENTS to replace the :RAW-BODY
in a Clack HTTP request with the body string.
This commit is contained in:
Jaidyn Ann 2024-12-29 12:21:28 -06:00
parent e5ef4db56b
commit 8898d4b3f7

View File

@ -160,6 +160,33 @@ Returns the object if it was retrieved or fetched; nil otherwise."
(string-trim '(#\") value)))) (string-trim '(#\") value))))
(str:split #\, signature-header))) (str:split #\, signature-header)))
(defun signed-string (env signature-alist)
"Generate the string that was signed for the signature-header of the Clack HTTP request ENV."
(let* ((headers (getf env :headers))
(header-names (signed-header-names signature-alist)))
(reduce
(lambda (a b) (format nil "~A~%~A" a b))
(mapcar
(lambda (header-name)
(str:string-case (string-downcase header-name)
;; (request-target) is a pseudo-header formatted like “post /inbox”.
("(request-target)"
(format nil "~A: ~A ~A"
header-name
(string-downcase (symbol-name (getf env :request-method)))
(getf env :path-info)))
;; Calculate digest ourselves; never cant trust the enemy!
("digest"
(format nil "~A: SHA-256=~A" header-name (string-sha256sum (body-contents env))))
;; … we can trust them on everything else, tho.
(otherwise
(format nil "~A: ~A" header-name (gethash header-name headers)))))
header-names))))
(defun signed-header-names (signature-alist)
"Return a list of the names of headers used in a SIGNATURE-ALISTs signed string."
(str:split #\space (cdr (assoc :headers signature-alist)) :omit-nulls 't))
;;; Fetching public keys ;;; Fetching public keys
@ -274,11 +301,12 @@ can be found). Uses the callback :RETRIEVE, defined in *CONFIG*."
(defun http-inbox (env path-items params) (defun http-inbox (env path-items params)
"If one tries to send an activity to our inbox, pass it along to "If one tries to send an activity to our inbox, pass it along to
the overloaded RECEIVE method." the overloaded RECEIVE method."
(let* ((contents (body-contents (getf env :raw-body)))) (let* ((contents (body-contents env)))
(receive (json-ld:parse contents)) (receive (json-ld:parse contents))
'(200 (:content-type "text/plain") ("You win!")))) '(200 (:content-type "text/plain") ("You win!"))))
;;; Sending a note ;;; Sending a note
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
@ -383,6 +411,19 @@ the overloaded RECEIVE method."
;;; Utils. ;;; Utils.
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
(defun body-contents (env)
"Given a Clack HTTP request property-list ENV, return the requests body
contents as a string. They are read from :RAW-BODY in this plist.
If the contents are a stream, the streams contents will be read into a
string and the streams object in ENV will be replaced with the string."
(let ((body (getf env :raw-body)))
(if (stringp body)
body
(setf (getf env :raw-body)
(babel:octets-to-string
(alexandria:read-stream-content-into-byte-vector body))))))
(defun assoc-by-path (alist path-items &optional (depth 0)) (defun assoc-by-path (alist path-items &optional (depth 0))
"Given an associative list and a path decomposed into a list of "Given an associative list and a path decomposed into a list of
its components, return the item with the closest according its components, return the item with the closest according
@ -427,9 +468,3 @@ or “/bear/apple/” or “/bear/”, but not “/bear” (not a directory)."
(loop for number across (loop for number across
sequence sequence
collect (format nil "~X" number)))) collect (format nil "~X" number))))
(defun body-contents (body)
"Given the :RAW-BODY of a Clack ENV, return its contents as a string."
(babel:octets-to-string
(alexandria:read-stream-content-into-byte-vector body)))