]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Implement signatures verification using rspamadm keypair
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 5 Jun 2018 16:03:10 +0000 (17:03 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 5 Jun 2018 16:03:10 +0000 (17:03 +0100)
lualib/rspamadm/keypair.lua

index 69b50b921333ef1bf3072011ca11726140c509c6..518c9e65f65f73f0014adc3719f1dcfd3ee1cfea 100644 (file)
@@ -16,8 +16,11 @@ limitations under the License.
 
 local argparse = require "argparse"
 local rspamd_keypair = require "rspamd_cryptobox_keypair"
+local rspamd_pubkey = require "rspamd_cryptobox_pubkey"
+local rspamd_signature = require "rspamd_cryptobox_signature"
 local rspamd_crypto = require "rspamd_cryptobox"
 local ucl = require "ucl"
+local logger = require "rspamd_logger"
 
 -- Define command line options
 local parser = argparse()
@@ -58,11 +61,35 @@ sign:argument "file"
     :argname "<file>"
     :args "*"
 
+local verify = parser:command "verify ver v"
+                     :description "Verifies a file using keypair or a public key"
+verify:mutex(
+    verify:option "-p --pubkey"
+          :description "Load pubkey from the specified file"
+          :argname "<file>",
+    verify:option "-P --pubstring"
+          :description "Load pubkey from the base32 encoded string"
+          :argname "<base32>",
+    verify:option "-k --keypair"
+          :description "Get pubkey from the keypair file"
+          :argname "<file>"
+)
+verify:argument "file"
+    :description "File to verify"
+    :argname "<file>"
+    :args "*"
+verify:flag "-n --nist"
+      :description "Uses nistp curves (P256)"
+verify:option "-s --suffix"
+      :description "Suffix for signature"
+      :argname "<suffix>"
+      :default("sig")
+
 -- Default command is generate, so duplicate options
 parser:flag "-s --sign"
         :description "Generates a sign keypair instead of the encryption one"
 parser:flag "-n --nist"
-        :description "Uses nist encryption algorithm"
+        :description "Uses nistp curves (P256)"
 parser:mutex(
     parser:flag "-j --json"
             :description "Output JSON instead of UCL",
@@ -74,81 +101,161 @@ parser:option "-o --output"
       :description "Write keypair to file"
       :argname "<file>"
 
-local function handler(args)
-  local opts = parser:parse(args)
+local function fatal(...)
+  logger.errx(...)
+  os.exit(1)
+end
 
-  local command = opts.command or "generate"
+local function generate_handler(opts)
+  local mode = 'encryption'
+  if opts.sign then
+    mode = 'sign'
+  end
+  local alg = 'curve25519'
+  if opts.nist then
+    alg = 'nist'
+  end
+  -- TODO: probably, do it in a more safe way
+  local kp = rspamd_keypair.create(mode, alg):totable()
 
-  if command == 'generate' then
-    local mode = 'encryption'
-    if opts.sign then
-      mode = 'sign'
+  local format = 'ucl'
+
+  if opts.json then
+    format = 'json'
+  end
+
+  if opts.output then
+    local out = io.open(opts.output, 'w')
+    if not out then
+      fatal('cannot open output to write: ' .. opts.output)
     end
-    local alg = 'curve25519'
-    if opts.nist then
-      alg = 'nist'
+    out:write(ucl.to_format(kp, format))
+    out:close()
+  else
+    io.write(ucl.to_format(kp, format))
+  end
+end
+
+local function sign_handler(opts)
+  if opts.file then
+    if type(opts.file) == 'string' then
+      opts.file = {opts.file}
     end
-    -- TODO: probably, do it in a more safe way
-    local kp = rspamd_keypair.create(mode, alg):totable()
+  else
+    parser:error('no files to sign')
+  end
+  if not opts.keypair then
+    parser:error("no keypair specified")
+  end
 
-    local format = 'ucl'
+  local ucl_parser = ucl.parser()
+  local res,err = ucl_parser:parse_file(opts.keypair)
 
-    if opts.json then
-      format = 'json'
-    end
+  if not res then
+    fatal(string.format('cannot load %s: %s', opts.keypair, err))
+  end
 
-    if opts.output then
-      local out = io.open(opts.output, 'w')
-      if not out then
-        parser:error('cannot open output to write: ' .. opts.output)
-      end
-      out:write(ucl.to_format(kp, format))
-      out:close()
-    else
-      io.write(ucl.to_format(kp, format))
+  local kp = rspamd_keypair.load(ucl_parser:get_object())
+
+  if not kp then
+    fatal("cannot load keypair: " .. opts.keypair)
+  end
+
+  for _,fname in ipairs(opts.file) do
+    local sig = rspamd_crypto.sign_file(kp, fname)
+
+    if not sig then
+      fatal(string.format("cannot sign %s\n", fname))
     end
 
-  elseif command == 'sign' then
-    if opts.file then
-      if type(opts.file) == 'string' then
-        opts.file = {opts.file}
-      end
-    else
-      parser:error('no files to sign')
+    local out = string.format('%s.%s', fname, opts.suffix or 'sig')
+    local of = io.open(out, 'w')
+    if not of then
+      fatal('cannot open output to write: ' .. out)
     end
-    if not opts.keypair then
-      parser:error("no keypair specified")
+    of:write(sig:bin())
+    of:close()
+    io.write(string.format('signed %s -> %s (%s)\n', fname, out, sig:hex()))
+  end
+end
+
+local function verify_handler(opts)
+  if opts.file then
+    if type(opts.file) == 'string' then
+      opts.file = {opts.file}
     end
+  else
+    parser:error('no files to verify')
+  end
 
+  local pk
+  local alg = 'curve25519'
+
+  if opts.keypair then
     local ucl_parser = ucl.parser()
     local res,err = ucl_parser:parse_file(opts.keypair)
 
     if not res then
-      parser:error(string.format('cannot load %s: %s', opts.keypair, err))
+      fatal(string.format('cannot load %s: %s', opts.keypair, err))
     end
 
     local kp = rspamd_keypair.load(ucl_parser:get_object())
 
     if not kp then
-      parser:error("cannot load keypair: " .. opts.keypair)
+      fatal("cannot load keypair: " .. opts.keypair)
     end
 
-    for _,fname in ipairs(opts.file) do
-      local sig = rspamd_crypto.sign_file(kp, fname)
-
-      if not sig then
-        parser:error(string.format("cannot sign %s\n", fname))
-      end
-
-      local out = string.format('%s.%s', fname, opts.suffix or 'sig')
-      local of = io.open(out, 'w')
-      if not of then
-        parser:error('cannot open output to write: ' .. out)
-      end
-      of:write(sig:bin())
-      of:close()
-      io.write(string.format('signed %s -> %s (%s)\n', fname, out, sig:hex()))
+    pk = kp:pk()
+    alg = kp:alg()
+  elseif opts.pubkey then
+    if opts.nist then alg = 'nist' end
+    pk = rspamd_pubkey.load(opts.pubkey, 'sign', alg)
+  elseif opts.pubstr then
+    if opts.nist then alg = 'nist' end
+    pk = rspamd_pubkey.create(opts.pubstr, 'sign', alg)
+  end
+
+  if not pk then
+    fatal("cannot create pubkey")
+  end
+
+  local valid = true
+
+  for _,fname in ipairs(opts.file) do
+
+    local sig_fname = string.format('%s.%s', fname, opts.suffix or 'sig')
+    local sig = rspamd_signature.load(sig_fname, alg)
+
+    if not sig then
+      fatal(string.format("cannot load signature for %s -> %s",
+          fname, sig_fname))
     end
+
+    if rspamd_crypto.verify_file(pk, sig, fname, alg) then
+      io.write(string.format('verified %s -> %s (%s)\n', fname, sig_fname, sig:hex()))
+    else
+      valid = false
+      io.write(string.format('FAILED to verify %s -> %s (%s)\n', fname,
+          sig_fname, sig:hex()))
+    end
+  end
+
+  if not valid then
+    os.exit(1)
+  end
+end
+
+local function handler(args)
+  local opts = parser:parse(args)
+
+  local command = opts.command or "generate"
+
+  if command == 'generate' then
+    generate_handler(opts)
+  elseif command == 'sign' then
+    sign_handler(opts)
+  elseif command == 'verify' then
+    verify_handler(opts)
   else
     parser:error('command %s is not yet implemented', command)
   end