rspec-pgp_matchers 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +21 -0
- data/.gitignore +12 -0
- data/.hound.yml +3 -0
- data/.rspec +3 -0
- data/.rubocop.ribose.yml +65 -0
- data/.rubocop.tb.yml +650 -0
- data/.rubocop.yml +40 -0
- data/.travis.yml +77 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +114 -0
- data/Rakefile +115 -0
- data/bin/bundle +105 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/ci/gemfiles/common.gemfile +6 -0
- data/ci/gemfiles/rspec-3.4.gemfile +5 -0
- data/ci/gemfiles/rspec-3.5.gemfile +5 -0
- data/ci/gemfiles/rspec-3.6.gemfile +5 -0
- data/ci/gemfiles/rspec-3.7.gemfile +5 -0
- data/ci/install_gpg_all.sh +137 -0
- data/ci/install_gpg_component.sh +301 -0
- data/lib/rspec/pgp_matchers.rb +16 -0
- data/lib/rspec/pgp_matchers/be_a_pgp_encrypted_message.rb +113 -0
- data/lib/rspec/pgp_matchers/be_a_valid_pgp_signature_of.rb +95 -0
- data/lib/rspec/pgp_matchers/gpg_matcher_helper.rb +63 -0
- data/lib/rspec/pgp_matchers/version.rb +8 -0
- data/rspec-pgp_matchers.gemspec +37 -0
- metadata +164 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
require "rspec/pgp_matchers/version"
|
5
|
+
require "rspec/pgp_matchers/gpg_matcher_helper"
|
6
|
+
require "rspec/pgp_matchers/be_a_pgp_encrypted_message"
|
7
|
+
require "rspec/pgp_matchers/be_a_valid_pgp_signature_of"
|
8
|
+
|
9
|
+
module RSpec
|
10
|
+
module PGPMatchers
|
11
|
+
class << self
|
12
|
+
attr_accessor :homedir
|
13
|
+
end
|
14
|
+
# Your code goes here...
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# A following PGP matcher calls the GPG executable in a subshell, then
|
5
|
+
# parses command output. This is a poor pattern in general, because output
|
6
|
+
# messages may subtly change over GPG evolution, and some maintenance work
|
7
|
+
# may be required.
|
8
|
+
#
|
9
|
+
# However, GPG executables do not provide any machine-readable output which
|
10
|
+
# could be used instead. Furthermore, using RNP or GPGME from here is also
|
11
|
+
# a poor idea, because this gem is going to be tested against various
|
12
|
+
# combinations of software, in some of which that dependency may be unavailable.
|
13
|
+
#
|
14
|
+
# If parsing the output of GPG commands will become a burden, then the preferred
|
15
|
+
# solution is to develop a minimalist validator which, if executed in
|
16
|
+
# a subshell, returns some useful machine-readable output. A validator tool
|
17
|
+
# running in a separate process may leverage GPGME, as it won't be exposed
|
18
|
+
# outside the validator. A previous implementation of this matcher may provide
|
19
|
+
# some useful ideas. See commit 2e2bd0da090d7d31ecacc2d1ea6bd3e13479e675.
|
20
|
+
#
|
21
|
+
# rubocop:disable Metrics/BlockLength
|
22
|
+
RSpec::Matchers.define :be_a_pgp_encrypted_message do
|
23
|
+
# rubocop:enable Metrics/BlockLength
|
24
|
+
include RSpec::PGPMatchers::GPGMatcherHelper
|
25
|
+
|
26
|
+
attr_reader :err, :expected_recipients
|
27
|
+
|
28
|
+
match do |encrypted_string|
|
29
|
+
@err = validate_encrypted_message(encrypted_string)
|
30
|
+
@err.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
chain :containing, :expected_cleartext
|
34
|
+
chain :signed_by, :expected_signer
|
35
|
+
|
36
|
+
chain :encrypted_for do |*recipients|
|
37
|
+
@expected_recipients = [*recipients]
|
38
|
+
end
|
39
|
+
|
40
|
+
failure_message do
|
41
|
+
err
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns +nil+ if signature is valid, or an error message otherwise.
|
45
|
+
def validate_encrypted_message(encrypted_string)
|
46
|
+
cmd_output = run_gpg_decrypt(encrypted_string)
|
47
|
+
cmd_result = analyse_decrypt_output(*cmd_output)
|
48
|
+
|
49
|
+
if cmd_result[:well_formed_pgp_data]
|
50
|
+
match_constraints(**cmd_result)
|
51
|
+
else
|
52
|
+
msg_no_pgg_data(encrypted_string)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_gpg_decrypt(encrypted_string)
|
57
|
+
enc_file = make_tempfile_containing(encrypted_string)
|
58
|
+
cmd = gpg_decrypt_command(enc_file)
|
59
|
+
run_command(cmd)
|
60
|
+
end
|
61
|
+
|
62
|
+
def analyse_decrypt_output(stdout_str, stderr_str, status)
|
63
|
+
{
|
64
|
+
well_formed_pgp_data: (status.exitstatus != 2),
|
65
|
+
recipients: detect_recipients(stderr_str),
|
66
|
+
signature: detect_signers(stderr_str).first,
|
67
|
+
cleartext: stdout_str,
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def match_constraints(cleartext:, recipients:, signature:, **_ignored)
|
72
|
+
[
|
73
|
+
(expected_cleartext && match_cleartext(cleartext)),
|
74
|
+
(expected_recipients && match_recipients(recipients)),
|
75
|
+
(expected_signer && match_signature(signature)),
|
76
|
+
].detect { |x| x }
|
77
|
+
end
|
78
|
+
|
79
|
+
def gpg_decrypt_command(enc_file)
|
80
|
+
homedir_path = Shellwords.escape(RSpec::PGPMatchers.homedir)
|
81
|
+
enc_path = Shellwords.escape(enc_file.path)
|
82
|
+
|
83
|
+
<<~SH
|
84
|
+
gpg \
|
85
|
+
--homedir #{homedir_path} \
|
86
|
+
--no-permission-warning \
|
87
|
+
--decrypt #{enc_path}
|
88
|
+
SH
|
89
|
+
end
|
90
|
+
|
91
|
+
def msg_mismatch(text)
|
92
|
+
"expected given Open PGP message to contain following " +
|
93
|
+
"text:\n#{expected_cleartext}\nbut was:\n#{text}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def msg_no_pgg_data(file_text)
|
97
|
+
"expected given text to be a valid Open PGP encrypted message, " +
|
98
|
+
"but it contains no PGP data, just:\n#{file_text}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def msg_wrong_recipients(recipients)
|
102
|
+
expected_recipients_list = expected_recipients.inspect
|
103
|
+
recipients_list = recipients.inspect
|
104
|
+
|
105
|
+
"expected given Open PGP message to be encrypted for following " +
|
106
|
+
"recipients: #{expected_recipients_list}, but was for: #{recipients_list}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def msg_wrong_signer(actual_signer)
|
110
|
+
"expected singature to be signed by #{expected_signer}, " +
|
111
|
+
"but was actually signed by #{actual_signer}"
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# A following PGP matcher calls the GPG executable in a subshell, then
|
5
|
+
# parses command output. This is a poor pattern in general, because output
|
6
|
+
# messages may subtly change over GPG evolution.
|
7
|
+
#
|
8
|
+
# However, GPG executables do not provide any machine-readable output which
|
9
|
+
# could be used instead. Furthermore, using RNP or GPGME from here is also
|
10
|
+
# a poor idea, because this gem is going to be tested against various
|
11
|
+
# combinations of software, in some of which that dependency may be unavailable.
|
12
|
+
#
|
13
|
+
# If output parsing will ever become a source of problems, then the preferred
|
14
|
+
# solution is to develop a minimalist validator which, if executed in
|
15
|
+
# a subshell, returns useful machine-readable output. A validator tool running
|
16
|
+
# in a separate process may leverage GPGME, as it won't be exposed outside
|
17
|
+
# the validator. A previous implementation of this matcher may provide some
|
18
|
+
# useful ideas. See commit 2e2bd0da090d7d31ecacc2d1ea6bd3e13479e675.
|
19
|
+
#
|
20
|
+
# rubocop:disable Metrics/BlockLength
|
21
|
+
RSpec::Matchers.define :be_a_valid_pgp_signature_of do |text|
|
22
|
+
# rubocop:enable Metrics/BlockLength
|
23
|
+
include RSpec::PGPMatchers::GPGMatcherHelper
|
24
|
+
|
25
|
+
attr_reader :err
|
26
|
+
|
27
|
+
match do |signature_string|
|
28
|
+
@err = verify_signature(text, signature_string)
|
29
|
+
@err.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
chain :signed_by, :expected_signer
|
33
|
+
|
34
|
+
failure_message do
|
35
|
+
err
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns +nil+ if first signature is valid, or an error message otherwise.
|
39
|
+
def verify_signature(cleartext, signature_string)
|
40
|
+
cmd_output = run_gpg_verify(cleartext, signature_string)
|
41
|
+
cmd_result = analyse_verify_output(*cmd_output)
|
42
|
+
|
43
|
+
if cmd_result[:well_formed_pgp_data]
|
44
|
+
match_constraints(**cmd_result)
|
45
|
+
else
|
46
|
+
msg_no_pgg_data(signature_string)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_gpg_verify(cleartext, signature_string)
|
51
|
+
sig_file = make_tempfile_containing(signature_string)
|
52
|
+
data_file = make_tempfile_containing(cleartext)
|
53
|
+
cmd = gpg_verify_command(sig_file, data_file)
|
54
|
+
run_command(cmd)
|
55
|
+
end
|
56
|
+
|
57
|
+
def analyse_verify_output(_stdout_str, stderr_str, status)
|
58
|
+
{
|
59
|
+
well_formed_pgp_data: (status.exitstatus != 2),
|
60
|
+
signature: detect_signers(stderr_str).first,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def match_constraints(signature:, **_ignored)
|
65
|
+
match_signature(signature)
|
66
|
+
end
|
67
|
+
|
68
|
+
def gpg_verify_command(sig_file, data_file)
|
69
|
+
homedir_path = Shellwords.escape(RSpec::PGPMatchers.homedir)
|
70
|
+
sig_path = Shellwords.escape(sig_file.path)
|
71
|
+
data_path = Shellwords.escape(data_file.path)
|
72
|
+
|
73
|
+
<<~SH
|
74
|
+
gpg \
|
75
|
+
--homedir #{homedir_path} \
|
76
|
+
--no-permission-warning \
|
77
|
+
--verify #{sig_path} #{data_path}
|
78
|
+
SH
|
79
|
+
end
|
80
|
+
|
81
|
+
def msg_mismatch(text)
|
82
|
+
"expected given signature to be a valid Open PGP signature " +
|
83
|
+
"of following text:\n#{text}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def msg_no_pgg_data(file_text)
|
87
|
+
"expected given text to be a valid Open PGP signature, " +
|
88
|
+
"but it contains no PGP data, just:\n#{file_text}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def msg_wrong_signer(actual_signer)
|
92
|
+
"expected singature to be signed by #{expected_signer}, " +
|
93
|
+
"but was actually signed by #{actual_signer}"
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
require "open3"
|
5
|
+
require "tempfile"
|
6
|
+
|
7
|
+
module RSpec
|
8
|
+
module PGPMatchers
|
9
|
+
module GPGMatcherHelper
|
10
|
+
def detect_signers(stderr_str)
|
11
|
+
rx = /(?<ok>Good|BAD) signature from .*\<(?<email>[^>]+)\>/
|
12
|
+
|
13
|
+
stderr_str.to_enum(:scan, rx).map do
|
14
|
+
{
|
15
|
+
email: $~["email"],
|
16
|
+
ok: ($~["ok"] == "Good"),
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def detect_recipients(stderr_str)
|
22
|
+
rx = /encrypted with .*\n.*\<(?<email>[^>]+)\>/
|
23
|
+
|
24
|
+
stderr_str.to_enum(:scan, rx).map do
|
25
|
+
$~["email"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def match_cleartext(cleartext)
|
30
|
+
if cleartext != expected_cleartext
|
31
|
+
msg_mismatch(cleartext)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def match_recipients(recipients)
|
36
|
+
if expected_recipients.sort != recipients.sort
|
37
|
+
msg_wrong_recipients(recipients)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if signature is valid. If `expected_signer` is not `nil`, then
|
42
|
+
# it additionally checks if the signature was issued by expected signer.
|
43
|
+
def match_signature(signature)
|
44
|
+
if !signature[:ok]
|
45
|
+
msg_mismatch(text)
|
46
|
+
elsif expected_signer && signature[:email] != expected_signer
|
47
|
+
msg_wrong_signer(signature[:email])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def make_tempfile_containing(file_content)
|
52
|
+
tempfile = Tempfile.new
|
53
|
+
tempfile.write(file_content)
|
54
|
+
tempfile.flush
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_command(cmd)
|
58
|
+
env = { "LC_ALL" => "C" } # Gettext English locale
|
59
|
+
Open3.capture3(env, cmd)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
lib = File.expand_path("lib", __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require "rspec/pgp_matchers/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = "rspec-pgp_matchers"
|
10
|
+
spec.version = RSpec::PGPMatchers::VERSION
|
11
|
+
spec.authors = ["Ribose Inc."]
|
12
|
+
spec.email = ["open.source@ribose.com"]
|
13
|
+
|
14
|
+
spec.summary = "RSpec matchers for testing OpenPGP messages"
|
15
|
+
spec.homepage = "https://github.com/riboseinc/rspec-pgp_matchers"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added
|
20
|
+
# into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
f.match(%r{^(test|spec|features)/})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_runtime_dependency "rspec-expectations", "~> 3.4"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
33
|
+
spec.add_development_dependency "gpgme"
|
34
|
+
spec.add_development_dependency "pry", ">= 0.10.3", "< 0.12"
|
35
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-pgp_matchers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ribose Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec-expectations
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gpgme
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.10.3
|
62
|
+
- - "<"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0.12'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 0.10.3
|
72
|
+
- - "<"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.12'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '12.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '12.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.0'
|
103
|
+
description:
|
104
|
+
email:
|
105
|
+
- open.source@ribose.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".editorconfig"
|
111
|
+
- ".gitignore"
|
112
|
+
- ".hound.yml"
|
113
|
+
- ".rspec"
|
114
|
+
- ".rubocop.ribose.yml"
|
115
|
+
- ".rubocop.tb.yml"
|
116
|
+
- ".rubocop.yml"
|
117
|
+
- ".travis.yml"
|
118
|
+
- Gemfile
|
119
|
+
- LICENSE.txt
|
120
|
+
- README.adoc
|
121
|
+
- Rakefile
|
122
|
+
- bin/bundle
|
123
|
+
- bin/console
|
124
|
+
- bin/rake
|
125
|
+
- bin/rspec
|
126
|
+
- bin/setup
|
127
|
+
- ci/gemfiles/common.gemfile
|
128
|
+
- ci/gemfiles/rspec-3.4.gemfile
|
129
|
+
- ci/gemfiles/rspec-3.5.gemfile
|
130
|
+
- ci/gemfiles/rspec-3.6.gemfile
|
131
|
+
- ci/gemfiles/rspec-3.7.gemfile
|
132
|
+
- ci/install_gpg_all.sh
|
133
|
+
- ci/install_gpg_component.sh
|
134
|
+
- lib/rspec/pgp_matchers.rb
|
135
|
+
- lib/rspec/pgp_matchers/be_a_pgp_encrypted_message.rb
|
136
|
+
- lib/rspec/pgp_matchers/be_a_valid_pgp_signature_of.rb
|
137
|
+
- lib/rspec/pgp_matchers/gpg_matcher_helper.rb
|
138
|
+
- lib/rspec/pgp_matchers/version.rb
|
139
|
+
- rspec-pgp_matchers.gemspec
|
140
|
+
homepage: https://github.com/riboseinc/rspec-pgp_matchers
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.7.6
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: RSpec matchers for testing OpenPGP messages
|
164
|
+
test_files: []
|