guile-fediverse/http-signature/crypto.scm

crypto.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 crypto)
19
  #:use-module (gcrypt base64)
20
  #:use-module (gcrypt pk-crypto)
21
  #:use-module (ice-9 match)
22
  #:use-module (http-signature asn1)
23
  #:export (;; this file also provides higher-level functions that produce
24
            ;; pem-encoded strings from gcrypt keys.
25
            pem-encode-public
26
            pem-decode-public))
27
28
(define pkcs1-type (asn1:sequence asn1:int-bv asn1:int-bv))
29
(define pkcs8-type (asn1:sequence (asn1:sequence asn1:object-identifier asn1:null)
30
                                  asn1:bitstring))
31
32
(define (asn.1->string asn)
33
  "Return the PEM-encoded representation of the @var{asn} record."
34
  (base64-encode asn))
35
36
(define (public-key->pkcs1-asn.1 pk)
37
  "Return the ASN.1 representation of @var{pk}, an RSA public key, using the
38
PKCS#1 representation."
39
  (match (canonical-sexp->sexp pk)
40
    (('public-key ('rsa ('n n) ('e e)))
41
     (encode-asn1 pkcs1-type (list n e)))))
42
43
(define (public-key->pkcs8-asn.1 pk)
44
  "Return the ASN.1 representation of @var{pk}, a public key, using the PKCS#8
45
representation.  Currently only RSA keys can be transformed."
46
  (match (canonical-sexp->sexp pk)
47
    (('public-key ('rsa ('n n) ('e e)))
48
     (encode-asn1 pkcs8-type
49
                  (list (list (list 1 2 840 113549 1 1 1) #f)
50
                        (public-key->pkcs1-asn.1 pk))))))
51
52
(define (cut-str str n)
53
  "Cut a string @var{str} at @var{n} characters by placing a @code{\\n}, so that
54
the string is aligned to @var{n} characters."
55
  (let loop ((str str))
56
    (if (< (string-length str) (+ n 1))
57
        str
58
        (string-append
59
          (substring str 0 n)
60
          "\n"
61
          (loop (substring str n))))))
62
63
(define (pem-encode-public keypair)
64
  "Return the PEM-encoded version of the public key in @var{keypair}, an RSA
65
keypair."
66
  (let* ((public (find-sexp-token keypair 'public-key))
67
         (asn (public-key->pkcs8-asn.1 public)))
68
    (string-append
69
      "-----BEGIN PUBLIC KEY-----\n"
70
      (cut-str (asn.1->string asn) 64)
71
      "\n-----END PUBLIC KEY-----")))
72
73
(define (decode-pkcs1 der)
74
  (let ((data (decode-asn1 der pkcs1-type)))
75
    (match data
76
      ((n e) (sexp->canonical-sexp `(public-key (rsa (n ,n) (e ,e))))))))
77
78
(define (decode-pkcs8 der)
79
  (let ((data (decode-asn1 der pkcs8-type)))
80
    (match data
81
      (((_ _) bs) (decode-pkcs1 bs)))))
82
83
(define (pem-decode-public str)
84
  "Return a public-key from the PEM-encoded key in @var{str}."
85
  (let* ((lines (string-split str #\newline))
86
         (first-line (car lines))
87
         (lines (cdr (reverse (cdr (reverse lines))))))
88
    (match first-line
89
      ("-----BEGIN PUBLIC KEY-----"
90
       (let loop ((content '()) (lines lines))
91
         (match lines
92
           (() (throw 'invalid-key))
93
           ((line lines ...)
94
            (if (equal? line "-----END PUBLIC KEY-----")
95
                (decode-pkcs8 (base64-decode (string-join (reverse content) "")))
96
                (loop (cons line content) lines))))))
97
      ("-----BEGIN RSA PUBLIC KEY-----"
98
       (let loop ((content '()) (lines lines))
99
         (match lines
100
           (() (throw 'invalid-key))
101
           ((line lines ...)
102
            (if (equal? line "-----END RSA PUBLIC KEY-----")
103
                (decode-pkcs1 (base64-decode (string-join (reverse content) "")))
104
                (loop (cons line content) lines))))))
105
      (_ (throw 'unrecognised-key-type)))))
106