# 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 . 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