aboutsummaryrefslogtreecommitdiff
blob: fff1e372e7d30519e46f238db84545cbcff2adae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# OpenPGP Web Key Directory implementation
# https://www.ietf.org/id/draft-koch-openpgp-webkey-service-06.txt

require 'base32'
require 'digest'

module Gentoo
  class WKDGenerator < Jekyll::Generator
    DEV_KEYRING = '_data/active-devs.gpg'.freeze
    SERVICE_KEYRING = '_data/service-keys.gpg'.freeze
    WKD_DIR = '.well-known/openpgpkey/'.freeze
    GPG_BASE_COMMAND = ['gpg',
                        '--no-auto-check-trustdb',
                        '--no-comments',
                        '--no-default-keyring',
                        '--no-emit-version',
                        '--no-greeting',
                        '--no-permission-warning',
                        '--no-secmem-warning',
                        '--preserve-permissions',
                        '--quiet',
                        '--with-colon',
                        ].freeze

    def generate_each_nick(site, keyring, nick, fps, email_domain)
      # Do not run if we have no fingerprints to do
      # otherwise GPG will print 'gpg: WARNING: nothing exported'
      return if fps.empty?
      gpg = GPG_BASE_COMMAND + ['--keyring', keyring]
      IO.popen(gpg + ['--export', *fps], 'rb') do |p|
        keydata = p.read
        next if keydata.empty?
        site.pages << WKDFile.new(site, nick, keydata)
        site.pages << WKDFile.new(site, nick, keydata, email_domain)
      end
    end

    def get_fingerprints_from_keyring(keyring)
      gpg = GPG_BASE_COMMAND + ['--keyring', keyring]
      # build a quick list of all fingerprints in this keyring
      # IO.popen in a non-block context returns a list of lines
      IO.popen(gpg + ['--list-keys'], 'rt',
               &:readlines).grep(/^fpr:/).map(&:strip).map do |line|
        line.split(':')[9]
      end.compact.map(&:upcase)
    end

    def generate(site)
      return if site.data['userinfo'].nil?

      # WKD uses z-Base32; replace the alphabet since the standard
      # Base32 module supports that and the zBase32 modules are hard to get
      old_base32_table = Base32.table
      Base32.table = 'ybndrfg8ejkmcpqxot1uwisza345h769'.freeze

      [['current', DEV_KEYRING], ['system', SERVICE_KEYRING]].each do |group, keyring|
        keyring_fps = get_fingerprints_from_keyring(keyring)
        # Now loop over users
        site.data['userinfo'][group].each do |nick, details|
          begin
            fps = details['gpgfp'].map { |fp| fp.gsub(/\s+/, '').upcase }
            # Run only on the intersection of fingerprints we want and fingerprints we have
            # TODO: extract the domain here to use for WKD Advanced, for future
            # cases where we have @FOO.gentoo.org emails.
            generate_each_nick(site, keyring, nick, (keyring_fps & fps), 'gentoo.org')
          rescue
            # fail them silently
          end
        end
      end

      # policy file is required
      # Need BOTH:
      # https://example.org/.well-known/openpgpkey/policy
      site.pages << WKDPolicyFile.new(site, email_domain=nil)
      # https://openpgpkey.example.org/.well-known/openpgpkey/example.org/policy
      site.pages << WKDPolicyFile.new(site, email_domain='gentoo.org')
      Base32.table = old_base32_table
    end
  end

  class WKDFile < Jekyll::Page
    def initialize(site, nick, keydata, email_domain=nil)
      @site = site
      @base = @site.source
      # IF email_domain is empty, then you get WKD direct
      # https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe
      # https://example.org/.well-known/openpgpkey/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=Joe.Doe
      dir_parts = [WKDGenerator::WKD_DIR]
      dir_parts << email_domain if !email_domain.nil? && email_domain != ''
      dir_parts << 'hu'
      dir_parts << '' # extra trailing empty element to get trailing slash
      @dir = File.join(dir_parts)
      @name = Base32.encode(Digest::SHA1.digest(nick.downcase))

      process(@name)
      read_yaml(File.join(@base, '_layouts'), 'passthrough.html')

      @content = keydata
    end

    def render_with_liquid?
      false
    end
  end

  class WKDPolicyFile < Jekyll::Page
    def initialize(site, email_domain=nil)
      @site = site
      @base = @site.source
      dir_parts = [WKDGenerator::WKD_DIR]
      dir_parts << email_domain if !email_domain.nil? && email_domain != ''
      dir_parts << '' # extra trailing empty element to get trailing slash
      @dir = File.join(dir_parts)
      @name = 'policy'

      process(@name)
      read_yaml(File.join(@base, '_layouts'), 'passthrough.html')

      @content = ''
    end
  end
end
# vim:et ts=2 sts=2: