eagletmt/akabei

View on GitHub
lib/akabei/signer.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'akabei/error'
require 'gpgme'

module Akabei
  class Signer
    class KeyNotFound < Error
      attr_reader :key_name
      def initialize(key_name)
        @key_name = key_name
        super("No such GPG key: #{key_name}")
      end
    end

    class AmbiguousKey < Error
      attr_reader :key_name, :found_keys
      def initialize(key_name, found_keys)
        @key_name = key_name
        @found_keys = found_keys
        super("Ambiguous GPG key: #{key_name}: #{formatted_keys}")
      end

      def formatted_keys
        @found_keys.map do |key|
          subkey = key.primary_subkey
          "#{subkey.length}#{subkey.pubkey_algo_letter}/#{subkey.fingerprint[-8 .. -1]}"
        end
      end
    end

    class InvalidSignature < Error
      attr_reader :path, :from
      def initialize(path, from)
        @path = path
        @from = from
        super("Invalid signature from #{from}: #{path}")
      end
    end

    class AgentNotFound < Error
      def initialize
        super('gpg-agent is not running')
      end
    end

    def self.get(gpg_key, crypto = nil)
      gpg_key && new(gpg_key, crypto)
    end

    def initialize(gpg_key, crypto = nil)
      check_gpg_agent!
      @gpg_key = find_secret_key(gpg_key)
      @crypto = crypto || GPGME::Crypto.new
    end

    def check_gpg_agent!
      unless ENV['GPG_AGENT_INFO']
        raise AgentNotFound.new
      end
    end

    def detach_sign(path)
      File.open(path) do |inp|
        File.open("#{path}.sig", 'w') do |out|
          @crypto.detach_sign(inp, signer: @gpg_key, output: out)
        end
      end
    end

    def verify!(path)
      File.open("#{path}.sig") do |sig|
        File.open(path) do |f|
          @crypto.verify(sig, signed_text: f) do |signature|
            unless signature.valid?
              raise InvalidSignature.new(path, signature.from)
            end
          end
        end
      end
    end

    def find_secret_key(key_name)
      keys = GPGME::Key.find(:secret, key_name, :sign)
      if keys.empty?
        raise KeyNotFound.new(key_name)
      elsif keys.size > 1
        raise AmbiguousKey.new(key_name, keys)
      else
        keys.first
      end
    end
  end
end