# 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) # 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) 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 generate_each_nick(site, keyring, nick, (keyring_fps & fps)) rescue # fail them silently end end end # policy file is required site.pages << WKDPolicyFile.new(site) Base32.table = old_base32_table end end class WKDFile < Jekyll::Page def initialize(site, nick, keydata) @site = site @base = @site.source @dir = WKDGenerator::WKD_DIR + 'hu/' @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) @site = site @base = @site.source @dir = WKDGenerator::WKD_DIR @name = 'policy' process(@name) read_yaml(File.join(@base, '_layouts'), 'passthrough.html') @content = '' end end end # vim:et ts=2 sts=2: