http.scm
| 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))) |
| 104 |