#!/usr/bin/env ruby
#
-# Copyright © 2018-2020 Nick Bowler
+# Copyright © 2018-2022 Nick Bowler
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@items.create("", attrs, "#{dir}/index.lst")
end
+
+ create_wkd_items(@items["/pubring.gpg"])
end
postprocess do
write @item.identifier.without_ext + '.css'
end
+compile '/gpg/*' do
+ filter :wkd_export, armor: true
+ write "/pubring/" + @item.identifier.components.last + ".asc"
+end
+
+compile '/gpg/*', rep: :hu do
+ filter :wkd_export
+ write "/pubring/wkd/" + @item[:wkd_hash]
+end
+
+compile '/*.gpg' do
+end
+
compile '/**/*.svg' do
filter :scour, comment_stripping: true
write @item.identifier.to_s
--- /dev/null
+# Nick's web site: Export public keys for the Web Key Directory
+#
+# Copyright © 2022 Nick Bowler
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+module WKD
+ require 'open3'
+
+ @@gpg2 = "/usr/bin/gpg"
+ @@wksclient = "/usr/libexec/gpg-wks-client"
+
+ def WKD.gpg2; @@gpg2 end
+ def WKD.gpg2=(x); @@gpg2 = x end
+ def WKD.wksclient; @@wksclient end
+ def WKD.wksclient=(x); @@wksclient = x end
+
+ # Return a list list of all UIDs known from the given GPG keyrings.
+ def WKD.uids_from_keyrings(*args)
+ uids = {}
+
+ Open3.popen2(@@gpg2,
+ "--no-default-keyring", "--with-colons", "--list-keys",
+ *args.map { |x| "--keyring=" + (x['/'] ? x : "./" + x) }
+ ) do |stdin, stdout, result|
+ stdin.close
+ stdout.each do |line|
+ fields = line.split(":")
+ next if fields[0] != "uid"
+ fields[9].gsub!(/\\x../) { |x| x[2..].hex.chr }
+ uids[fields[9]] = true
+ end
+ stdout.close
+
+ raise "gpg failed" unless result.value.success?
+ end
+
+ return uids.keys
+ end
+
+ # Given a list of UIDs, return a dictionary where the keys are UIDs
+ # and the values are the WKS hash.
+ def WKD.hashes_from_uids(*args)
+ wkd_hash = {}
+
+ consume_output = Proc.new do |s|
+ while l = s.slice!(/([^\n]*)\n/) do
+ hash, uid = l.chomp.split(nil, 2)
+ wkd_hash[uid] = hash
+ end
+ end
+
+ Open3.popen2(@@wksclient,
+ "--print-wkd-hash"
+ ) do |stdin, stdout, result|
+ buf = ""
+ args.flatten.each do |uid|
+ stdin.puts(uid)
+ stdin.flush
+
+ loop do
+ buf += stdout.read_nonblock(100)
+ consume_output.call(buf)
+ rescue EOFError, IO::WaitReadable
+ break
+ end
+ end
+ stdin.close
+
+ loop do
+ buf += stdout.readpartial(100)
+ consume_output.call(buf)
+ rescue EOFError
+ break
+ end
+ stdout.close
+
+ raise "gpg-wks-client failed" unless result.value.success?
+ end
+
+ return wkd_hash
+ end
+
+end
+
+# Call during preprocessing to create items for each unique UID found in the
+# given keyring items. The items have the identifier /gpg/UID and the content
+# is the same UID. The items are created with the :keyrings attribute set to
+# the list of keyring files and :wkd_hash is for the Web Key Directory.
+def create_wkd_items(keyring_items)
+ keyring_files = {}
+ [*keyring_items].each { |item| keyring_files[item.raw_filename] = true }
+
+ wkd = WKD.hashes_from_uids(WKD.uids_from_keyrings(*keyring_files.keys))
+ wkd.each do |uid, hash|
+ attrs = {
+ keyrings: keyring_files.keys,
+ wkd_hash: hash
+ }
+ @items.create(uid, attrs, "/gpg/" + uid)
+ end
+end
+
+# Convert items created by create_wkd_items into real GPG keyrings.
+class WKDExport < Nanoc::Filter
+ identifier :wkd_export
+ type :text => :binary
+
+ def run(content, params = {})
+ args = @item[:keyrings].map { |x|
+ "--keyring=" + (x['/'] ? x : "./" + x)
+ };
+ args.append("--armor") if params[:armor]
+
+ dummy, result = Open3.capture2(WKD.gpg2, "--export",
+ "--no-default-keyring", "--output=" + output_filename,
+ *args, content.chomp)
+ raise "gpg failed" unless result.success?
+ end
+end
# Nick's web site: Generate a project page from a submodule README.md
#
-# Copyright © 2021 Nick Bowler
+# Copyright © 2021-2022 Nick Bowler
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
obtaining += line.lstrip
end
+ gpg_uid = "nbowler@draconx.ca"
obtaining += "\n\n" + <<~EOF
[gpg]: https://gnupg.org/
+ [keyring]: /pubring/#{gpg_uid}.asc
Use the signature file to verify that the corresponding source
bundle is intact. After downloading both files, if [GnuPG][gpg]
is installed, the signature can be verified with a command like:
- <kbd>gpg --verify #{targz}.sig</kbd>
+ <kbd>gpg --verify #{targz}.sig #{targz}</kbd>
If the verification fails because you don't have the required
public key, that key can be imported with a command such as:
- <kbd>gpg --keyserver keys.gnupg.net --recv-keys 5B45D3D185B8E1F6</kbd>
+ <kbd>gpg --locate-external-keys #{gpg_uid}</kbd>
- Then run the verify command again.
+ Then run the verify command again. Alternatively, you can
+ [download the public keyring][keyring] manually and import it
+ using `gpg --import`.
EOF
end