From 566b5e696c119d93719beac64c2ab5e55a21c908 Mon Sep 17 00:00:00 2001
From: Jaidyn Ann <10477760+JadedCtrl@users.noreply.github.com>
Date: Mon, 10 Jun 2024 18:10:19 -0500
Subject: [PATCH] Split HTTP-signature functions into own package
Modularization!
---
activitypub-servist.asd | 10 ++-
src/activitypub-servist.lisp | 154 ++------------------------------
src/signatures.lisp | 167 +++++++++++++++++++++++++++++++++++
3 files changed, 179 insertions(+), 152 deletions(-)
create mode 100644 src/signatures.lisp
diff --git a/activitypub-servist.asd b/activitypub-servist.asd
index 70faeeb..68e2d5d 100644
--- a/activitypub-servist.asd
+++ b/activitypub-servist.asd
@@ -1,5 +1,9 @@
(defsystem "activitypub-servist"
- :depends-on ("alexandria" "clack" "dexador" "inferior-shell" "ironclad" "local-time" "purl" "str" "webtentacle" "yason")
- :components ((:file "src/activitypub-servist")))
+ :depends-on ("activitypub-servist/signatures"
+ "alexandria" "clack" "dexador"
+ "local-time" "purl" "str" "webtentacle" "yason")
+ :components ((:file "src/activitypub-servist")))
-;; (ql:quickload '(alexandria clack dexador inferior-shell ironclad local-time purl str webtentacle yason))
+(defsystem "activitypub-servist/signatures"
+ :depends-on ("cl-base64" "inferior-shell" "ironclad" "str")
+ :components ((:file "src/signatures")))
diff --git a/src/activitypub-servist.lisp b/src/activitypub-servist.lisp
index cb2d59f..0e11697 100644
--- a/src/activitypub-servist.lisp
+++ b/src/activitypub-servist.lisp
@@ -14,7 +14,7 @@
;;; along with this program. If not, see .
(defpackage #:activitypub-servist
- (:use #:cl)
+ (:use #:cl #:activitypub-servist/signatures)
(:export :server :start-server))
(in-package #:activitypub-servist)
@@ -219,10 +219,10 @@ Mi ne estas knabino!!")
(format nil "host: ~A~%" (quri:uri-host inbox-uri))
(format nil "date: ~A~%" date-header)
(format nil "digest: ~A" digest-header)))
- (signature (base64:usb8-array-to-base64-string
- (ironclad:sign-message (openssl-shell-import-key-pair *privkey*)
- (string-to-ub8-vector
- (string-sha256sum signed-headers)))))
+;; (signature (base64:usb8-array-to-base64-string
+;; (ironclad:sign-message (openssl-shell-import-key-pair *privkey*)
+;; (string-to-ub8-vector
+;; (string-sha256sum signed-headers))))
(signature (openssl-shell-sign-string *privkey* signed-headers))
(signature-header (str:concat "keyId=\"" from "#main-key\","
"algorithm=\"rsa-sha256\","
@@ -332,153 +332,9 @@ or “/bear/apple/” or “/bear/”, but not “/bear” (not a directory)."
(str:split #\/ pathname :omit-nulls 't))
-(defun string-to-ub8-vector (string)
- "Convert the given STRING into an unsigned 8-bit vector."
- (coerce
- (loop for char across string
- collect (char-code char))
- '(vector (unsigned-byte 8))))
-
-
-(defun digest-string (digest-spec string)
- "Compute the digest of a STRING, given an Ironclad DIGEST-SPEC."
- (ironclad:digest-sequence digest-spec (string-to-ub8-vector string)))
-
-
-(defun string-sha256sum (string)
- "Compute the sha256 checksum of a STRING, in hexadecimal string-format."
- (base64:usb8-array-to-base64-string
- (digest-string (ironclad:make-digest :sha256) string)))
-
(defun sequence-hexadecimal-string (sequence)
(reduce #'str:concat
(loop for number across
sequence
collect (format nil "~X" number))))
-
-
-
-;; ————————————————————————————————————————
-;; RSA keys
-;; ————————————————————————————————————————
-;; At the moment, I’ve yet to use figure out how to create PEM representations of
-;; a public keypair properly in Lisp.
-;; So at the moment, keys are generated into PEM files by the openssl binary on
-;; the host’s system; and the output of the openssl command is used to parse into
-;; Ironclad keys.
-;; Yes, I know, this is absolutely horrific. Actually disgusting.
-;; But at the moment,I want to focus on other core parts of ActivityPub; I’ve
-;; tired of messing with ASN1 & co. That’s for another day! ^^
-(defun openssl-shell-generate-key-pair ()
- "Generate a 2048-bit RSA key-pair in PEM-format using one’s `openssl` binary.
-It returns two values: The private key, then the public key."
- (let* ((private-pem-key (inferior-shell:run/s "openssl genrsa 2048"))
- (public-pem-key
- (inferior-shell:run/s
- `(inferior-shell:pipe (echo ,private-pem-key)
- (openssl rsa -outform PEM -pubout)))))
- (values private-pem-key
- public-pem-key)))
-
-
-(defun openssl-shell-destructure-private-key (pem-string &optional results)
- "When passed the output of the shell command `openssl rsa -text -noout`, will
-parse the output into a plist containing relavent numbers:
- :n (modulus), :e (public exponent), :d (private exponent), :p (1st prime),
- :q (2nd prime), :e1 (1st exponent), :e2 (2nd exponent), and :c (coefficient)."
- (let* ((lines (if (stringp pem-string)
- (inferior-shell:run/lines
- `(inferior-shell:pipe
- (echo ,pem-string)
- (openssl rsa -text -noout)))
- pem-string))
- (line (str:trim (car lines))))
- (cond
- ((not lines)
- (mapcar
- (lambda (result-item)
- (if (stringp result-item)
- (parse-integer (str:replace-all ":" "" result-item) :radix 16)
- result-item))
- results))
- ((str:starts-with-p "Private" line)
- (openssl-shell-destructure-private-key
- (cdr lines) results))
- ((str:starts-with-p "modulus:" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:n))))
- ((str:starts-with-p "prime1" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:p))))
- ((str:starts-with-p "prime2" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:q))))
- ((str:starts-with-p "exponent1" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:e1))))
- ((str:starts-with-p "exponent2" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:e2))))
- ((str:starts-with-p "coefficient" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:c))))
- ((str:starts-with-p "privateExponent" line)
- (openssl-shell-destructure-private-key
- (cdr lines) (nconc results '(:d))))
- ((str:starts-with-p "publicExponent" line)
- (openssl-shell-destructure-private-key
- (cdr lines)
- (nconc
- results
- (list
- :e
- (parse-integer
- (car (str:split #\space
- (str:replace-first
- "publicExponent: "
- ""
- line))))))))
- ('t
- (let* ((last-element (car (last results)))
- (total-string (if (stringp last-element)
- (str:concat last-element line)
- line)))
- (openssl-shell-destructure-private-key
- (cdr lines)
- (if (stringp last-element)
- (nconc (reverse (cdr (reverse results)))
- (list total-string))
- (nconc results
- (list total-string)))))))))
-
-
-(defun openssl-shell-import-key-pair (private-pem-string)
- "Given the string value of a private RSA PEM file, this will parse it into two
-returned values: An Ironclad private key, and an Ironclad public key."
- (let ((key-values
- (openssl-shell-destructure-private-key private-pem-string)))
- (values (ironclad:make-private-key
- :rsa
- :n (getf key-values :n)
- :e (getf key-values :e)
- :d (getf key-values :d)
- :p (getf key-values :p)
- :q (getf key-values :q))
- (ironclad:make-public-key
- :rsa
- :n (getf key-values :n)
- :e (getf key-values :e)))))
-
-
-(defun openssl-shell-sign-string (private-pem-string string)
- "Use the OpenSSL binary on the host system to RSS-SHA256 sign a STRING with a
-private key."
- (alexandria:write-string-into-file private-pem-string #p"/tmp/private.pem" :if-does-not-exist :create :if-exists :overwrite)
- (apply #'str:concat
- (inferior-shell:run/lines
- `(inferior-shell:pipe
- (printf ,string)
- (openssl dgst -sha256 -sign /tmp/private.pem -)
- (base64)))))
-
diff --git a/src/signatures.lisp b/src/signatures.lisp
new file mode 100644
index 0000000..0311f55
--- /dev/null
+++ b/src/signatures.lisp
@@ -0,0 +1,167 @@
+;;; Copyright © 2023-2024 Jaidyn Levesque
+;;;
+;;; This program is free software: you can redistribute it and/or
+;;; modify it under the terms of the GNU Affero General Public License
+;;; as published by the Free Software Foundation, either version 3 of
+;;; the License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU Affero General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Affero General Public License
+;;; along with this program. If not, see .
+
+(defpackage #:activitypub-servist/signatures
+ (:use #:cl)
+ (:export :openssl-shell-generate-key-pair
+ :openssl-shell-sign-string :openssl-shell-import-key-pair
+ :digest-string :string-sha256sum))
+
+(in-package #:activitypub-servist/signatures)
+
+
+;;; Key creation/parsing
+;;; ————————————————————————————————————————
+;; At the moment, I’ve yet to use figure out how to create PEM representations of
+;; a public keypair properly in Lisp.
+;; So at the moment, keys are generated into PEM files by the openssl binary on
+;; the host’s system; and the output of the openssl command is used to parse into
+;; Ironclad keys.
+;; Yes, I know, this is absolutely horrific. Actually disgusting.
+;; But at the moment,I want to focus on other core parts of ActivityPub; I’ve
+;; tired of messing with ASN1 & co. That’s for another day! ^^
+
+(defun openssl-shell-generate-key-pair ()
+ "Generate a 2048-bit RSA key-pair in PEM-format using one’s `openssl` binary.
+It returns two values: The private key, then the public key."
+ (let* ((private-pem-key (inferior-shell:run/s "openssl genrsa 2048"))
+ (public-pem-key
+ (inferior-shell:run/s
+ `(inferior-shell:pipe (echo ,private-pem-key)
+ (openssl rsa -outform PEM -pubout)))))
+ (values private-pem-key
+ public-pem-key)))
+
+(defun openssl-shell-destructure-private-key (pem-string &optional results)
+ "When passed the output of the shell command `openssl rsa -text -noout`, will
+parse the output into a plist containing relavent numbers:
+ :n (modulus), :e (public exponent), :d (private exponent), :p (1st prime),
+ :q (2nd prime), :e1 (1st exponent), :e2 (2nd exponent), and :c (coefficient)."
+ (let* ((lines (if (stringp pem-string)
+ (inferior-shell:run/lines
+ `(inferior-shell:pipe
+ (echo ,pem-string)
+ (openssl rsa -text -noout)))
+ pem-string))
+ (line (str:trim (car lines))))
+ (cond
+ ((not lines)
+ (mapcar
+ (lambda (result-item)
+ (if (stringp result-item)
+ (parse-integer (str:replace-all ":" "" result-item) :radix 16)
+ result-item))
+ results))
+ ((str:starts-with-p "Private" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) results))
+ ((str:starts-with-p "modulus:" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:n))))
+ ((str:starts-with-p "prime1" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:p))))
+ ((str:starts-with-p "prime2" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:q))))
+ ((str:starts-with-p "exponent1" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:e1))))
+ ((str:starts-with-p "exponent2" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:e2))))
+ ((str:starts-with-p "coefficient" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:c))))
+ ((str:starts-with-p "privateExponent" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines) (nconc results '(:d))))
+ ((str:starts-with-p "publicExponent" line)
+ (openssl-shell-destructure-private-key
+ (cdr lines)
+ (nconc
+ results
+ (list
+ :e
+ (parse-integer
+ (car (str:split #\space
+ (str:replace-first
+ "publicExponent: "
+ ""
+ line))))))))
+ ('t
+ (let* ((last-element (car (last results)))
+ (total-string (if (stringp last-element)
+ (str:concat last-element line)
+ line)))
+ (openssl-shell-destructure-private-key
+ (cdr lines)
+ (if (stringp last-element)
+ (nconc (reverse (cdr (reverse results)))
+ (list total-string))
+ (nconc results
+ (list total-string)))))))))
+
+(defun openssl-shell-import-key-pair (private-pem-string)
+ "Given the string value of a private RSA PEM file, this will parse it into two
+returned values: An Ironclad private key, and an Ironclad public key."
+ (let ((key-values
+ (openssl-shell-destructure-private-key private-pem-string)))
+ (values (ironclad:make-private-key
+ :rsa
+ :n (getf key-values :n)
+ :e (getf key-values :e)
+ :d (getf key-values :d)
+ :p (getf key-values :p)
+ :q (getf key-values :q))
+ (ironclad:make-public-key
+ :rsa
+ :n (getf key-values :n)
+ :e (getf key-values :e)))))
+
+
+
+;;; Signing
+;;; ————————————————————————————————————————
+(defun openssl-shell-sign-string (private-pem-string string)
+ "Use the OpenSSL binary on the host system to RSS-SHA256 sign a STRING with a
+private key."
+ (alexandria:write-string-into-file private-pem-string #p"/tmp/private.pem" :if-does-not-exist :create :if-exists :overwrite)
+ (apply #'str:concat
+ (inferior-shell:run/lines
+ `(inferior-shell:pipe
+ (printf ,string)
+ (openssl dgst -sha256 -sign /tmp/private.pem -)
+ (base64)))))
+
+
+
+;;; Misc.
+;;; ————————————————————————————————————————
+(defun digest-string (digest-spec string)
+ "Compute the digest of a STRING, given an Ironclad DIGEST-SPEC."
+ (ironclad:digest-sequence digest-spec (string-to-ub8-vector string)))
+
+(defun string-sha256sum (string)
+ "Compute the sha256 checksum of a STRING, in hexadecimal string-format."
+ (base64:usb8-array-to-base64-string
+ (digest-string (ironclad:make-digest :sha256) string)))
+
+(defun string-to-ub8-vector (string)
+ "Convert the given STRING into an unsigned 8-bit vector."
+ (coerce
+ (loop for char across string
+ collect (char-code char))
+ '(vector (unsigned-byte 8))))