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