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))) |