123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- # 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:
|