Add http signature support
Makefile.am
| 10 | 10 | activitystreams/vocabulary.scm \ | |
| 11 | 11 | http-signature/asn1.scm \ | |
| 12 | 12 | http-signature/crypto.scm \ | |
| 13 | + | http-signature/http.scm \ | |
| 13 | 14 | http-signature/vocabulary.scm \ | |
| 14 | 15 | webfinger/webfinger.scm | |
| 15 | 16 |
http-signature/http.scm unknown status 1
| 1 | + | ;;;; Copyright (C) 2020 Julien Lepiller <julien@lepiller.eu> | |
| 2 | + | ;;;; | |
| 3 | + | ;;;; This library is free software; you can redistribute it and/or | |
| 4 | + | ;;;; modify it under the terms of the GNU Lesser General Public | |
| 5 | + | ;;;; License as published by the Free Software Foundation; either | |
| 6 | + | ;;;; version 3 of the License, or (at your option) any later version. | |
| 7 | + | ;;;; | |
| 8 | + | ;;;; This library is distributed in the hope that it will be useful, | |
| 9 | + | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 10 | + | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 11 | + | ;;;; Lesser General Public License for more details. | |
| 12 | + | ;;;; | |
| 13 | + | ;;;; You should have received a copy of the GNU Lesser General Public | |
| 14 | + | ;;;; License along with this library; if not, write to the Free Software | |
| 15 | + | ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 16 | + | ;;;; | |
| 17 | + | ||
| 18 | + | (define-module (http-signature http) | |
| 19 | + | #:use-module (gcrypt base64) | |
| 20 | + | #:use-module (gcrypt hash) | |
| 21 | + | #:use-module (gcrypt pk-crypto) | |
| 22 | + | #:use-module (http-signature crypto) | |
| 23 | + | #:use-module (ice-9 match) | |
| 24 | + | #:use-module (rnrs bytevectors) | |
| 25 | + | #:use-module (srfi srfi-19) | |
| 26 | + | #:use-module (web client) | |
| 27 | + | #:use-module (web http) | |
| 28 | + | #:use-module (web uri) | |
| 29 | + | #:export (http-post/signed | |
| 30 | + | verify-signed-headers)) | |
| 31 | + | ||
| 32 | + | (define* (http-post/signed to key keyid #:key (body #f) (verify-certificate? #t) | |
| 33 | + | (port (open-socket-for-uri to #:verify-certificate? verify-certificate?)) | |
| 34 | + | (version '(1 . 1)) | |
| 35 | + | (keep-alive? #f) | |
| 36 | + | (headers '()) | |
| 37 | + | (decode-body? #t) | |
| 38 | + | (streaming? #f)) | |
| 39 | + | (let* ((uri (string->uri to)) | |
| 40 | + | (cur-date (current-date)) | |
| 41 | + | (date (with-output-to-string | |
| 42 | + | (lambda _ | |
| 43 | + | ((header-writer 'date) cur-date (current-output-port))))) | |
| 44 | + | (signed-data (string-append "(request-target): post " | |
| 45 | + | (uri-path uri) "\nhost: " | |
| 46 | + | (uri-host uri) "\ndate: " | |
| 47 | + | date)) | |
| 48 | + | (public-key (find-sexp-token key 'public-key)) | |
| 49 | + | (private-key (find-sexp-token key 'private-key)) | |
| 50 | + | (data (bytevector->hash-data (sha256 (string->utf8 signed-data)) | |
| 51 | + | #:key-type (key-type public-key))) | |
| 52 | + | (signature-sexp (sign data private-key)) | |
| 53 | + | (signature-bv (match (canonical-sexp->sexp signature-sexp) | |
| 54 | + | (('sig-val ('rsa ('s s))) s))) | |
| 55 | + | (signature (base64-encode signature-bv))) | |
| 56 | + | (http-post to | |
| 57 | + | #:body body | |
| 58 | + | #:verify-certificate? verify-certificate? | |
| 59 | + | #:port port | |
| 60 | + | #:keep-alive? keep-alive? | |
| 61 | + | #:headers | |
| 62 | + | (cons* | |
| 63 | + | `(date . ,cur-date) | |
| 64 | + | `(Signature . | |
| 65 | + | ,(string-append "keyId=\"" keyid "\",headers=\"" | |
| 66 | + | "(request-target) host date\"," | |
| 67 | + | "algorithm=\"rsa-sha256\",signature=\"" | |
| 68 | + | signature "\"")) | |
| 69 | + | headers) | |
| 70 | + | #:decode-body? decode-body? | |
| 71 | + | #:streaming? streaming?))) | |
| 72 | + | ||
| 73 | + | (define (verify-signed-headers headers pubkey-getter method) | |
| 74 | + | (let* ((signature-header (assoc-ref headers 'Signature)) | |
| 75 | + | (elements (string-split signature-header #\,)) | |
| 76 | + | (elements (map (lambda (elem) | |
| 77 | + | (let ((s (string-split elem #\=))) | |
| 78 | + | (cons (car s) (string-trim-both | |
| 79 | + | (string-join (cdr s) "=") #\")))) | |
| 80 | + | elements)) | |
| 81 | + | (signed-headers (string-split (assoc-ref elements "headers") #\space)) | |
| 82 | + | (keyid (assoc-ref elements "keyId")) | |
| 83 | + | (signature (assoc-ref elements "signature")) | |
| 84 | + | (headers (map (lambda (header) | |
| 85 | + | (cons header | |
| 86 | + | (if (equal? header "(request-target)") | |
| 87 | + | method | |
| 88 | + | (assoc-ref headers (string->symbol header))))) | |
| 89 | + | signed-headers)) | |
| 90 | + | (signature-bv (base64-decode signature)) | |
| 91 | + | (signature-sexp (sexp->canonical-sexp `(sig-val (rsa (s ,signature-bv))))) | |
| 92 | + | (data (map | |
| 93 | + | (match-lambda | |
| 94 | + | ((header . val) | |
| 95 | + | (string-append header ": " val))) | |
| 96 | + | headers)) | |
| 97 | + | (data (string-join data "\n")) | |
| 98 | + | (public-key (pubkey-getter keyid)) | |
| 99 | + | (signed-data | |
| 100 | + | (bytevector->hash-data (sha256 (string->utf8 data)) | |
| 101 | + | #:key-type (key-type public-key)))) | |
| 102 | + | ||
| 103 | + | (verify signature-sexp signed-data public-key))) |