lib/diaspora_federation/entities/signable.rb
# frozen_string_literal: true

module DiasporaFederation
  module Entities
    # Signable is a module that encapsulates basic signature generation/verification flow for entities.
    module Signable
      include Logging

      # Digest instance used for signing
      DIGEST = OpenSSL::Digest.new("SHA256")

      # Sign the data with the key
      #
      # @param [OpenSSL::PKey::RSA] privkey An RSA key
      # @return [String] A Base64 encoded signature of #signature_data with key
      def sign_with_key(privkey)
        Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
      end

      # Check that signature is a correct signature
      #
      # @param [String] author The author of the signature
      # @param [String] signature_key The signature to be verified
      # @raise [SignatureVerificationFailed] if the signature is not valid
      # @raise [PublicKeyNotFound] if no public key is found
      def verify_signature(author, signature_key)
        pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
        raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?

        signature = public_send(signature_key)
        raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?

        valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
        raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid

        logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
      end

      # This method defines what data is used for a signature creation/verification
      #
      # @abstract
      # @return [String] a string to sign
      def signature_data
        raise NotImplementedError.new("you must override this method to define signature base string")
      end

      # Raised, if verify_signatures fails to verify signatures (no public key found)
      class PublicKeyNotFound < RuntimeError
      end

      # Raised, if verify_signatures fails to verify signatures (signatures are wrong)
      class SignatureVerificationFailed < RuntimeError
      end
    end
  end
end
