git-authenticate.scm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2020 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 (test-git-authenticate)
  19. #:use-module (git)
  20. #:use-module (guix git)
  21. #:use-module (guix git-authenticate)
  22. #:use-module (guix openpgp)
  23. #:use-module (guix tests git)
  24. #:use-module (guix tests gnupg)
  25. #:use-module (guix build utils)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-34)
  28. #:use-module (srfi srfi-64)
  29. #:use-module (rnrs bytevectors)
  30. #:use-module (rnrs io ports))
  31. ;; Test the (guix git-authenticate) tools.
  32. (define (gpg+git-available?)
  33. (and (which (git-command))
  34. (which (gpg-command)) (which (gpgconf-command))))
  35. (test-begin "git-authenticate")
  36. (unless (which (git-command)) (test-skip 1))
  37. (test-assert "unsigned commits"
  38. (with-temporary-git-repository directory
  39. '((add "a.txt" "A")
  40. (commit "first commit")
  41. (add "b.txt" "B")
  42. (commit "second commit"))
  43. (with-repository directory repository
  44. (let ((commit1 (find-commit repository "first"))
  45. (commit2 (find-commit repository "second")))
  46. (guard (c ((unsigned-commit-error? c)
  47. (oid=? (git-authentication-error-commit c)
  48. (commit-id commit1))))
  49. (authenticate-commits repository (list commit1 commit2)
  50. #:keyring-reference "master")
  51. 'failed)))))
  52. (unless (gpg+git-available?) (test-skip 1))
  53. (test-assert "signed commits, SHA1 signature"
  54. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  55. %ed25519-secret-key-file)
  56. ;; Force use of SHA1 for signatures.
  57. (call-with-output-file (string-append (getenv "GNUPGHOME") "/gpg.conf")
  58. (lambda (port)
  59. (display "digest-algo sha1" port)))
  60. (with-temporary-git-repository directory
  61. `((add "a.txt" "A")
  62. (add "signer.key" ,(call-with-input-file %ed25519-public-key-file
  63. get-string-all))
  64. (add ".guix-authorizations"
  65. ,(object->string
  66. `(authorizations (version 0)
  67. ((,(key-fingerprint %ed25519-public-key-file)
  68. (name "Charlie"))))))
  69. (commit "first commit"
  70. (signer ,(key-fingerprint %ed25519-public-key-file))))
  71. (with-repository directory repository
  72. (let ((commit (find-commit repository "first")))
  73. (guard (c ((unsigned-commit-error? c)
  74. (oid=? (git-authentication-error-commit c)
  75. (commit-id commit))))
  76. (authenticate-commits repository (list commit)
  77. #:keyring-reference "master")
  78. 'failed))))))
  79. (unless (gpg+git-available?) (test-skip 1))
  80. (test-assert "signed commits, default authorizations"
  81. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  82. %ed25519-secret-key-file)
  83. (with-temporary-git-repository directory
  84. `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
  85. get-string-all))
  86. (commit "zeroth commit")
  87. (add "a.txt" "A")
  88. (commit "first commit"
  89. (signer ,(key-fingerprint %ed25519-public-key-file)))
  90. (add "b.txt" "B")
  91. (commit "second commit"
  92. (signer ,(key-fingerprint %ed25519-public-key-file))))
  93. (with-repository directory repository
  94. (let ((commit1 (find-commit repository "first"))
  95. (commit2 (find-commit repository "second")))
  96. (authenticate-commits repository (list commit1 commit2)
  97. #:default-authorizations
  98. (list (openpgp-public-key-fingerprint
  99. (read-openpgp-packet
  100. %ed25519-public-key-file)))
  101. #:keyring-reference "master"))))))
  102. (unless (gpg+git-available?) (test-skip 1))
  103. (test-assert "signed commits, .guix-authorizations"
  104. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  105. %ed25519-secret-key-file)
  106. (with-temporary-git-repository directory
  107. `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
  108. get-string-all))
  109. (add ".guix-authorizations"
  110. ,(object->string
  111. `(authorizations (version 0)
  112. ((,(key-fingerprint
  113. %ed25519-public-key-file)
  114. (name "Charlie"))))))
  115. (commit "zeroth commit")
  116. (add "a.txt" "A")
  117. (commit "first commit"
  118. (signer ,(key-fingerprint %ed25519-public-key-file)))
  119. (add ".guix-authorizations"
  120. ,(object->string `(authorizations (version 0) ()))) ;empty
  121. (commit "second commit"
  122. (signer ,(key-fingerprint %ed25519-public-key-file)))
  123. (add "b.txt" "B")
  124. (commit "third commit"
  125. (signer ,(key-fingerprint %ed25519-public-key-file))))
  126. (with-repository directory repository
  127. (let ((commit1 (find-commit repository "first"))
  128. (commit2 (find-commit repository "second"))
  129. (commit3 (find-commit repository "third")))
  130. ;; COMMIT1 and COMMIT2 are fine.
  131. (and (authenticate-commits repository (list commit1 commit2)
  132. #:keyring-reference "master")
  133. ;; COMMIT3 is signed by an unauthorized key according to its
  134. ;; parent's '.guix-authorizations' file.
  135. (guard (c ((unauthorized-commit-error? c)
  136. (and (oid=? (git-authentication-error-commit c)
  137. (commit-id commit3))
  138. (bytevector=?
  139. (openpgp-public-key-fingerprint
  140. (unauthorized-commit-error-signing-key c))
  141. (openpgp-public-key-fingerprint
  142. (read-openpgp-packet
  143. %ed25519-public-key-file))))))
  144. (authenticate-commits repository
  145. (list commit1 commit2 commit3)
  146. #:keyring-reference "master")
  147. 'failed)))))))
  148. (unless (gpg+git-available?) (test-skip 1))
  149. (test-assert "signed commits, .guix-authorizations, unauthorized merge"
  150. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  151. %ed25519-secret-key-file
  152. %ed25519bis-public-key-file
  153. %ed25519bis-secret-key-file)
  154. (with-temporary-git-repository directory
  155. `((add "signer1.key"
  156. ,(call-with-input-file %ed25519-public-key-file
  157. get-string-all))
  158. (add "signer2.key"
  159. ,(call-with-input-file %ed25519bis-public-key-file
  160. get-string-all))
  161. (add ".guix-authorizations"
  162. ,(object->string
  163. `(authorizations (version 0)
  164. ((,(key-fingerprint
  165. %ed25519-public-key-file)
  166. (name "Alice"))))))
  167. (commit "zeroth commit")
  168. (add "a.txt" "A")
  169. (commit "first commit"
  170. (signer ,(key-fingerprint %ed25519-public-key-file)))
  171. (branch "devel")
  172. (checkout "devel")
  173. (add "devel/1.txt" "1")
  174. (commit "first devel commit"
  175. (signer ,(key-fingerprint %ed25519bis-public-key-file)))
  176. (checkout "master")
  177. (add "b.txt" "B")
  178. (commit "second commit"
  179. (signer ,(key-fingerprint %ed25519-public-key-file)))
  180. (merge "devel" "merge"
  181. (signer ,(key-fingerprint %ed25519-public-key-file))))
  182. (with-repository directory repository
  183. (let ((master1 (find-commit repository "first commit"))
  184. (master2 (find-commit repository "second commit"))
  185. (devel1 (find-commit repository "first devel commit"))
  186. (merge (find-commit repository "merge")))
  187. (define (correct? c commit)
  188. (and (oid=? (git-authentication-error-commit c)
  189. (commit-id commit))
  190. (bytevector=?
  191. (openpgp-public-key-fingerprint
  192. (unauthorized-commit-error-signing-key c))
  193. (openpgp-public-key-fingerprint
  194. (read-openpgp-packet %ed25519bis-public-key-file)))))
  195. (and (authenticate-commits repository (list master1 master2)
  196. #:keyring-reference "master")
  197. ;; DEVEL1 is signed by an unauthorized key according to its
  198. ;; parent's '.guix-authorizations' file.
  199. (guard (c ((unauthorized-commit-error? c)
  200. (correct? c devel1)))
  201. (authenticate-commits repository
  202. (list master1 devel1)
  203. #:keyring-reference "master")
  204. #f)
  205. ;; MERGE is authorized but one of its ancestors is not.
  206. (guard (c ((unauthorized-commit-error? c)
  207. (correct? c devel1)))
  208. (authenticate-commits repository
  209. (list master1 master2
  210. devel1 merge)
  211. #:keyring-reference "master")
  212. #f)))))))
  213. (unless (gpg+git-available?) (test-skip 1))
  214. (test-assert "signed commits, .guix-authorizations, authorized merge"
  215. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  216. %ed25519-secret-key-file
  217. %ed25519bis-public-key-file
  218. %ed25519bis-secret-key-file)
  219. (with-temporary-git-repository directory
  220. `((add "signer1.key"
  221. ,(call-with-input-file %ed25519-public-key-file
  222. get-string-all))
  223. (add "signer2.key"
  224. ,(call-with-input-file %ed25519bis-public-key-file
  225. get-string-all))
  226. (add ".guix-authorizations"
  227. ,(object->string
  228. `(authorizations (version 0)
  229. ((,(key-fingerprint
  230. %ed25519-public-key-file)
  231. (name "Alice"))))))
  232. (commit "zeroth commit")
  233. (add "a.txt" "A")
  234. (commit "first commit"
  235. (signer ,(key-fingerprint %ed25519-public-key-file)))
  236. (branch "devel")
  237. (checkout "devel")
  238. (add ".guix-authorizations"
  239. ,(object->string ;add the second signer
  240. `(authorizations (version 0)
  241. ((,(key-fingerprint
  242. %ed25519-public-key-file)
  243. (name "Alice"))
  244. (,(key-fingerprint
  245. %ed25519bis-public-key-file))))))
  246. (commit "first devel commit"
  247. (signer ,(key-fingerprint %ed25519-public-key-file)))
  248. (add "devel/2.txt" "2")
  249. (commit "second devel commit"
  250. (signer ,(key-fingerprint %ed25519bis-public-key-file)))
  251. (checkout "master")
  252. (add "b.txt" "B")
  253. (commit "second commit"
  254. (signer ,(key-fingerprint %ed25519-public-key-file)))
  255. (merge "devel" "merge"
  256. (signer ,(key-fingerprint %ed25519-public-key-file)))
  257. ;; After the merge, the second signer is authorized.
  258. (add "c.txt" "C")
  259. (commit "third commit"
  260. (signer ,(key-fingerprint %ed25519bis-public-key-file))))
  261. (with-repository directory repository
  262. (let ((master1 (find-commit repository "first commit"))
  263. (master2 (find-commit repository "second commit"))
  264. (devel1 (find-commit repository "first devel commit"))
  265. (devel2 (find-commit repository "second devel commit"))
  266. (merge (find-commit repository "merge"))
  267. (master3 (find-commit repository "third commit")))
  268. (authenticate-commits repository
  269. (list master1 master2 devel1 devel2
  270. merge master3)
  271. #:keyring-reference "master"))))))
  272. (unless (gpg+git-available?) (test-skip 1))
  273. (test-assert "signed commits, .guix-authorizations removed"
  274. (with-fresh-gnupg-setup (list %ed25519-public-key-file
  275. %ed25519-secret-key-file)
  276. (with-temporary-git-repository directory
  277. `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
  278. get-string-all))
  279. (add ".guix-authorizations"
  280. ,(object->string
  281. `(authorizations (version 0)
  282. ((,(key-fingerprint
  283. %ed25519-public-key-file)
  284. (name "Charlie"))))))
  285. (commit "zeroth commit")
  286. (add "a.txt" "A")
  287. (commit "first commit"
  288. (signer ,(key-fingerprint %ed25519-public-key-file)))
  289. (remove ".guix-authorizations")
  290. (commit "second commit"
  291. (signer ,(key-fingerprint %ed25519-public-key-file)))
  292. (add "b.txt" "B")
  293. (commit "third commit"
  294. (signer ,(key-fingerprint %ed25519-public-key-file))))
  295. (with-repository directory repository
  296. (let ((commit1 (find-commit repository "first"))
  297. (commit2 (find-commit repository "second"))
  298. (commit3 (find-commit repository "third")))
  299. ;; COMMIT1 and COMMIT2 are fine.
  300. (and (authenticate-commits repository (list commit1 commit2)
  301. #:keyring-reference "master")
  302. ;; COMMIT3 is rejected because COMMIT2 removes
  303. ;; '.guix-authorizations'.
  304. (guard (c ((unauthorized-commit-error? c)
  305. (oid=? (git-authentication-error-commit c)
  306. (commit-id commit2))))
  307. (authenticate-commits repository
  308. (list commit1 commit2 commit3)
  309. #:keyring-reference "master")
  310. 'failed)))))))
  311. (test-end "git-authenticate")