gnupg.scm 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2010, 2011, 2013, 2014, 2016, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
  4. ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix gnupg)
  21. #:use-module (ice-9 popen)
  22. #:use-module (ice-9 match)
  23. #:use-module (ice-9 regex)
  24. #:use-module (ice-9 rdelim)
  25. #:use-module (ice-9 i18n)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (guix i18n)
  28. #:use-module ((guix utils) #:select (config-directory))
  29. #:use-module ((guix build utils) #:select (mkdir-p))
  30. #:export (%gpg-command
  31. %openpgp-key-server
  32. current-keyring
  33. gnupg-verify
  34. gnupg-verify*
  35. gnupg-status-good-signature?
  36. gnupg-status-missing-key?))
  37. ;;; Commentary:
  38. ;;;
  39. ;;; GnuPG interface.
  40. ;;;
  41. ;;; Code:
  42. (define %gpg-command
  43. ;; The GnuPG 2.x command-line program name.
  44. (make-parameter (or (getenv "GUIX_GPG_COMMAND") "gpg")))
  45. (define %gpgv-command
  46. ;; The 'gpgv' program.
  47. (make-parameter (or (getenv "GUIX_GPGV_COMMAND") "gpgv")))
  48. (define current-keyring
  49. ;; The default keyring of "trusted keys".
  50. (make-parameter (string-append (config-directory #:ensure? #f)
  51. "/gpg/trustedkeys.kbx")))
  52. (define %openpgp-key-server
  53. ;; The default key server. Note that keys.gnupg.net appears to be
  54. ;; unreliable.
  55. (make-parameter "pool.sks-keyservers.net"))
  56. ;; Regexps for status lines. See file `doc/DETAILS' in GnuPG.
  57. (define sigid-rx
  58. (make-regexp
  59. "^\\[GNUPG:\\] SIG_ID ([A-Za-z0-9+/]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+)"))
  60. (define goodsig-rx
  61. (make-regexp "^\\[GNUPG:\\] GOODSIG ([[:xdigit:]]+) (.+)$"))
  62. (define validsig-rx
  63. (make-regexp
  64. "^\\[GNUPG:\\] VALIDSIG ([[:xdigit:]]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+) .*$"))
  65. (define expkeysig-rx ; good signature, but expired key
  66. (make-regexp "^\\[GNUPG:\\] EXPKEYSIG ([[:xdigit:]]+) (.*)$"))
  67. (define revkeysig-rx ; good signature, but revoked key
  68. (make-regexp "^\\[GNUPG:\\] REVKEYSIG ([[:xdigit:]]+) (.*)$"))
  69. (define errsig-rx
  70. ;; Note: The fingeprint part (the last element of the line) appeared in
  71. ;; GnuPG 2.2.7 according to 'doc/DETAILS', and it may be missing.
  72. (make-regexp
  73. "^\\[GNUPG:\\] ERRSIG ([[:xdigit:]]+) ([^ ]+) ([^ ]+) ([^ ]+) ([[:digit:]]+) ([[:digit:]]+)(.*)"))
  74. (define* (gnupg-verify sig file
  75. #:optional (keyring (current-keyring)))
  76. "Verify signature SIG for FILE against the keys in KEYRING. All the keys in
  77. KEYRING as assumed to be \"trusted\", whether or not they expired or were
  78. revoked. Return a status s-exp if GnuPG failed."
  79. (define (maybe-fingerprint str)
  80. (match (string-trim-both str)
  81. ((or "-" "") #f)
  82. (fpr fpr)))
  83. (define (status-line->sexp line)
  84. (cond ((regexp-exec sigid-rx line)
  85. =>
  86. (lambda (match)
  87. `(signature-id ,(match:substring match 1) ; sig id
  88. ,(match:substring match 2) ; date
  89. ,(string->number ; timestamp
  90. (match:substring match 3)))))
  91. ((regexp-exec goodsig-rx line)
  92. =>
  93. (lambda (match)
  94. `(good-signature ,(match:substring match 1) ; key id
  95. ,(match:substring match 2)))) ; user name
  96. ((regexp-exec validsig-rx line)
  97. =>
  98. (lambda (match)
  99. `(valid-signature ,(match:substring match 1) ; fingerprint
  100. ,(match:substring match 2) ; sig creation date
  101. ,(string->number ; timestamp
  102. (match:substring match 3)))))
  103. ((regexp-exec expkeysig-rx line)
  104. =>
  105. (lambda (match)
  106. `(expired-key-signature ,(match:substring match 1) ; fingerprint
  107. ,(match:substring match 2)))) ; user name
  108. ((regexp-exec revkeysig-rx line)
  109. =>
  110. (lambda (match)
  111. `(revoked-key-signature ,(match:substring match 1) ; fingerprint
  112. ,(match:substring match 2)))) ; user name
  113. ((regexp-exec errsig-rx line)
  114. =>
  115. (lambda (match)
  116. `(signature-error ,(match:substring match 1) ; key id
  117. ,(match:substring match 2) ; pubkey algo
  118. ,(match:substring match 3) ; hash algo
  119. ,(match:substring match 4) ; sig class
  120. ,(string->number ; timestamp
  121. (match:substring match 5))
  122. ,(let ((rc
  123. (string->number ; return code
  124. (match:substring match 6))))
  125. (case rc
  126. ((9) 'missing-key)
  127. ((4) 'unknown-algorithm)
  128. (else rc)))
  129. ,(maybe-fingerprint ; fingerprint or #f
  130. (match:substring match 7)))))
  131. (else
  132. `(unparsed-line ,line))))
  133. (define (parse-status input)
  134. (let loop ((line (read-line input))
  135. (result '()))
  136. (if (eof-object? line)
  137. (reverse result)
  138. (loop (read-line input)
  139. (cons (status-line->sexp line) result)))))
  140. (let* ((pipe (open-pipe* OPEN_READ (%gpgv-command) "--status-fd=1"
  141. "--keyring" keyring sig file))
  142. (status (parse-status pipe)))
  143. ;; Ignore PIPE's exit status since STATUS above should contain all the
  144. ;; info we need.
  145. (close-pipe pipe)
  146. status))
  147. (define (gnupg-status-good-signature? status)
  148. "If STATUS, as returned by `gnupg-verify', denotes a good signature, return
  149. a fingerprint/user pair; return #f otherwise."
  150. (match (assq 'valid-signature status)
  151. (('valid-signature fingerprint date timestamp)
  152. (match (or (assq 'good-signature status)
  153. (assq 'expired-key-signature status)
  154. (assq 'revoked-key-signature status))
  155. ((_ key-id user) (cons fingerprint user))
  156. (_ #f)))
  157. (_
  158. #f)))
  159. (define (gnupg-status-missing-key? status)
  160. "If STATUS denotes a missing-key error, then return the fingerprint of the
  161. missing key or its key id if the fingerprint is unavailable."
  162. (any (lambda (sexp)
  163. (match sexp
  164. (('signature-error key-id _ ... 'missing-key fingerprint)
  165. (or fingerprint key-id))
  166. (_ #f)))
  167. status))
  168. (define* (gnupg-receive-keys fingerprint/key-id server
  169. #:optional (keyring (current-keyring)))
  170. "Download FINGERPRINT/KEY-ID from SERVER, a key server, and add it to
  171. KEYRING."
  172. (unless (file-exists? keyring)
  173. (mkdir-p (dirname keyring))
  174. (call-with-output-file keyring (const #t))) ;create an empty keybox
  175. (zero? (system* (%gpg-command) "--keyserver" server
  176. "--no-default-keyring" "--keyring" keyring
  177. "--recv-keys" fingerprint/key-id)))
  178. (define* (gnupg-verify* sig file
  179. #:key
  180. (key-download 'interactive)
  181. (server (%openpgp-key-server))
  182. (keyring (current-keyring)))
  183. "Like `gnupg-verify', but try downloading the public key if it's missing.
  184. Return two values: 'valid-signature and a fingerprint/name pair upon success,
  185. 'missing-key and a fingerprint if the key could not be found, and
  186. 'invalid-signature with a fingerprint if the signature is invalid.
  187. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
  188. values: 'always', 'never', and 'interactive' (default). Return a
  189. fingerprint/user name pair on success and #f otherwise."
  190. (let ((status (gnupg-verify sig file)))
  191. (match (gnupg-status-good-signature? status)
  192. ((fingerprint . user)
  193. (values 'valid-signature (cons fingerprint user)))
  194. (#f
  195. (let ((missing (gnupg-status-missing-key? status)))
  196. (define (download-and-try-again)
  197. ;; Download the missing key and try again.
  198. (if (gnupg-receive-keys missing server keyring)
  199. (match (gnupg-status-good-signature?
  200. (gnupg-verify sig file keyring))
  201. (#f
  202. (values 'invalid-signature missing))
  203. ((fingerprint . user)
  204. (values 'valid-signature
  205. (cons fingerprint user))))
  206. (values 'missing-key missing)))
  207. (define (receive?)
  208. (let ((answer
  209. (begin
  210. (format #t (G_ "Would you like to add this key \
  211. to keyring '~a'?~%")
  212. keyring)
  213. (read-line))))
  214. (string-match (locale-yes-regexp) answer)))
  215. (case key-download
  216. ((never)
  217. (values 'missing-key missing))
  218. ((always)
  219. (download-and-try-again))
  220. (else
  221. (if (receive?)
  222. (download-and-try-again)
  223. (values 'missing-key missing)))))))))
  224. ;;; gnupg.scm ends here