gnupg.scm 10 KB

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