Update GPG signature checking

f38
David Woodhouse 8 years ago
parent 3ad9d1db2c
commit 8196b1c240

@ -0,0 +1,316 @@
# The gpg_verify macro is defined further down in this document.
# gpg_verify takes one option and a list of 2- or 3-tuples.
#
# With no arguments, attempts to figure everything out. Finds one keyring and
# tries to pair each signature file with a source. If there is no source found
# which matches a signature, the build is aborted.
#
# -k gives a common keyring to verify all signatures against, except when an
# argument specifies its own keyring.
#
# Each argument must be of the form "F,S,K" or "F,S", where each of F, S and K
# is either the number or the filename of one of the source files in the
# package. A pathname including directories is not allowed.
# F is a source file to check.
# S is a signature.
# K is a keyring.
#
# When an argument specifies a keyring, that signature will be verified against
# the keys in that keyring. For arguments that don't specify a keyring, the one
# specified with -k will be used, if any. If no keyring is specified either
# way, the macro will default to the first one it finds in the source list.
#
# It is assumed that all the keys in all keyrings, whether automatically found
# or explicitly specified, are trusted to authenticate the source files. There
# must not be any untrusted keys included.
# Some utility functions to the global namespace
# Most of these should come from the utility macros in the other repo.
%define gpg_macros_init %{lua:
function db(str)
io.stderr:write(tostring(str) .. '\\n')
end
\
-- Simple basename clone
function basename(str)
local name = string.gsub(str, "(.*/)(.*)", "%2")
return name
end
\
-- Get the numbered or source file.
-- The spec writer can use any numbering scheme. The sources table
-- always counts from 1 and has no gaps, so we have to go back to the
-- SOURCEN macros.
function get_numbered_source(num)
local macro = "%SOURCE" .. num
local val = rpm.expand(macro)
if val == macro then
return nil
end
return val
end
-- Get the named source file. This returns the full path to a source file,
-- or nil if no such source exists.
function get_named_source(name)
local path
for _,path in ipairs(sources) do
if name == basename(path) then
return path
end
end
return nil
end
\
-- Determine whether the supplied filename contains a signature
-- Assumes the file will be closed when the handle goes out of scope
function is_signature(fname)
-- I don't really like this, but you can have completely binary sigs
if string.find(fname, '%.sig$') then
return true
end
local file = io.open(fname, 'r')
if file == nil then return false end
\
local c = 1
while true do
local line = file:read('*line')
if (line == nil or c > 10) then break end
if string.find(line, "BEGIN PGP SIGNATURE") then
return true
end
c = c+1
end
return false
end
\
-- Determine whether the supplied filename looks like a keyring
-- Ends in .gpg (might be binary data)? Contains "BEGIN PGP PUBLIC KEY BLOCK"
function is_keyring(fname)
-- XXX Have to hack this now to make it not find macros.gpg while we're testing.
if string.find(fname, '%.gpg$') and not string.find(fname, 'macros.gpg$') then
return true
end
\
local file = io.open(fname, 'r')
if file == nil then return false end
io.input(file)
local c = 1
while true do
local line = io.read('*line')
if (line == nil or c > 10) then break end
if string.find(line, "BEGIN PGP PUBLIC KEY BLOCK") then
return true
end
c = c+1
end
return false
end
\
-- Output code to have the current scriptlet echo something
function echo(str)
print("echo " .. str .. "\\n")
end
\
-- Output an exit statement with nonzero return to the current scriptlet
function exit()
print("exit 1\\n")
end
\
-- Call the RPM %error macro
function rpmerror(str)
echo("gpg_verify: " .. str)
rpm.expand("%{error:gpg_verify: " .. str .. "}")
exit(1)
end
\
-- XXX How to we get just a flag and no option?
function getflag(flag)
return nil
end
\
-- Extract the value of a passed option
function getoption(opt)
out = rpm.expand("%{-" .. opt .. "*}")
-- if string.len(out) == 0 then
if #out == 0 then
return nil
end
return out
end
\
function unknownarg(a)
rpmerror("Unknown argument to %%gpg_verify: " .. a)
end
\
function rprint(s, l, i) -- recursive Print (structure, limit, indent)
l = (l) or 100; i = i or ""; -- default item limit, indent string
if (l<1) then db("ERROR: Item limit reached."); return l-1 end;
local ts = type(s);
if (ts ~= "table") then db(i,ts,s); return l-1 end
db(i,ts); -- print "table"
for k,v in pairs(s) do -- db("[KEY] VALUE")
l = rprint(v, l, i.."\t["..tostring(k).."]");
if (l < 0) then break end
end
return l
end
\
-- Given a list of source file numbers or file names, validate them and
-- convert them to a list of full filenames.
function check_sources_list(arr)
local files = {}
local src,fpath
for _, src in ipairs(arr) do
if tonumber(src) then
-- We have a number; turn it to a full path to the corresponding source file
fpath = get_numbered_source(src)
else
fpath = get_named_source(src)
end
if not src then
err = 'Not a valid source: ' .. src
if src == '1' then
err = err .. '. Note that "Source:" is the 0th source file, not the 1st.'
end
rpmerror(err)
end
table.insert(files, fpath)
end
return files
end
rpm.define("gpg_macros_init %{nil}")
}#
# The actual macro
%define gpg_verify(k:) %gpg_macros_init%{lua:
-- RPM will ignore the first thing we output unless we give it a newline.
print('\\n')
\
local defkeyspec = getoption("k")
local args = rpm.expand("%*")
local sourcefiles = {}
local signature_table = {}
local signatures = {}
local keyrings = {}
local defkey, match, captures, s
\
local function storematch(m, c)
match = m; captures = c
end
\
-- Scan all of the sources and try to categorize them.
-- Move to a function
for i,s in pairs(sources) do
sourcefiles[s] = true
-- db('File: ' .. i .. ", " .. s)
if is_signature(s) then
table.insert(signatures, s)
signature_table[s] = true
db('Found signature: ' .. s)
elseif is_keyring(s) then
table.insert(keyrings, s)
db('Found keyring: ' .. s)
else
-- Must be a source
db('Found source: ' .. s)
end
end
\
if defkeyspec then
defkey = check_sources_list({defkeyspec})[1]
if not defkey then
rpmerror('The provided keyring ' .. defkeyspec .. ' is not a valid source number or filename.')
end
end
\
if defkey then
db('Defkey: ' .. defkey)
else
db('No common key yet')
if keyrings[1] then
defkey = keyrings[1]
db('Using first found keyring file: '..defkey)
end
end
\
-- Check over any given args to make sure they're valid, and to see if a
-- common key is required.
local needdefkey = false
local double = rex.newPOSIX('^([^,]+),([^,]+)$')
local triple = rex.newPOSIX('^([^,]+),([^,]+),([^,]+)$')
local arglist = {}
\
-- RPM gives us the arguments in a single string.
-- Split on spaces and iterate
for arg in args:gmatch('%S+') do
db('Checking ' .. arg)
if triple:gmatch(arg, storematch) > 0 then
db('Looks OK')
local parsed = {srcnum=captures[1], signum=captures[2], keynum=captures[3]}
s = check_sources_list({captures[1], captures[2], captures[3]})
parsed.srcfile = s[1]
parsed.sigfile = s[2]
parsed.keyfile = s[3]
table.insert(arglist, parsed)
elseif double:gmatch(arg, storematch) > 0 then
db('Looks OK; needs common key')
needdefkey = true
local parsed = {srcnum=captures[1], signum=captures[2], keynum=defkeyspec, keyfile=defkey}
s = check_sources_list({captures[1], captures[2]})
parsed.srcfile = s[1]
parsed.sigfile = s[2]
table.insert(arglist, parsed)
else
rpmerror('Provided argument '..arg..' is not valid.')
end
end
\
-- So we now know if one of those args needs a common key
if needdefkey and not defkey then
rpmerror('No common key was specified or found, yet the arguments require one.')
end
\
-- And if we have no arguments at all and no common key was found,
-- then we can't do an automatic check
if not defkey and args == '' then
rpmerror('No keyring specified and none found; cannot auto-check.')
end
\
-- Nothing to check means automatic mode
if #arglist == 0 then
local noext
for i,_ in pairs(signature_table) do
-- Find the name without the extension
noext = string.gsub(i, '%.[^.]+$', '')
if sourcefiles[noext] then
table.insert(arglist, {srcfile=noext, sigfile=i, keyfile=defkey})
else
rpmerror('Found signature ' .. i .. ' with no matching source file.')
end
end
end
\
-- Now actually check things
for _,arg in ipairs(arglist) do
local gpgfile = '$GPGHOME/' .. basename(arg.keyfile) .. '.gpg'
echo('Checking signature: file ' .. arg.srcfile .. ' sig ' .. arg.sigfile .. ' key ' .. arg.keyfile)
\
-- We need a secure temp directorry
print('GPGHOME=$(mktemp -qd)\\n')
\
-- Call gpg2 to generate the dearmored key
print('gpg2 --homedir $GPGHOME --no-default-keyring --quiet --yes ')
print('--output '.. gpgfile .. ' --dearmor ' .. arg.keyfile .. "\\n")
\
-- Call gpgv2 to verify the signature against the source file with the dearmored key
print('gpgv2 --homedir $GPGHOME --keyring ' .. gpgfile .. ' ' .. arg.sigfile .. ' ' .. arg.srcfile .. '\\n')
\
print('rm -rf $GPGHOME\\n')
echo('')
end
\
db('------------')
}#
# vim: set filetype=spec:

@ -33,7 +33,8 @@ Source0: ftp://ftp.infradead.org/pub/openconnect/openconnect-%{version}%{?gitsuf
%if 0%{?gitcount} == 0 %if 0%{?gitcount} == 0
Source1: ftp://ftp.infradead.org/pub/openconnect/openconnect-%{version}%{?gitsuffix}.tar.gz.asc Source1: ftp://ftp.infradead.org/pub/openconnect/openconnect-%{version}%{?gitsuffix}.tar.gz.asc
%endif %endif
Source2: gpgkey-BE07D9FD54809AB2C4B0FF5F63762CDA67E2F359.gpg Source2: gpgkey-BE07D9FD54809AB2C4B0FF5F63762CDA67E2F359.asc
Source3: macros.gpg
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
@ -78,9 +79,10 @@ This package provides the core HTTP and authentication support from
the OpenConnect VPN client, to be used by GUI authentication dialogs the OpenConnect VPN client, to be used by GUI authentication dialogs
for NetworkManager etc. for NetworkManager etc.
%include %SOURCE3
%prep %prep
%if 0%{?gitcount} == 0 %if 0%{?gitcount} == 0
gpgv2 --keyring %{SOURCE2} %{SOURCE1} %{SOURCE0} %gpg_verify
%endif %endif
%setup -q -n openconnect-%{version}%{?gitsuffix} %setup -q -n openconnect-%{version}%{?gitsuffix}

Loading…
Cancel
Save