Add http signature support

Julien LepillerSun May 03 16:39:38+0200 2020

cd9a4b7

Add http signature support

Makefile.am

1010
  activitystreams/vocabulary.scm \
1111
  http-signature/asn1.scm \
1212
  http-signature/crypto.scm \
13+
  http-signature/http.scm \
1314
  http-signature/vocabulary.scm \
1415
  webfinger/webfinger.scm
1516

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