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 |