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