]> git.draconx.ca Git - homepage.git/blob - lib/gpg-wkd.rb
Ensure ASCII-armoured keyrings are text items.
[homepage.git] / lib / gpg-wkd.rb
1 # Nick's web site: Export public keys for the Web Key Directory
2 #
3 # Copyright © 2022 Nick Bowler
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18 module WKD
19     require 'open3'
20
21     @@gpg2 = "/usr/bin/gpg"
22     @@wksclient = "/usr/libexec/gpg-wks-client"
23
24     def WKD.gpg2; @@gpg2 end
25     def WKD.gpg2=(x); @@gpg2 = x end
26     def WKD.wksclient; @@wksclient end
27     def WKD.wksclient=(x); @@wksclient = x end
28
29     # Convert a list of keyring filenames into GPG keyring arguments
30     def WKD.keyring_args(args)
31         return [ "--no-default-keyring",
32             *args.map { |x| "--keyring=" + (x['/'] ? x : "./" + x) } ]
33     end
34
35     # Helper for implementing export filters below
36     def WKD.export(item, uid, *args)
37         data, result = Open3.capture2(@@gpg2, "--export", *args,
38             *WKD.keyring_args(item[:keyrings]), uid.chomp)
39         raise "gpg failed" unless result.success?
40         return data
41     end
42
43     # Return a list list of all UIDs known from the given GPG keyrings.
44     def WKD.uids_from_keyrings(*args)
45         uids = {}
46
47         Open3.popen2(@@gpg2,
48             "--with-colons", "--list-keys", *WKD.keyring_args(args)
49         ) do |stdin, stdout, result|
50             stdin.close
51             stdout.each do |line|
52                 fields = line.split(":")
53                 next if fields[0] != "uid"
54                 fields[9].gsub!(/\\x../) { |x| x[2..].hex.chr }
55                 uids[fields[9]] = true
56             end
57             stdout.close
58
59             raise "gpg failed" unless result.value.success?
60         end
61
62         return uids.keys
63     end
64
65     # Given a list of UIDs, return a dictionary where the keys are UIDs
66     # and the values are the WKS hash.
67     def WKD.hashes_from_uids(*args)
68         wkd_hash = {}
69
70         consume_output = Proc.new do |s|
71             while l = s.slice!(/([^\n]*)\n/) do
72                 hash, uid = l.chomp.split(nil, 2)
73                 wkd_hash[uid] = hash
74             end
75         end
76
77         Open3.popen2(@@wksclient,
78             "--print-wkd-hash"
79         ) do |stdin, stdout, result|
80             buf = ""
81             args.flatten.each do |uid|
82                 stdin.puts(uid)
83                 stdin.flush
84
85                 loop do
86                     buf += stdout.read_nonblock(100)
87                     consume_output.call(buf)
88                 rescue EOFError, IO::WaitReadable
89                     break
90                 end
91             end
92             stdin.close
93
94             loop do
95                 buf += stdout.readpartial(100)
96                 consume_output.call(buf)
97             rescue EOFError
98                 break
99             end
100             stdout.close
101
102             raise "gpg-wks-client failed" unless result.value.success?
103         end
104
105         return wkd_hash
106     end
107
108 end
109
110 # Call during preprocessing to create items for each unique UID found in the
111 # given keyring items.  The items have the identifier /gpg/UID and the content
112 # is the same UID.  The items are created with the :keyrings attribute set to
113 # the list of keyring files and :wkd_hash is for the Web Key Directory.
114 def create_wkd_items(keyring_items)
115     keyring_files = {}
116     [*keyring_items].each { |item| keyring_files[item.raw_filename] = true }
117
118     wkd = WKD.hashes_from_uids(WKD.uids_from_keyrings(*keyring_files.keys))
119     wkd.each do |uid, hash|
120         attrs = {
121             keyrings: keyring_files.keys,
122             wkd_hash: hash
123         }
124         @items.create(uid, attrs, "/gpg/" + uid)
125     end
126 end
127
128 # Convert items created by create_wkd_items into real GPG keyrings.
129 class WKDExport < Nanoc::Filter
130     identifier :wkd_export
131     type :text => :binary
132
133     def run(content, params = {})
134         WKD.export(item, content, "--output=" + output_filename)
135     end
136 end
137
138 class WKDExportArmor < Nanoc::Filter
139     identifier :wkd_export_armor
140     type :text
141
142     def run(content, params = {})
143         data = WKD.export(item, content, "--armor")
144         return data
145     end
146 end