lib/rspec/pgp_matchers/be_a_valid_pgp_signature_of.rb
# (c) Copyright 2018 Ribose Inc.
#
# A following PGP matcher calls the GPG executable in a subshell, then
# parses command output. This is a poor pattern in general, because output
# messages may subtly change over GPG evolution.
#
# However, GPG executables do not provide any machine-readable output which
# could be used instead. Furthermore, using RNP or GPGME from here is also
# a poor idea, because this gem is going to be tested against various
# combinations of software, in some of which that dependency may be unavailable.
#
# If output parsing will ever become a source of problems, then the preferred
# solution is to develop a minimalist validator which, if executed in
# a subshell, returns useful machine-readable output. A validator tool running
# in a separate process may leverage GPGME, as it won't be exposed outside
# the validator. A previous implementation of this matcher may provide some
# useful ideas. See commit 2e2bd0da090d7d31ecacc2d1ea6bd3e13479e675.
#
# rubocop:disable Metrics/BlockLength
RSpec::Matchers.define :be_a_valid_pgp_signature_of do |text|
# rubocop:enable Metrics/BlockLength
include RSpec::PGPMatchers::GPGMatcherHelper
attr_reader :err
match do |signature_string|
@err = verify_signature(text, signature_string)
@err.nil?
end
chain :signed_by, :expected_signer
failure_message do
err
end
# Returns +nil+ if first signature is valid, or an error message otherwise.
def verify_signature(cleartext, signature_string)
cmd_output = run_verify(cleartext, signature_string)
cmd_result = analyse_verify_output(*cmd_output)
if cmd_result[:well_formed_pgp_data]
match_constraints(**cmd_result)
else
msg_no_pgg_data(signature_string)
end
end
def analyse_verify_output(_stdout_str, stderr_str, status)
{
well_formed_pgp_data: (status.exitstatus != 2),
signature: detect_signers(stderr_str).first,
}
end
def match_constraints(signature:, **_ignored)
match_signature(signature)
end
def msg_mismatch(text)
"expected given signature to be a valid Open PGP signature " +
"of following text:\n#{text}"
end
def msg_no_pgg_data(file_text)
"expected given text to be a valid Open PGP signature, " +
"but it contains no PGP data, just:\n#{file_text}"
end
def msg_wrong_signer(actual_signer)
"expected singature to be signed by #{expected_signer}, " +
"but was actually signed by #{actual_signer}"
end
end