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 |