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: