From 7382be9d83bc5f95ad8d184b95c379ca5b9d1725 Mon Sep 17 00:00:00 2001 From: Silvio Rhatto Date: Sat, 22 Jan 2011 18:24:55 -0200 Subject: Code split into backend, keyring and keyringer modules --- bin/keyringer | 30 +++----- lib/backend.rb | 3 + lib/backend/crypt.rb | 159 ++++++++++++++++++++++++++++++++++++++++++ lib/keyring.rb | 5 ++ lib/keyring/crypt.rb | 10 +++ lib/keyring/fs.rb | 13 ++++ lib/keyringer.rb | 9 +++ lib/keyringer/bash_wrapper.rb | 7 ++ lib/keyringer/crypt.rb | 158 ----------------------------------------- lib/keyringer/decrypt.rb | 10 +++ lib/keyringer/fs.rb | 13 ---- 11 files changed, 225 insertions(+), 192 deletions(-) create mode 100644 lib/backend.rb create mode 100644 lib/backend/crypt.rb create mode 100644 lib/keyring.rb create mode 100644 lib/keyring/crypt.rb create mode 100644 lib/keyring/fs.rb create mode 100644 lib/keyringer.rb create mode 100644 lib/keyringer/bash_wrapper.rb delete mode 100644 lib/keyringer/crypt.rb create mode 100644 lib/keyringer/decrypt.rb delete mode 100644 lib/keyringer/fs.rb diff --git a/bin/keyringer b/bin/keyringer index 974fa80..74dfcb1 100755 --- a/bin/keyringer +++ b/bin/keyringer @@ -9,31 +9,19 @@ def usage exit 1 end -# TODO: misc checks -def doDecrypt(someArguments) - filename = ARGV[2] - file = Keyringer::Fs.new() - content = file.get_as_string(filename) - - crypt = Keyringer::Crypt.new(nil) - output = crypt.decrypt(content) - puts output -end - -def doWrapper(someArguments) - exec("keyringer " + ARGV.join(' ')) -end - $:.unshift File.dirname(__FILE__) + '/../lib' require 'keyringer' begin - keyring = ARGV[0] - action = ARGV[1] - if action == 'decrypt' - doDecrypt(ARGV) - else - doWrapper(ARGV) + action = Keyringer.const_get(ARGV[1].capitalize) + + if action.is_a?(Class) + instance = action.new + output = instance.execute + puts output end +rescue NameError + wrapper = Keyringer::BashWrapper.new + wrapper.execute rescue SystemExit => e exit e.status rescue Exception => e diff --git a/lib/backend.rb b/lib/backend.rb new file mode 100644 index 0000000..18f220c --- /dev/null +++ b/lib/backend.rb @@ -0,0 +1,3 @@ +# internal requires +$:.unshift File.dirname(__FILE__) +require 'backend/crypt' diff --git a/lib/backend/crypt.rb b/lib/backend/crypt.rb new file mode 100644 index 0000000..2f00515 --- /dev/null +++ b/lib/backend/crypt.rb @@ -0,0 +1,159 @@ +# This code is based on the library from http://schleuder2.nadir.org/ +require 'gpgme' + +module Backend + # Wrapper for ruby-gpgme. Method naming is not strictly logical, this might + # change but aliases will be set up then. + class Crypt + # Instantiates and stores password + def initialize(password) + @password = password + @ctx = GPGME::Ctx.new + # feed the passphrase into the Context + @ctx.set_passphrase_cb(method(:passfunc)) + end + + # TODO: use a logging facility + def debug(message) + puts message + end + + # TODO: use a logging facility + def error(message) + puts message + end + + # Verify a gpg-signature. Use +signed_string+ if the signature is + # detached. Returns a GPGME::SignatureResult + def verify(sig, signed_string='') + in_signed = '' + if signed_string.empty? + # verify +sig+ as cleartext (aka pgp/inline) signature + debug 'No extra signed_string, verifying cleartext signature' + output = GPGME.verify(sig) do |sig| + in_signed = sig + end + else + # verify detached signature + debug 'Verifying detached signature' + # Don't know why we need a GPGME::Data object this time but without gpgme throws exceptions + plain = GPGME::Data.new + GPGME.verify(sig, signed_string, plain) do |sig| + in_signed = sig + end + output = signed_string + + end + debug 'verify_result: ' + in_signed.inspect + + [output, in_signed] + end + + # Decrypt a string. + def decrypt(str) + output = "" + in_encrypted = nil + in_signed = nil + + # TODO: return ciphertext if missing key. Sensible e.g. if it is part + # of a nested MIME-message and encrypted to someone else on purpose. + # Breaking if even the whole message is not decryptable is a job for + # the processor. + + # return input instead of empty String if not encrypted + unless str =~ /^-----BEGIN PGP MESSAGE-----/ + # match pgp-mime- and inline-pgp-signatures + if str =~ /^-----BEGIN PGP SIG/ + debug 'found signed, not encrypted message, verifying' + output, in_signed = verify(str) + else + debug 'found not signed, not encrypted message, returning input' + output = str + end + else + debug 'found pgp content, decrypting and verifying with gpgme' + in_encrypted = true + output = GPGME.decrypt(str, :passphrase_callback => method(:passfunc)) do |sig| + in_signed = sig + end + if output.empty? + Exception.new("Output from GPGME.decrypt was empty!") + end + # TODO: return mailadresses or keys instead of signature-objects? + end + [output, in_encrypted, in_signed] + end + + # Encrypt a string to a single receiver and sign it. +receiver+ must be a + # Keyringer::Member + def encrypt_str(str, receiver) + # encypt and sign and return encrypted data as string + key = receiver.key || receiver.email + GPGME.encrypt([key], str, {:passphrase_callback => method(:passfunc), :armor => true, :sign => true, :always_trust => true}) + end + + # Lists all public keys matching +pattern+. Returns an array of + # GPGME::GpgKey's + def list_keys(pattern='') + GPGME.list_keys(pattern) + end + + # Returns the GPGME::GpgKey matching +pattern+. Log an error if more than + # one matches, because duplicated user-ids is a sensitive issue. + def get_key(pattern) + pattern = "<#{pattern}>" if pattern =~ /.*@.*/ + k = list_keys(pattern) + if k.length > 1 + error "There's more than one key matching the pattern you gave me!\nPattern: #{pattern}\nkeys: #{k.inspect}" + false + else + k.first + end + end + + # Signs +string+ with the private key of the list (aka detached signature) + def sign(string) + GPGME::detach_sign(string, {:armor => true, :passphrase_callback => method(:passfunc)}) + end + + # Clearsigns +string+ with the private key of the list + def clearsign(string) + GPGME::clearsign(string, {:armor => true, :passphrase_callback => method(:passfunc)}) + end + + # Exports the public key matching +keyid+ as ascii key block. + def export(keyid) + GPGME.export(keyid, :armor=>:true) + end + + # Delete the public key matching +pattern+ from the public key ring of the + # list + def delete_key(key) + key = get_key(key) if key.kind_of?(String) + begin + @ctx.delete_key(key) + return true + rescue => e + return e + end + end + + # Import +keydata+ into public key ring of the list + def add_key(keydata) + GPGME.import(keydata) + end + + def add_key_from_file(keyfile) + add_key(File.read(keyfile)) + end + + private + + def passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd) + io = IO.for_fd(fd, 'w') + io.puts @password + io.flush + end + + end +end diff --git a/lib/keyring.rb b/lib/keyring.rb new file mode 100644 index 0000000..7bfdb86 --- /dev/null +++ b/lib/keyring.rb @@ -0,0 +1,5 @@ +# internal requires +$:.unshift File.dirname(__FILE__) +require 'backend' +require 'keyring/crypt' +require 'keyring/fs' diff --git a/lib/keyring/crypt.rb b/lib/keyring/crypt.rb new file mode 100644 index 0000000..5968730 --- /dev/null +++ b/lib/keyring/crypt.rb @@ -0,0 +1,10 @@ +module Keyring + class Crypt + def decrypt(filename) + file = Fs.new() + content = file.get_as_string(filename) + crypt = Backend::Crypt.new(nil) + return crypt.decrypt(content) + end + end +end diff --git a/lib/keyring/fs.rb b/lib/keyring/fs.rb new file mode 100644 index 0000000..9f20378 --- /dev/null +++ b/lib/keyring/fs.rb @@ -0,0 +1,13 @@ +module Keyring + class Fs + def get_as_string(filename) + data = '' + f = File.open(filename, "r") + f.each_line do |line| + data += line + end + return data + end + end +end + diff --git a/lib/keyringer.rb b/lib/keyringer.rb new file mode 100644 index 0000000..9d455d1 --- /dev/null +++ b/lib/keyringer.rb @@ -0,0 +1,9 @@ +# internal requires +$:.unshift File.dirname(__FILE__) +require 'keyring' +require 'keyringer/decrypt' +require 'keyringer/bash_wrapper' + +module Keyring + VERSION = '2.0-alpha' +end diff --git a/lib/keyringer/bash_wrapper.rb b/lib/keyringer/bash_wrapper.rb new file mode 100644 index 0000000..ba4b4d0 --- /dev/null +++ b/lib/keyringer/bash_wrapper.rb @@ -0,0 +1,7 @@ +module Keyringer + class BashWrapper + def execute + exec("keyringer " + ARGV.join(' ')) + end + end +end diff --git a/lib/keyringer/crypt.rb b/lib/keyringer/crypt.rb deleted file mode 100644 index 1ec4126..0000000 --- a/lib/keyringer/crypt.rb +++ /dev/null @@ -1,158 +0,0 @@ -require 'gpgme' - -module Keyringer - # Wrapper for ruby-gpgme. Method naming is not strictly logical, this might - # change but aliases will be set up then. - class Crypt - # Instantiates and stores password - def initialize(password) - @password = password - @ctx = GPGME::Ctx.new - # feed the passphrase into the Context - @ctx.set_passphrase_cb(method(:passfunc)) - end - - # TODO: use a logging facility - def debug(message) - puts message - end - - # TODO: use a logging facility - def error(message) - puts message - end - - # Verify a gpg-signature. Use +signed_string+ if the signature is - # detached. Returns a GPGME::SignatureResult - def verify(sig, signed_string='') - in_signed = '' - if signed_string.empty? - # verify +sig+ as cleartext (aka pgp/inline) signature - debug 'No extra signed_string, verifying cleartext signature' - output = GPGME.verify(sig) do |sig| - in_signed = sig - end - else - # verify detached signature - debug 'Verifying detached signature' - # Don't know why we need a GPGME::Data object this time but without gpgme throws exceptions - plain = GPGME::Data.new - GPGME.verify(sig, signed_string, plain) do |sig| - in_signed = sig - end - output = signed_string - - end - debug 'verify_result: ' + in_signed.inspect - - [output, in_signed] - end - - # Decrypt a string. - def decrypt(str) - output = "" - in_encrypted = nil - in_signed = nil - - # TODO: return ciphertext if missing key. Sensible e.g. if it is part - # of a nested MIME-message and encrypted to someone else on purpose. - # Breaking if even the whole message is not decryptable is a job for - # the processor. - - # return input instead of empty String if not encrypted - unless str =~ /^-----BEGIN PGP MESSAGE-----/ - # match pgp-mime- and inline-pgp-signatures - if str =~ /^-----BEGIN PGP SIG/ - debug 'found signed, not encrypted message, verifying' - output, in_signed = verify(str) - else - debug 'found not signed, not encrypted message, returning input' - output = str - end - else - debug 'found pgp content, decrypting and verifying with gpgme' - in_encrypted = true - output = GPGME.decrypt(str, :passphrase_callback => method(:passfunc)) do |sig| - in_signed = sig - end - if output.empty? - Exception.new("Output from GPGME.decrypt was empty!") - end - # TODO: return mailadresses or keys instead of signature-objects? - end - [output, in_encrypted, in_signed] - end - - # Encrypt a string to a single receiver and sign it. +receiver+ must be a - # Keyringer::Member - def encrypt_str(str, receiver) - # encypt and sign and return encrypted data as string - key = receiver.key || receiver.email - GPGME.encrypt([key], str, {:passphrase_callback => method(:passfunc), :armor => true, :sign => true, :always_trust => true}) - end - - # Lists all public keys matching +pattern+. Returns an array of - # GPGME::GpgKey's - def list_keys(pattern='') - GPGME.list_keys(pattern) - end - - # Returns the GPGME::GpgKey matching +pattern+. Log an error if more than - # one matches, because duplicated user-ids is a sensitive issue. - def get_key(pattern) - pattern = "<#{pattern}>" if pattern =~ /.*@.*/ - k = list_keys(pattern) - if k.length > 1 - error "There's more than one key matching the pattern you gave me!\nPattern: #{pattern}\nkeys: #{k.inspect}" - false - else - k.first - end - end - - # Signs +string+ with the private key of the list (aka detached signature) - def sign(string) - GPGME::detach_sign(string, {:armor => true, :passphrase_callback => method(:passfunc)}) - end - - # Clearsigns +string+ with the private key of the list - def clearsign(string) - GPGME::clearsign(string, {:armor => true, :passphrase_callback => method(:passfunc)}) - end - - # Exports the public key matching +keyid+ as ascii key block. - def export(keyid) - GPGME.export(keyid, :armor=>:true) - end - - # Delete the public key matching +pattern+ from the public key ring of the - # list - def delete_key(key) - key = get_key(key) if key.kind_of?(String) - begin - @ctx.delete_key(key) - return true - rescue => e - return e - end - end - - # Import +keydata+ into public key ring of the list - def add_key(keydata) - GPGME.import(keydata) - end - - def add_key_from_file(keyfile) - add_key(File.read(keyfile)) - end - - private - - def passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd) - io = IO.for_fd(fd, 'w') - io.puts @password - io.flush - end - - end -end diff --git a/lib/keyringer/decrypt.rb b/lib/keyringer/decrypt.rb new file mode 100644 index 0000000..9f872aa --- /dev/null +++ b/lib/keyringer/decrypt.rb @@ -0,0 +1,10 @@ +module Keyringer + class Decrypt + def execute + filename = ARGV[2] + crypt = Keyring::Crypt.new + output = crypt.decrypt(filename) + return output + end + end +end diff --git a/lib/keyringer/fs.rb b/lib/keyringer/fs.rb deleted file mode 100644 index 7b7742a..0000000 --- a/lib/keyringer/fs.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Keyringer - class Fs - def get_as_string(filename) - data = '' - f = File.open(filename, "r") - f.each_line do |line| - data += line - end - return data - end - end -end - -- cgit v1.2.3