You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
11 KiB
317 lines
11 KiB
9 years ago
|
# 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:
|