pki.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2016, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix pki)
  19. #:use-module (guix config)
  20. #:use-module (gcrypt pk-crypto)
  21. #:use-module ((guix utils) #:select (with-atomic-file-output))
  22. #:use-module ((guix build utils) #:select (mkdir-p))
  23. #:autoload (srfi srfi-1) (delete-duplicates)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 rdelim)
  26. #:export (%public-key-file
  27. %private-key-file
  28. %acl-file
  29. current-acl
  30. public-keys->acl
  31. acl->public-keys
  32. authorized-key?
  33. write-acl
  34. signature-sexp
  35. signature-subject
  36. signature-signed-data
  37. valid-signature?
  38. signature-case))
  39. ;;; Commentary:
  40. ;;;
  41. ;;; Public key infrastructure for the authentication and authorization of
  42. ;;; archive imports. This is essentially a subset of SPKI for our own
  43. ;;; purposes (see <http://theworld.com/~cme/spki.txt> and
  44. ;;; <http://www.ietf.org/rfc/rfc2693.txt>.)
  45. ;;;
  46. ;;; Code:
  47. (define (public-keys->acl keys)
  48. "Return an ACL that lists all of KEYS with a '(guix import)'
  49. tag---meaning that all of KEYS are authorized for archive imports. Each
  50. element in KEYS must be a canonical sexp with type 'public-key'."
  51. ;; Use SPKI-style ACL entry sexp for PUBLIC-KEY, authorizing imports
  52. ;; signed by the corresponding secret key (see the IETF draft at
  53. ;; <http://theworld.com/~cme/spki.txt> for the ACL format.)
  54. ;;
  55. ;; Note: We always use PUBLIC-KEY to designate the subject. Someday we may
  56. ;; want to have name certificates and to use subject names instead of
  57. ;; complete keys.
  58. `(acl ,@(map (lambda (key)
  59. `(entry ,key
  60. (tag (guix import))))
  61. (delete-duplicates
  62. (map canonical-sexp->sexp keys)))))
  63. (define %acl-file
  64. (string-append %config-directory "/acl"))
  65. (define %public-key-file
  66. (string-append %config-directory "/signing-key.pub"))
  67. (define %private-key-file
  68. (string-append %config-directory "/signing-key.sec"))
  69. (define (ensure-acl)
  70. "Make sure the ACL file exists, and create an initialized one if needed."
  71. (unless (file-exists? %acl-file)
  72. ;; If there's no public key file, don't attempt to create the ACL.
  73. (when (file-exists? %public-key-file)
  74. (let ((public-key (call-with-input-file %public-key-file
  75. (compose string->canonical-sexp
  76. read-string))))
  77. (mkdir-p (dirname %acl-file))
  78. (with-atomic-file-output %acl-file
  79. (lambda (port)
  80. (write-acl (public-keys->acl (list public-key))
  81. port)))))))
  82. (define (write-acl acl port)
  83. "Write ACL to PORT in canonical-sexp format."
  84. (let ((sexp (sexp->canonical-sexp acl)))
  85. (display (canonical-sexp->string sexp) port)))
  86. (define (current-acl)
  87. "Return the current ACL."
  88. (ensure-acl)
  89. (if (file-exists? %acl-file)
  90. (call-with-input-file %acl-file
  91. (compose canonical-sexp->sexp
  92. string->canonical-sexp
  93. read-string))
  94. (public-keys->acl '()))) ; the empty ACL
  95. (define (acl->public-keys acl)
  96. "Return the public keys (as canonical sexps) listed in ACL with the '(guix
  97. import)' tag."
  98. (match acl
  99. (('acl
  100. ('entry subject-keys
  101. ('tag ('guix 'import)))
  102. ...)
  103. (map sexp->canonical-sexp subject-keys))
  104. (_
  105. (error "invalid access-control list" acl))))
  106. (define* (authorized-key? key #:optional (acl (current-acl)))
  107. "Return #t if KEY (a canonical sexp) is an authorized public key for archive
  108. imports according to ACL."
  109. ;; Note: ACL is kept in native sexp form to make 'authorized-key?' faster,
  110. ;; by not having to convert it with 'canonical-sexp->sexp' on each call.
  111. ;; TODO: We could use a better data type for ACLs.
  112. (let ((key (canonical-sexp->sexp key)))
  113. (match acl
  114. (('acl
  115. ('entry subject-keys
  116. ('tag ('guix 'import)))
  117. ...)
  118. (not (not (member key subject-keys))))
  119. (_
  120. (error "invalid access-control list" acl)))))
  121. (define (signature-sexp data secret-key public-key)
  122. "Return a SPKI-style sexp for the signature of DATA with SECRET-KEY that
  123. includes DATA, the actual signature value (with a 'sig-val' tag), and
  124. PUBLIC-KEY (see <http://theworld.com/~cme/spki.txt> for examples.)"
  125. (string->canonical-sexp
  126. (format #f
  127. "(signature ~a ~a ~a)"
  128. (canonical-sexp->string data)
  129. (canonical-sexp->string (sign data secret-key))
  130. (canonical-sexp->string public-key))))
  131. (define (signature-subject sig)
  132. "Return the signer's public key for SIG."
  133. (find-sexp-token sig 'public-key))
  134. (define (signature-signed-data sig)
  135. "Return the signed data from SIG, typically an sexp such as
  136. (hash \"sha256\" #...#)."
  137. (find-sexp-token sig 'data))
  138. (define (valid-signature? sig)
  139. "Return #t if SIG is valid."
  140. (let* ((data (signature-signed-data sig))
  141. (signature (find-sexp-token sig 'sig-val))
  142. (public-key (signature-subject sig)))
  143. (and data signature
  144. (verify signature data public-key))))
  145. (define* (%signature-status signature hash
  146. #:optional (acl (current-acl)))
  147. "Return a symbol denoting the status of SIGNATURE vs. HASH vs. ACL.
  148. This procedure must only be used internally, because it would be easy to
  149. forget some of the cases."
  150. (let ((subject (signature-subject signature))
  151. (data (signature-signed-data signature)))
  152. (if (and data subject)
  153. (if (authorized-key? subject acl)
  154. (if (equal? (hash-data->bytevector data) hash)
  155. (if (valid-signature? signature)
  156. 'valid-signature
  157. 'invalid-signature)
  158. 'hash-mismatch)
  159. 'unauthorized-key)
  160. 'corrupt-signature)))
  161. (define-syntax signature-case
  162. (syntax-rules (valid-signature invalid-signature
  163. hash-mismatch unauthorized-key corrupt-signature
  164. else)
  165. "\
  166. Match the cases of the verification of SIGNATURE against HASH and ACL:
  167. - the 'valid-signature' case if SIGNATURE is indeed a signature of HASH with
  168. a key present in ACL;
  169. - 'invalid-signature' if SIGNATURE is incorrect;
  170. - 'hash-mismatch' if the hash in SIGNATURE does not match HASH;
  171. - 'unauthorized-key' if the public key in SIGNATURE is not listed in ACL;
  172. - 'corrupt-signature' if SIGNATURE is not a valid signature sexp.
  173. This macro guarantees at compile-time that all these cases are handled.
  174. SIGNATURE, and ACL must be canonical sexps; HASH must be a bytevector."
  175. ;; Simple case: we only care about valid signatures.
  176. ((_ (signature hash acl)
  177. (valid-signature valid-exp ...)
  178. (else else-exp ...))
  179. (case (%signature-status signature hash acl)
  180. ((valid-signature) valid-exp ...)
  181. (else else-exp ...)))
  182. ;; Full case.
  183. ((_ (signature hash acl)
  184. (valid-signature valid-exp ...)
  185. (invalid-signature invalid-exp ...)
  186. (hash-mismatch mismatch-exp ...)
  187. (unauthorized-key unauthorized-exp ...)
  188. (corrupt-signature corrupt-exp ...))
  189. (case (%signature-status signature hash acl)
  190. ((valid-signature) valid-exp ...)
  191. ((invalid-signature) invalid-exp ...)
  192. ((hash-mismatch) mismatch-exp ...)
  193. ((unauthorized-key) unauthorized-exp ...)
  194. ((corrupt-signature) corrupt-exp ...)
  195. (else (error "bogus signature status"))))))
  196. ;;; pki.scm ends here