-
-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New implementation for signing releases #274
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,56 +2,74 @@ | |
(:require | ||
[clojure.java.io :as io] | ||
[boot.pod :as pod] | ||
[mvxcvi.crypto.pgp :as pgp]) | ||
(clj-pgp | ||
[core :as pgp] | ||
[keyring :as keyring] | ||
[signature :as pgp-sig])) | ||
(:import | ||
[java.util.regex Pattern] | ||
[java.io StringBufferInputStream])) | ||
|
||
(def default-secring | ||
(-> (System/getProperty "user.home") | ||
(io/file ".gnupg" "secring.gpg"))) | ||
|
||
(defn secring | ||
[& [file]] | ||
(pgp/load-secret-keyring (io/file (or file default-secring)))) | ||
|
||
(defn get-pubkey | ||
[secring & [user]] | ||
(let [ks (pgp/list-public-keys secring) | ||
re-user #(re-find (re-pattern (Pattern/quote (str user))) %) | ||
match? #(some re-user (-> % pgp/key-info :user-ids))] | ||
(let [ks' (if-not user ks (filter match? ks))] | ||
(if (or (not user) (<= (count ks') 1)) | ||
(first ks') | ||
(throw (ex-info "multiple keys match" | ||
{:keys (mapv pgp/key-info ks')})))))) | ||
|
||
(defn secret-key-for | ||
[secring pubkey] | ||
(pgp/get-secret-key secring (pgp/key-id pubkey))) | ||
|
||
(defn prompt-for | ||
[keyring user-id] | ||
(if-let [pk (-> keyring secring (get-pubkey user-id))] | ||
(-> pk pgp/key-info :user-ids first (str "\nGPG passphrase: ")) | ||
(throw (Exception. (format "public key not found (%s)" user-id))))) | ||
|
||
(defn sign | ||
[content passphrase & {:keys [keyring user-id]}] | ||
(let [sr (secring keyring) | ||
pk (get-pubkey sr user-id) | ||
sk (secret-key-for sr pk) | ||
pr (pgp/unlock-key sk passphrase)] | ||
(-> content (pgp/sign :sha1 pr) pgp/encode-ascii))) | ||
(defn find-secring [] | ||
(let [home (System/getenv "HOME") | ||
locations [(System/getenv "GNUPGHOME") (System/getenv "BOOT_GPG_SECRING") (str home "/.gnupg/secring.gpg") (str home "/.gpg/secring.gpg")] | ||
exists-fn #(when (and % (.exists (io/as-file %))) %) | ||
secring (some exists-fn locations)] | ||
(or secring (throw (Exception. "Can't find secring file. Please set 'BOOT_GPG_SECRING'"))))) | ||
|
||
(defn get-keyring [] | ||
(keyring/load-secret-keyring (io/file (find-secring)))) | ||
|
||
(defn find-master-key-by-user [user] | ||
(let [keyring (get-keyring) | ||
pub-keys (keyring/list-public-keys keyring) | ||
re-user #(re-find (re-pattern user) %) | ||
match? #(some re-user (-> % pgp/key-info :user-ids)) | ||
key (some #(when (match? %) %) pub-keys)] | ||
(or key (throw (Exception. (format "User %s not found" user)))))) | ||
|
||
(defn find-master-key [] | ||
(if-let [user (System/getenv "BOOT_GPG_USER")] | ||
(find-master-key-by-user user) | ||
(let [keyring (get-keyring) | ||
master-keys (filter (comp :master-key? pgp/key-info) (keyring/list-public-keys keyring))] | ||
(cond | ||
(= 0 (count master-keys)) (throw (Exception. "No master key found")) | ||
(= 1 (count master-keys)) (first master-keys) | ||
:else (throw (Exception. "Multiple master keys found. Please set 'BOOT_GPG_USER'")))))) | ||
|
||
(defn get-private-key-by-id [hex-id passphrase] | ||
(let [keyring (get-keyring) | ||
seckey (keyring/get-secret-key keyring hex-id)] | ||
(pgp/unlock-key seckey passphrase))) | ||
|
||
(defn get-private-key [passphrase] | ||
(let [keyring (get-keyring)] | ||
(if-let [hex-id (System/getenv "BOOT_GPG_SIGNING_KEY_ID")] | ||
(get-private-key-by-id hex-id passphrase) | ||
(-> (find-master-key) | ||
pgp/key-info | ||
:key-id | ||
(#(keyring/get-secret-key keyring %)) | ||
(pgp/unlock-key passphrase))))) | ||
|
||
(defn read-pass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm concerned about having a prompt as the only way to pass a passphrase around. Unattended builds (CIs) need another option to be able to run. Maybe it's possible to use the .gpgconf? I know you can specify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current approach was to use a Java API as opposed to shelling out to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A straightforward solution to your concern would be to add a BOOT_GPG_PASSPHRASE environment variable that, if set, will be used instead of the prompt. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know, lein uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re: environmental variables adding another one might be more in line with the implementation of this pull request, but I'm actually arguing it would be better to remove all of them and pass them explicitly as optional arguments (making the configuration explicit when you call the function rather than relying on the global environment state). Just my 2 cents. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the current PR, GPG configuration is driven by environment variables. They are documented here: https://github.com/boot-clj/boot/wiki/Deploying-with-Boot There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Each environment variable you add to the project adds global complexity and clutter. If it's only used by one task, then why not it be a parameter to the task itself? You get automatic documentation for it and you can set it programmatically rather than shelling out. I mean, fine by all means if you want to give the option to drive the implementation via env vars, but don't take away the task options: they are much more easier to use and repl-friendly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no problem with whatever configuration mechanism we are going to adopt. This PR is a proof of concept that can be refined and finalized by others. @micha has started this https://github.com/adzerk-oss/env which will likely address this problem. Please feel free to contribute to this PR. |
||
[prompt] | ||
(String/valueOf (.readPassword (System/console) prompt nil))) | ||
|
||
(defn sign [content] | ||
(let [passphrase (read-pass (str "\nGPG passphrase: ")) | ||
pr (get-private-key passphrase)] | ||
(-> content (pgp-sig/sign pr) pgp/encode-ascii))) | ||
|
||
(defn sign-jar | ||
[outpath jarpath passphrase & {:keys [keyring user-id]}] | ||
[outpath jarpath] | ||
(let [outdir (doto (io/file outpath) .mkdirs) | ||
jarname (.getName (io/file jarpath)) | ||
pomxml (StringBufferInputStream. (pod/pom-xml jarpath)) | ||
jarout (io/file outdir (str jarname ".asc")) | ||
pomout (io/file outdir (.replaceAll jarname "\\.jar$" ".pom.asc")) | ||
sign-it #(sign % passphrase :keyring keyring :user-id user-id)] | ||
sign-it #(sign %)] | ||
(spit pomout (sign-it pomxml)) | ||
(spit jarout (sign-it (io/input-stream jarpath))) | ||
{[:extension "jar.asc"] (.getPath jarout) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
version=2.2.0 | ||
version=2.2.1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a breaking change to the API, no? We've removed options from tasks, etc. Perhaps we bump to 3.0.0? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to either keep this or move it to
boot.util
? It seems like a handy function to have hanging around.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@micha it's still in
boot.pgp
but I agree that a fn reading user input/passwords could be inboot.util
as well.