123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- ;;; guile-gcrypt --- crypto tooling for guile
- ;;; Copyright © 2013, 2014, 2015, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
- ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com>
- ;;;
- ;;; This file is part of guile-gcrypt.
- ;;;
- ;;; guile-gcrypt is free software; you can redistribute it and/or modify it
- ;;; under the terms of the GNU General Public License as published by
- ;;; the Free Software Foundation; either version 3 of the License, or
- ;;; (at your option) any later version.
- ;;;
- ;;; guile-gcrypt is distributed in the hope that it will be useful, but
- ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;;; General Public License for more details.
- ;;;
- ;;; You should have received a copy of the GNU General Public License
- ;;; along with guile-gcrypt. If not, see <http://www.gnu.org/licenses/>.
- (define-module (gcrypt pk-crypto)
- #:use-module (gcrypt base16)
- #:use-module (gcrypt internal)
- #:use-module (gcrypt common)
- #:use-module (system foreign)
- #:use-module (rnrs bytevectors)
- #:use-module (ice-9 match)
- #:use-module (ice-9 rdelim)
- #:export (canonical-sexp?
- string->canonical-sexp
- canonical-sexp->string
- read-file-sexp
- number->canonical-sexp
- canonical-sexp-car
- canonical-sexp-cdr
- canonical-sexp-nth
- canonical-sexp-nth-data
- canonical-sexp-length
- canonical-sexp-null?
- canonical-sexp-list?
- bytevector->hash-data
- hash-data->bytevector
- key-type
- sign
- verify
- generate-key
- find-sexp-token
- canonical-sexp->sexp
- sexp->canonical-sexp)
- #:re-export (gcrypt-version))
- ;;; Commentary:
- ;;;
- ;;; Public key cryptographic routines from GNU Libgcrypt.
- ;;;;
- ;;; Libgcrypt uses "canonical s-expressions" to represent key material,
- ;;; parameters, and data. We keep it as an opaque object to map them to
- ;;; Scheme s-expressions because (1) Libgcrypt sexps may be stored in secure
- ;;; memory, and (2) the read syntax is different.
- ;;;
- ;;; A 'canonical-sexp->sexp' procedure is provided nevertheless, for use in
- ;;; cases where it is safe to move data out of Libgcrypt---e.g., when
- ;;; processing ACL entries, public keys, etc.
- ;;;
- ;;; Canonical sexps were defined by Rivest et al. in the IETF draft at
- ;;; <http://people.csail.mit.edu/rivest/Sexp.txt> for the purposes of SPKI
- ;;; (see <http://www.ietf.org/rfc/rfc2693.txt>.)
- ;;;
- ;;; Code:
- ;; Libgcrypt "s-expressions".
- (define-wrapped-pointer-type <canonical-sexp>
- canonical-sexp?
- naked-pointer->canonical-sexp
- canonical-sexp->pointer
- (lambda (obj port)
- ;; Don't print OBJ's external representation: we don't want key material
- ;; to leak in backtraces and such.
- (format port "#<canonical-sexp ~a | ~a>"
- (number->string (object-address obj) 16)
- (number->string (pointer-address (canonical-sexp->pointer obj))
- 16))))
- (define finalize-canonical-sexp!
- (libgcrypt->pointer "gcry_sexp_release"))
- (define-inlinable (pointer->canonical-sexp ptr)
- "Return a <canonical-sexp> that wraps PTR."
- (let* ((sexp (naked-pointer->canonical-sexp ptr))
- (ptr* (canonical-sexp->pointer sexp)))
- ;; Did we already have a <canonical-sexp> object for PTR?
- (when (equal? ptr ptr*)
- ;; No, so we can safely add a finalizer (in Guile 2.0.9
- ;; 'set-pointer-finalizer!' *adds* a finalizer rather than replacing the
- ;; existing one.)
- (set-pointer-finalizer! ptr finalize-canonical-sexp!))
- sexp))
- (define string->canonical-sexp
- (let ((proc (libgcrypt->procedure int
- "gcry_sexp_new"
- `(* * ,size_t ,int))))
- (lambda (str)
- "Parse STR and return the corresponding gcrypt s-expression."
- ;; When STR comes from 'canonical-sexp->string', it may contain
- ;; characters that are really meant to be interpreted as bytes as in a C
- ;; 'char *'. Thus, convert STR to ISO-8859-1 so the byte values of the
- ;; characters are preserved.
- (let* ((sexp (bytevector->pointer (make-bytevector (sizeof '*))))
- (err (proc sexp (string->pointer str "ISO-8859-1") 0 1)))
- (if (= 0 err)
- (pointer->canonical-sexp (dereference-pointer sexp))
- (throw 'gcry-error 'string->canonical-sexp err))))))
- (define-syntax GCRYSEXP_FMT_ADVANCED
- (identifier-syntax 3))
- (define canonical-sexp->string
- (let ((proc (libgcrypt->procedure size_t
- "gcry_sexp_sprint"
- `(* ,int * ,size_t))))
- (lambda (sexp)
- "Return a textual representation of SEXP."
- (let loop ((len 1024))
- (let* ((buf (bytevector->pointer (make-bytevector len)))
- (size (proc (canonical-sexp->pointer sexp)
- GCRYSEXP_FMT_ADVANCED buf len)))
- (if (zero? size)
- (loop (* len 2))
- (pointer->string buf size "ISO-8859-1")))))))
- (define (read-file-sexp file)
- "Return the canonical sexp read from FILE."
- (call-with-input-file file
- (compose string->canonical-sexp
- read-string)))
- (define canonical-sexp-car
- (let ((proc (libgcrypt->procedure '* "gcry_sexp_car" '(*))))
- (lambda (lst)
- "Return the first element of LST, an sexp, if that element is a list;
- return #f if LST or its first element is not a list (this is different from
- the usual Lisp 'car'.)"
- (let ((result (proc (canonical-sexp->pointer lst))))
- (if (null-pointer? result)
- #f
- (pointer->canonical-sexp result))))))
- (define canonical-sexp-cdr
- (let ((proc (libgcrypt->procedure '* "gcry_sexp_cdr" '(*))))
- (lambda (lst)
- "Return the tail of LST, an sexp, or #f if LST is not a list."
- (let ((result (proc (canonical-sexp->pointer lst))))
- (if (null-pointer? result)
- #f
- (pointer->canonical-sexp result))))))
- (define canonical-sexp-nth
- (let ((proc (libgcrypt->procedure '* "gcry_sexp_nth" `(* ,int))))
- (lambda (lst index)
- "Return the INDEXth nested element of LST, an s-expression. Return #f
- if that element does not exist, or if it's an atom. (Note: this is obviously
- different from Scheme's 'list-ref'.)"
- (let ((result (proc (canonical-sexp->pointer lst) index)))
- (if (null-pointer? result)
- #f
- (pointer->canonical-sexp result))))))
- (define (dereference-size_t p)
- "Return the size_t value pointed to by P."
- (bytevector-uint-ref (pointer->bytevector p (sizeof size_t))
- 0 (native-endianness)
- (sizeof size_t)))
- (define canonical-sexp-length
- (let ((proc (libgcrypt->procedure int "gcry_sexp_length" '(*))))
- (lambda (sexp)
- "Return the length of SEXP if it's a list (including the empty list);
- return zero if SEXP is an atom."
- (proc (canonical-sexp->pointer sexp)))))
- (define token-string?
- (let ((token-cs (char-set-union char-set:digit
- char-set:letter
- (char-set #\- #\. #\/ #\_
- #\: #\* #\+ #\=))))
- (lambda (str)
- "Return #t if STR is a token as per Section 4.3 of
- <http://people.csail.mit.edu/rivest/Sexp.txt>."
- (and (not (string-null? str))
- (string-every token-cs str)
- (not (char-set-contains? char-set:digit (string-ref str 0)))))))
- (define canonical-sexp-nth-data
- (let ((proc (libgcrypt->procedure '* "gcry_sexp_nth_data" `(* ,int *))))
- (lambda (lst index)
- "Return as a symbol (for \"sexp tokens\") or a bytevector (for any other
- \"octet string\") the INDEXth data element (atom) of LST, an s-expression.
- Return #f if that element does not exist, or if it's a list."
- (let* ((size* (bytevector->pointer (make-bytevector (sizeof '*))))
- (result (proc (canonical-sexp->pointer lst) index size*)))
- (if (null-pointer? result)
- #f
- (let* ((len (dereference-size_t size*))
- (str (pointer->string result len "ISO-8859-1")))
- ;; The sexp spec speaks of "tokens" and "octet strings".
- ;; Sometimes these octet strings are actual strings (text),
- ;; sometimes they're bytevectors, and sometimes they're
- ;; multi-precision integers (MPIs). Only the application knows.
- ;; However, for convenience, we return a symbol when a token is
- ;; encountered since tokens are frequent (at least in the 'car'
- ;; of each sexp.)
- (if (token-string? str)
- (string->symbol str) ; an sexp "token"
- (bytevector-copy ; application data, textual or binary
- (pointer->bytevector result len)))))))))
- (define (number->canonical-sexp number)
- "Return an s-expression representing NUMBER."
- (let ((hex-number
- (match (number->string number 16)
- ;; Append a 0 if necessary. For whatever reason gcrypt
- ;; rejects hex numbers that don't have an even number of
- ;; digits.
- ((? (lambda (s) (odd? (string-length s))) odd-string)
- (string-append "0" odd-string))
- (even-str even-str))))
- (string->canonical-sexp (string-append "#" hex-number "#"))))
- (define* (bytevector->hash-data bv
- #:optional
- (hash-algo "sha256")
- #:key (key-type 'ecc))
- "Given BV, a bytevector containing a hash of type HASH-ALGO, return an
- s-expression suitable for use as the 'data' argument for 'sign'. KEY-TYPE
- must be a symbol: 'dsa, 'ecc, or 'rsa."
- (string->canonical-sexp
- (format #f "(data (flags ~a) (hash \"~a\" #~a#))"
- (case key-type
- ((ecc dsa) "rfc6979")
- ((rsa) "pkcs1")
- (else (error "unknown key type" key-type)))
- hash-algo
- (bytevector->base16-string bv))))
- (define (key-type sexp)
- "Return a symbol denoting the type of public or private key represented by
- SEXP--e.g., 'rsa', 'ecc'--or #f if SEXP does not denote a valid key."
- (case (canonical-sexp-nth-data sexp 0)
- ((public-key private-key)
- (canonical-sexp-nth-data (canonical-sexp-nth sexp 1) 0))
- (else #f)))
- (define* (hash-data->bytevector data)
- "Return two values: the hash value (a bytevector), and the hash algorithm (a
- string) extracted from DATA, an sexp as returned by 'bytevector->hash-data'.
- Return #f if DATA does not conform."
- (let ((hash (find-sexp-token data 'hash)))
- (if hash
- (let ((algo (canonical-sexp-nth-data hash 1))
- (value (canonical-sexp-nth-data hash 2)))
- (values value (symbol->string algo)))
- (values #f #f))))
- (define sign
- (let ((proc (libgcrypt->procedure int "gcry_pk_sign" '(* * *))))
- (lambda (data secret-key)
- "Sign DATA, a canonical s-expression representing a suitable hash, with
- SECRET-KEY (a canonical s-expression whose car is 'private-key'.) Note that
- DATA must be a 'data' s-expression, as returned by
- 'bytevector->hash-data' (info \"(gcrypt) Cryptographic Functions\")."
- (let* ((sig (bytevector->pointer (make-bytevector (sizeof '*))))
- (err (proc sig (canonical-sexp->pointer data)
- (canonical-sexp->pointer secret-key))))
- (if (= 0 err)
- (pointer->canonical-sexp (dereference-pointer sig))
- (throw 'gcry-error 'sign err))))))
- (define verify
- (let ((proc (libgcrypt->procedure int "gcry_pk_verify" '(* * *))))
- (lambda (signature data public-key)
- "Verify that SIGNATURE is a signature of DATA with PUBLIC-KEY, all of
- which are gcrypt s-expressions; return #t if the verification was successful,
- #f otherwise. Raise an error if, for example, one of the given s-expressions
- is invalid."
- (let ((err (proc (canonical-sexp->pointer signature)
- (canonical-sexp->pointer data)
- (canonical-sexp->pointer public-key))))
- (cond ((zero? err) #t)
- ((error-code=? error/bad-signature err) #f)
- (else (throw 'gcry-error 'verify err)))))))
- (define generate-key
- (let ((proc (libgcrypt->procedure int "gcry_pk_genkey" '(* *))))
- (lambda (params)
- "Return as an s-expression a new key pair for PARAMS. PARAMS must be an
- s-expression like: (genkey (rsa (nbits 4:2048)))."
- (let* ((key (bytevector->pointer (make-bytevector (sizeof '*))))
- (err (proc key (canonical-sexp->pointer params))))
- (if (zero? err)
- (pointer->canonical-sexp (dereference-pointer key))
- (throw 'gcry-error 'generate-key err))))))
- (define find-sexp-token
- (let ((proc (libgcrypt->procedure '*
- "gcry_sexp_find_token"
- `(* * ,size_t))))
- (lambda (sexp token)
- "Find in SEXP the first element whose 'car' is TOKEN and return it;
- return #f if not found."
- (let* ((token (string->pointer (symbol->string token)))
- (res (proc (canonical-sexp->pointer sexp) token 0)))
- (if (null-pointer? res)
- #f
- (pointer->canonical-sexp res))))))
- (define-inlinable (canonical-sexp-null? sexp)
- "Return #t if SEXP is the empty-list sexp."
- (null-pointer? (canonical-sexp->pointer sexp)))
- (define (canonical-sexp-list? sexp)
- "Return #t if SEXP is a list."
- (or (canonical-sexp-null? sexp)
- (> (canonical-sexp-length sexp) 0)))
- (define (canonical-sexp-fold proc seed sexp)
- "Fold PROC (as per SRFI-1) over SEXP, a canonical sexp."
- (if (canonical-sexp-list? sexp)
- (let ((len (canonical-sexp-length sexp)))
- (let loop ((index 0)
- (result seed))
- (if (= index len)
- result
- (loop (+ 1 index)
- ;; XXX: Call 'nth-data' *before* 'nth' to work around
- ;; <https://dev.gnupg.org/T1594>, which
- ;; affects 1.6.0 and earlier versions.
- (proc (or (canonical-sexp-nth-data sexp index)
- (canonical-sexp-nth sexp index))
- result)))))
- (error "sexp is not a list" sexp)))
- (define (canonical-sexp->sexp sexp)
- "Return a Scheme sexp corresponding to SEXP. This is particularly useful to
- compare sexps (since Libgcrypt does not provide an 'equal?' procedure), or to
- use pattern matching."
- (if (canonical-sexp-list? sexp)
- (reverse
- (canonical-sexp-fold (lambda (item result)
- (cons (if (canonical-sexp? item)
- (canonical-sexp->sexp item)
- item)
- result))
- '()
- sexp))
- ;; As of Libgcrypt 1.6.0, there's no function to extract the buffer of a
- ;; non-list sexp (!), so we first enlist SEXP, then get at its buffer.
- (let ((sexp (string->canonical-sexp
- (string-append "(" (canonical-sexp->string sexp)
- ")"))))
- (or (canonical-sexp-nth-data sexp 0)
- (canonical-sexp-nth sexp 0)))))
- (define (sexp->canonical-sexp sexp)
- "Return a canonical sexp equivalent to SEXP, a Scheme sexp as returned by
- 'canonical-sexp->sexp'."
- (define (string-hex-pad str)
- (if (odd? (string-length str))
- (string-append "0" str)
- str))
- ;; XXX: This is inefficient, but the Libgcrypt API doesn't allow us to do
- ;; much better.
- (string->canonical-sexp
- (call-with-output-string
- (lambda (port)
- (define (write item)
- (cond ((list? item)
- (display "(" port)
- (for-each write item)
- (display ")" port))
- ((symbol? item)
- (format port " ~a" item))
- ((bytevector? item)
- (format port " #~a#"
- (bytevector->base16-string item)))
- ((integer? item)
- (format port " #~a#"
- (string-hex-pad (number->string item 16))))
- (else
- (error "unsupported sexp item type" item))))
- (write sexp)))))
- ;;; pk-crypto.scm ends here
|