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 (srfi srfi-1)
23
  #:use-module (srfi srfi-9)
24
  #:use-module (rnrs bytevectors)
25
  #:export (;; this file provides some functions to deal with asn.1, der and pem.
26
            asn.1?
27
            make-asn.1
28
            asn.1-type
29
            asn.1-len
30
            asn.1-val
31
32
            asn.1-compute-len
33
            asn.1->bv
34
            ;; this file also provides higher-level functions that produce
35
            ;; pem-encoded strings from gcrypt keys.
36
            pem-encode-public))
37
38
(define-record-type asn.1
39
  (make-asn.1 type len val)
40
  asn.1?
41
  (type asn.1-type)
42
  (len  asn.1-len)
43
  (val  asn.1-val))
44
45
(define (asn.1-compute-len asn)
46
  "Computes the length of values in an asn.1 record.  Return that record, where
47
lengths are updated."
48
  (match asn
49
    (($ asn.1 type len val)
50
     (match type
51
       ('bool
52
        (match val
53
          ((? boolean? val)
54
           (make-asn.1 type 1 val))))
55
       ('int
56
        (match val
57
         ((? bytevector? val)
58
          (make-asn.1 type (bytevector-length val) val))))
59
       ('null
60
        (make-asn.1 type 0 val))
61
       ('sequence
62
        (let ((nval (map asn.1-compute-len val)))
63
          (make-asn.1 type (fold + 0 (map asn.1-full-length nval)) nval)))))))
64
65
(define (asn.1-full-length asn)
66
  "Compute the length of an asn.1 record, including the length of its length
67
field."
68
  (match asn
69
    (($ asn.1 type len val)
70
     (+ len 1 1 (if (> len 127) (nbits len) 0)))))
71
72
(define (nbits len)
73
  (if (< len 256) 1 (+ 1 (nbits (quotient len 256)))))
74
75
(define (asn.1->bv asn)
76
  "Take an asn.1 where lengths are computed and returns a bytevector that
77
corresponds to the DER encoding of that content."
78
  (let ((bv (make-bytevector (asn.1-full-length asn))))
79
    (match asn
80
      (($ asn.1 type len val)
81
       (let ((val-pos (if (> len 127) (+ 2 (nbits len)) 2)))
82
         (bytevector-u8-set! bv 1 (if (> len 127) (+ 128 (nbits len)) len))
83
         (when (> len 127)
84
           (let loop ((val len) (pos (- val-pos 1)))
85
             (when (> val 0)
86
               (bytevector-u8-set! bv pos (modulo val 256))
87
               (loop (quotient val 256) (- pos 1)))))
88
         (match type
89
           ('bool
90
            (begin
91
              (bytevector-u8-set! bv 0 #x01)
92
              (bytevector-u8-set! bv val-pos (if val 255 0))))
93
           ('int
94
            (begin
95
              (bytevector-u8-set! bv 0 #x02)
96
              (bytevector-copy! val 0 bv val-pos (bytevector-length val))))
97
           ('null
98
            (bytevector-u8-set! bv 0 #x05))
99
           ('sequence
100
            (begin
101
              (bytevector-u8-set! bv 0 #x30)
102
              (let loop ((val (map asn.1->bv val)) (pos val-pos))
103
                (when (not (null? val))
104
                  (bytevector-copy! (car val) 0 bv pos (bytevector-length (car val)))
105
                  (loop (cdr val) (+ pos (bytevector-length (car val))))))))))))
106
    bv))
107
108
(define (asn.1->string asn)
109
  "Return the PEM-encoded representation of the @var{asn} record."
110
  (base64-encode (asn.1->bv asn)))
111
112
(define (public-key->pkcs1-asn.1 pk)
113
  "Return the ASN.1 representation of @var{pk}, an RSA public key, using the
114
PKCS#1 representation."
115
  (asn.1-compute-len
116
    (match (canonical-sexp->sexp pk)
117
      (('public-key ('rsa ('n n) ('e e)))
118
       (make-asn.1
119
         'sequence #f
120
         (list
121
           (make-asn.1 'int #f n)
122
           (make-asn.1 'int #f e))))))))
123
124
(define (public-key->pkcs8-asn.1 pk)
125
  "Return the ASN.1 representation of @var{pk}, a public key, using the PKCS#8
126
representation.  Currently only RSA keys can be transformed."
127
  (asn.1-compute-len
128
    (match (canonical-sexp->sexp pk)
129
      (('public-key ('rsa ('n n) ('e e)))
130
       (make-asn.1
131
         'sequence #f
132
         (list
133
           (make-asn.1)
134
           (make-asn.1))))))
135
136
(define (cut-str str n)
137
  "Cut a string @var{str} at @var{n} characters by placing a @code{\\n}, so that
138
the string is aligned to @var{n} characters."
139
  (let loop ((str str))
140
    (if (< (string-length str) (+ n 1))
141
        str
142
        (string-append
143
          (substring str 0 n)
144
          "\n"
145
          (loop (substring str n))))))
146
147
(define (pem-encode-public keypair)
148
  "Return the PEM-encoded version of the public key in @var{keypair}, an RSA
149
keypair."
150
  (let* ((public (find-sexp-token keypair 'public-key))
151
         (asn (public-key->pkcs1-asn.1 public)))
152
    (string-append
153
      "-----BEGIN RSA PUBLIC KEY-----\n"
154
      (cut-str (asn.1->string asn) 64)
155
      "\n-----END RSA PUBLIC KEY-----")))
156