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 |