guile-fediverse/http-signature/http.scm

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