schleuder 4.0.2 → 5.0.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 +4 -4
- data/README.md +6 -9
- data/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb +1 -1
- data/db/migrate/20211106112020_change_boolean_values_to_integers.rb +46 -0
- data/db/migrate/20211107151309_add_limits_to_string_columns.rb +28 -0
- data/db/migrate/20220910170110_add_key_auto_import_from_email.rb +11 -0
- data/db/schema.rb +16 -16
- data/etc/list-defaults.yml +16 -0
- data/etc/schleuder.yml +29 -11
- data/lib/schleuder/cli.rb +15 -2
- data/lib/schleuder/conf.rb +23 -3
- data/lib/schleuder/email_key_importer.rb +91 -0
- data/lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb +21 -0
- data/lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb +1 -1
- data/lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb +36 -4
- data/lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb +9 -0
- data/lib/schleuder/filters_runner.rb +1 -30
- data/lib/schleuder/gpgme/ctx.rb +34 -93
- data/lib/schleuder/gpgme/key.rb +1 -1
- data/lib/schleuder/gpgme/key_extractor.rb +30 -0
- data/lib/schleuder/http.rb +56 -0
- data/lib/schleuder/key_fetcher.rb +89 -0
- data/lib/schleuder/keyword_handlers/key_management.rb +2 -2
- data/lib/schleuder/keyword_handlers/subscription_management.rb +19 -3
- data/lib/schleuder/list.rb +26 -10
- data/lib/schleuder/list_builder.rb +1 -1
- data/lib/schleuder/logger.rb +1 -1
- data/lib/schleuder/mail/gpg/decrypted_part.rb +20 -0
- data/lib/schleuder/mail/gpg/delivery_handler.rb +38 -0
- data/lib/schleuder/mail/gpg/encrypted_part.rb +29 -5
- data/lib/schleuder/mail/gpg/gpgme_ext.rb +8 -0
- data/lib/schleuder/mail/gpg/gpgme_helper.rb +155 -0
- data/lib/schleuder/mail/gpg/inline_decrypted_message.rb +82 -0
- data/lib/schleuder/mail/gpg/inline_signed_message.rb +73 -0
- data/lib/schleuder/mail/gpg/mime_signed_message.rb +28 -0
- data/lib/schleuder/mail/gpg/missing_keys_error.rb +6 -0
- data/lib/schleuder/mail/gpg/sign_part.rb +19 -9
- data/lib/schleuder/mail/gpg/signed_part.rb +37 -0
- data/lib/schleuder/mail/gpg/verified_part.rb +10 -0
- data/lib/schleuder/mail/gpg/verify_result_attribute.rb +32 -0
- data/lib/schleuder/mail/gpg/version_part.rb +22 -0
- data/lib/schleuder/mail/gpg.rb +236 -7
- data/lib/schleuder/mail/message.rb +98 -14
- data/lib/schleuder/runner.rb +40 -10
- data/lib/schleuder/sks_client.rb +18 -0
- data/lib/schleuder/version.rb +1 -1
- data/lib/schleuder/vks_client.rb +24 -0
- data/lib/schleuder-api-daemon/routes/key.rb +22 -1
- data/lib/schleuder.rb +11 -7
- data/locales/de.yml +38 -19
- data/locales/en.yml +22 -3
- metadata +58 -21
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'schleuder/mail/gpg/gpgme_ext'
|
2
|
+
|
3
|
+
# GPGME methods for encryption/decryption/signing
|
4
|
+
module Mail
|
5
|
+
module Gpg
|
6
|
+
class GpgmeHelper
|
7
|
+
|
8
|
+
def self.encrypt(plain, options = {})
|
9
|
+
options = options.merge({armor: true})
|
10
|
+
|
11
|
+
plain_data = GPGME::Data.new(plain)
|
12
|
+
cipher_data = GPGME::Data.new(options[:output])
|
13
|
+
|
14
|
+
recipient_keys = keys_for_data options[:recipients], options.delete(:keys)
|
15
|
+
|
16
|
+
if recipient_keys.empty?
|
17
|
+
raise MissingKeysError.new('No keys to encrypt to!')
|
18
|
+
end
|
19
|
+
|
20
|
+
flags = 0
|
21
|
+
flags |= GPGME::ENCRYPT_ALWAYS_TRUST if options[:always_trust]
|
22
|
+
|
23
|
+
GPGME::Ctx.new(options) do |ctx|
|
24
|
+
begin
|
25
|
+
if options[:sign]
|
26
|
+
if options[:signers] && options[:signers].size > 0
|
27
|
+
signers = GPGME::Key.find(:secret, options[:signers], :sign)
|
28
|
+
ctx.add_signer(*signers)
|
29
|
+
end
|
30
|
+
ctx.encrypt_sign(recipient_keys, plain_data, cipher_data, flags)
|
31
|
+
else
|
32
|
+
ctx.encrypt(recipient_keys, plain_data, cipher_data, flags)
|
33
|
+
end
|
34
|
+
rescue GPGME::Error::UnusablePublicKey => exc
|
35
|
+
exc.keys = ctx.encrypt_result.invalid_recipients
|
36
|
+
raise exc
|
37
|
+
rescue GPGME::Error::UnusableSecretKey => exc
|
38
|
+
exc.keys = ctx.sign_result.invalid_signers
|
39
|
+
raise exc
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
cipher_data.seek(0)
|
44
|
+
cipher_data
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.decrypt(cipher, options = {})
|
48
|
+
cipher_data = GPGME::Data.new(cipher)
|
49
|
+
plain_data = GPGME::Data.new(options[:output])
|
50
|
+
|
51
|
+
GPGME::Ctx.new(options) do |ctx|
|
52
|
+
begin
|
53
|
+
if options[:verify]
|
54
|
+
ctx.decrypt_verify(cipher_data, plain_data)
|
55
|
+
plain_data.verify_result = ctx.verify_result
|
56
|
+
else
|
57
|
+
ctx.decrypt(cipher_data, plain_data)
|
58
|
+
end
|
59
|
+
rescue GPGME::Error::UnsupportedAlgorithm => exc
|
60
|
+
exc.algorithm = ctx.decrypt_result.unsupported_algorithm
|
61
|
+
raise exc
|
62
|
+
rescue GPGME::Error::WrongKeyUsage => exc
|
63
|
+
exc.key_usage = ctx.decrypt_result.wrong_key_usage
|
64
|
+
raise exc
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
plain_data.seek(0)
|
69
|
+
plain_data
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.sign(plain, options = {})
|
73
|
+
options.merge!({
|
74
|
+
armor: true,
|
75
|
+
signer: options.delete(:sign_as),
|
76
|
+
mode: GPGME::SIG_MODE_DETACH
|
77
|
+
})
|
78
|
+
crypto = GPGME::Crypto.new
|
79
|
+
crypto.sign GPGME::Data.new(plain), options
|
80
|
+
end
|
81
|
+
|
82
|
+
# returns [success(bool), VerifyResult(from gpgme)]
|
83
|
+
# success will be true when there is at least one sig and no invalid sig
|
84
|
+
def self.sign_verify(plain, signature, options = {})
|
85
|
+
signed_data = GPGME::Data.new(plain)
|
86
|
+
signature = GPGME::Data.new(signature)
|
87
|
+
|
88
|
+
success = verify_result = nil
|
89
|
+
GPGME::Ctx.new(options) do |ctx|
|
90
|
+
ctx.verify signature, signed_data, nil
|
91
|
+
verify_result = ctx.verify_result
|
92
|
+
signatures = verify_result.signatures
|
93
|
+
success = signatures &&
|
94
|
+
signatures.size > 0 &&
|
95
|
+
signatures.detect{|s| !s.valid? }.nil?
|
96
|
+
end
|
97
|
+
return [success, verify_result]
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.inline_verify(signed_text, options = {})
|
101
|
+
signed_data = GPGME::Data.new(signed_text)
|
102
|
+
success = verify_result = nil
|
103
|
+
GPGME::Ctx.new(options) do |ctx|
|
104
|
+
ctx.verify signed_data, nil
|
105
|
+
verify_result = ctx.verify_result
|
106
|
+
signatures = verify_result.signatures
|
107
|
+
success = signatures &&
|
108
|
+
signatures.size > 0 &&
|
109
|
+
signatures.detect{|s| !s.valid? }.nil?
|
110
|
+
end
|
111
|
+
return [success, verify_result]
|
112
|
+
end
|
113
|
+
|
114
|
+
# normalizes the list of recipients' emails, key ids and key data to a
|
115
|
+
# list of Key objects
|
116
|
+
#
|
117
|
+
# if key_data is given, _only_ key material from there is used,
|
118
|
+
# and eventually already imported keys in the keychain are ignored.
|
119
|
+
def self.keys_for_data(emails_or_shas_or_keys, key_data = nil)
|
120
|
+
if key_data
|
121
|
+
# in this case, emails_or_shas_or_keys is supposed to be the list of
|
122
|
+
# recipients, and key_data the key material to be used.
|
123
|
+
# We now map these to whatever we find in key_data for each of these
|
124
|
+
# addresses.
|
125
|
+
[emails_or_shas_or_keys].flatten.map do |r|
|
126
|
+
k = key_data[r]
|
127
|
+
key_id = case k
|
128
|
+
when GPGME::Key
|
129
|
+
# assuming this is already imported
|
130
|
+
k.fingerprint
|
131
|
+
when nil, ''
|
132
|
+
# nothing
|
133
|
+
nil
|
134
|
+
when /-----BEGIN PGP/
|
135
|
+
# ASCII key data
|
136
|
+
GPGME::Key.import(k).imports.map(&:fpr)
|
137
|
+
else
|
138
|
+
# key id or fingerprint
|
139
|
+
k
|
140
|
+
end
|
141
|
+
unless key_id.nil? || key_id.empty?
|
142
|
+
GPGME::Key.find(:public, key_id, :encrypt)
|
143
|
+
end
|
144
|
+
end.flatten.compact
|
145
|
+
elsif emails_or_shas_or_keys && (emails_or_shas_or_keys.size > 0)
|
146
|
+
# key lookup in keychain for all receivers
|
147
|
+
GPGME::Key.find :public, emails_or_shas_or_keys, :encrypt
|
148
|
+
else
|
149
|
+
# empty array given
|
150
|
+
[]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'schleuder/mail/gpg/verified_part'
|
2
|
+
|
3
|
+
# decryption of the so called 'PGP-Inline' message types
|
4
|
+
# this is not a standard, so the implementation is based on the notes
|
5
|
+
# here http://binblog.info/2008/03/12/know-your-pgp-implementation/
|
6
|
+
# and on test messages generated with the Mozilla Enigmail OpenPGP
|
7
|
+
# plugin https://www.enigmail.net
|
8
|
+
module Mail
|
9
|
+
module Gpg
|
10
|
+
class InlineDecryptedMessage < Mail::Message
|
11
|
+
|
12
|
+
# options are:
|
13
|
+
#
|
14
|
+
# :verify: decrypt and verify
|
15
|
+
def self.setup(cipher_mail, options = {})
|
16
|
+
if cipher_mail.multipart?
|
17
|
+
self.new do
|
18
|
+
Mail::Gpg.copy_headers cipher_mail, self
|
19
|
+
|
20
|
+
# Drop the HTML-part of a multipart/alternative-message if it is
|
21
|
+
# inline-encrypted: that ciphertext is probably wrapped in HTML,
|
22
|
+
# which GnuPG chokes upon, so we would have to parse the HTML to
|
23
|
+
# handle the message-part properly.
|
24
|
+
# Also it's not clear how to handle the resulting plain-text: is
|
25
|
+
# it HTML or simple text? That depends on the sending MUA and
|
26
|
+
# the original input.
|
27
|
+
# In summary, that's too much complications.
|
28
|
+
if cipher_mail.mime_type == 'multipart/alternative' &&
|
29
|
+
cipher_mail.html_part.present? &&
|
30
|
+
cipher_mail.html_part.body.decoded.include?('-----BEGIN PGP MESSAGE-----')
|
31
|
+
cipher_mail.parts.delete_if do |part|
|
32
|
+
part[:content_type].content_type == 'text/html'
|
33
|
+
end
|
34
|
+
# Set the content-type of the newly generated message to
|
35
|
+
# something less confusing.
|
36
|
+
content_type 'multipart/mixed'
|
37
|
+
# Leave a marker for other code.
|
38
|
+
header['X-MailGpg-Deleted-Html-Part'] = 'true'
|
39
|
+
end
|
40
|
+
|
41
|
+
cipher_mail.parts.each do |part|
|
42
|
+
p = VerifiedPart.new do |p|
|
43
|
+
if part.has_content_type? && /application\/(?:octet-stream|pgp-encrypted)/ =~ part.mime_type
|
44
|
+
# encrypted attachment, we set the content_type to the generic 'application/octet-stream'
|
45
|
+
# and remove the .pgp/gpg/asc from name/filename in header fields
|
46
|
+
decrypted = GpgmeHelper.decrypt(part.decoded, options)
|
47
|
+
p.verify_result decrypted.verify_result if options[:verify]
|
48
|
+
p.content_type part.content_type.sub(/application\/(?:octet-stream|pgp-encrypted)/, 'application/octet-stream')
|
49
|
+
.sub(/name=(?:"')?(.*)\.(?:pgp|gpg|asc)(?:"')?/, 'name="\1"')
|
50
|
+
p.content_disposition part.content_disposition.sub(/filename=(?:"')?(.*)\.(?:pgp|gpg|asc)(?:"')?/, 'filename="\1"')
|
51
|
+
p.content_transfer_encoding Mail::Encodings::Base64
|
52
|
+
p.body Mail::Encodings::Base64.encode(decrypted.to_s)
|
53
|
+
else
|
54
|
+
body = part.body.decoded
|
55
|
+
if body.include?('-----BEGIN PGP MESSAGE-----')
|
56
|
+
decrypted = GpgmeHelper.decrypt(body, options)
|
57
|
+
p.verify_result decrypted.verify_result if options[:verify]
|
58
|
+
p.body decrypted.to_s
|
59
|
+
else
|
60
|
+
p.content_type part.content_type
|
61
|
+
p.content_transfer_encoding part.content_transfer_encoding
|
62
|
+
p.body part.body.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
add_part p
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
decrypted = cipher_mail.body.empty? ? '' : GpgmeHelper.decrypt(cipher_mail.body.decoded, options)
|
71
|
+
self.new do
|
72
|
+
cipher_mail.header.fields.each do |field|
|
73
|
+
header[field.name] = field.value
|
74
|
+
end
|
75
|
+
body decrypted.to_s
|
76
|
+
verify_result decrypted.verify_result if options[:verify] && decrypted != ''
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'schleuder/mail/gpg/verified_part'
|
2
|
+
|
3
|
+
module Mail
|
4
|
+
module Gpg
|
5
|
+
class InlineSignedMessage < Mail::Message
|
6
|
+
|
7
|
+
def self.setup(signed_mail, options = {})
|
8
|
+
if signed_mail.multipart?
|
9
|
+
self.new do
|
10
|
+
global_verify_result = []
|
11
|
+
signed_mail.header.fields.each do |field|
|
12
|
+
header[field.name] = field.value
|
13
|
+
end
|
14
|
+
signed_mail.parts.each do |part|
|
15
|
+
if Mail::Gpg.signed_inline?(part)
|
16
|
+
signed_text = part.body.to_s
|
17
|
+
success, vr = GpgmeHelper.inline_verify(signed_text, options)
|
18
|
+
p = VerifiedPart.new(part)
|
19
|
+
if success
|
20
|
+
p.body self.class.strip_inline_signature signed_text
|
21
|
+
end
|
22
|
+
p.verify_result vr
|
23
|
+
global_verify_result << vr
|
24
|
+
add_part p
|
25
|
+
else
|
26
|
+
add_part part
|
27
|
+
end
|
28
|
+
end
|
29
|
+
verify_result global_verify_result
|
30
|
+
end
|
31
|
+
else
|
32
|
+
self.new do
|
33
|
+
signed_mail.header.fields.each do |field|
|
34
|
+
header[field.name] = field.value
|
35
|
+
end
|
36
|
+
signed_text = signed_mail.body.to_s
|
37
|
+
success, vr = GpgmeHelper.inline_verify(signed_text, options)
|
38
|
+
if success
|
39
|
+
body self.class.strip_inline_signature signed_text
|
40
|
+
else
|
41
|
+
body signed_text
|
42
|
+
end
|
43
|
+
verify_result vr
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
END_SIGNED_TEXT = '-----END PGP SIGNED MESSAGE-----'
|
49
|
+
END_SIGNED_TEXT_RE = /^#{END_SIGNED_TEXT}\s*$/
|
50
|
+
INLINE_SIG_RE = Regexp.new('^-----BEGIN PGP SIGNATURE-----\s*$.*^-----END PGP SIGNATURE-----\s*$', Regexp::MULTILINE)
|
51
|
+
BEGIN_SIG_RE = /^(-----BEGIN PGP SIGNATURE-----)\s*$/
|
52
|
+
|
53
|
+
|
54
|
+
# utility method to remove inline signature and related pgp markers
|
55
|
+
def self.strip_inline_signature(signed_text)
|
56
|
+
if signed_text =~ INLINE_SIG_RE
|
57
|
+
signed_text = signed_text.dup
|
58
|
+
if signed_text !~ END_SIGNED_TEXT_RE
|
59
|
+
# insert the 'end of signed text' marker in case it is missing
|
60
|
+
signed_text = signed_text.gsub BEGIN_SIG_RE, "-----END PGP SIGNED MESSAGE-----\n\\1"
|
61
|
+
end
|
62
|
+
signed_text.gsub! INLINE_SIG_RE, ''
|
63
|
+
signed_text.strip!
|
64
|
+
end
|
65
|
+
# Strip possible inline-"headers" (e.g. "Hash: SHA256", or "Comment: something").
|
66
|
+
signed_text.gsub(/(.*^-----BEGIN PGP SIGNED MESSAGE-----\n)(.*?)^$(.+)/m, '\1\3')
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'schleuder/mail/gpg/verified_part'
|
2
|
+
|
3
|
+
module Mail
|
4
|
+
module Gpg
|
5
|
+
class MimeSignedMessage < Mail::Message
|
6
|
+
|
7
|
+
def self.setup(signed_mail, options = {})
|
8
|
+
content_part, signature = signed_mail.parts
|
9
|
+
success, vr = SignPart.verify_signature(content_part, signature, options)
|
10
|
+
self.new do
|
11
|
+
verify_result vr
|
12
|
+
signed_mail.header.fields.each do |field|
|
13
|
+
header[field.name] = field.value
|
14
|
+
end
|
15
|
+
content_part.header.fields.each do |field|
|
16
|
+
header[field.name] = field.value
|
17
|
+
end
|
18
|
+
if content_part.multipart?
|
19
|
+
content_part.parts.each{|part| add_part part}
|
20
|
+
else
|
21
|
+
body content_part.body.raw_source
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -1,15 +1,26 @@
|
|
1
1
|
module Mail
|
2
2
|
module Gpg
|
3
3
|
class SignPart < Mail::Part
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
|
5
|
+
def initialize(cleartext_mail, options = {})
|
6
|
+
signature = GpgmeHelper.sign(cleartext_mail.encoded, options)
|
7
|
+
super() do
|
8
|
+
body signature.to_s
|
9
|
+
content_type "application/pgp-signature; name=\"signature.asc\""
|
10
|
+
content_disposition 'attachment; filename="signature.asc"'
|
11
|
+
content_description 'OpenPGP digital signature'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# true if all signatures are valid
|
16
|
+
def self.signature_valid?(plain_part, signature_part, options = {})
|
17
|
+
verify_signature(plain_part, signature_part, options)[0]
|
18
|
+
end
|
19
|
+
|
20
|
+
# will return [success(boolean), verify_result(as returned by gpgme)]
|
10
21
|
def self.verify_signature(plain_part, signature_part, options = {})
|
11
22
|
if !(signature_part.has_content_type? &&
|
12
|
-
|
23
|
+
('application/pgp-signature' == signature_part.mime_type))
|
13
24
|
return false
|
14
25
|
end
|
15
26
|
|
@@ -19,7 +30,7 @@ module Mail
|
|
19
30
|
plaintext = [ plain_part.header.raw_source,
|
20
31
|
"\r\n\r\n",
|
21
32
|
plain_part.body.raw_source
|
22
|
-
|
33
|
+
].join
|
23
34
|
else
|
24
35
|
plaintext = plain_part.encoded
|
25
36
|
end
|
@@ -30,4 +41,3 @@ module Mail
|
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
33
|
-
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mail/part'
|
2
|
+
require 'schleuder/mail/gpg/sign_part'
|
3
|
+
|
4
|
+
module Mail
|
5
|
+
module Gpg
|
6
|
+
|
7
|
+
class SignedPart < Mail::Part
|
8
|
+
|
9
|
+
def self.build(cleartext_mail)
|
10
|
+
new do
|
11
|
+
if cleartext_mail.body.multipart?
|
12
|
+
if cleartext_mail.content_type =~ /^(multipart[^;]+)/
|
13
|
+
# preserve multipart/alternative etc
|
14
|
+
content_type $1
|
15
|
+
else
|
16
|
+
content_type 'multipart/mixed'
|
17
|
+
end
|
18
|
+
cleartext_mail.body.parts.each do |p|
|
19
|
+
add_part p
|
20
|
+
end
|
21
|
+
else
|
22
|
+
content_type cleartext_mail.content_type
|
23
|
+
body cleartext_mail.body.raw_source
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def sign(options)
|
29
|
+
SignPart.new(self, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mail
|
2
|
+
module Gpg
|
3
|
+
module VerifyResultAttribute
|
4
|
+
|
5
|
+
# the result of signature verification, as provided by GPGME
|
6
|
+
def verify_result(result = nil)
|
7
|
+
if result
|
8
|
+
self.verify_result = result
|
9
|
+
else
|
10
|
+
@verify_result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_result=(result)
|
15
|
+
@verify_result = result
|
16
|
+
end
|
17
|
+
|
18
|
+
# checks validity of signatures (true / false)
|
19
|
+
def signature_valid?
|
20
|
+
sigs = self.signatures
|
21
|
+
sigs.any? && sigs.all?{|s| s.valid?}
|
22
|
+
end
|
23
|
+
|
24
|
+
# list of all signatures from verify_result
|
25
|
+
def signatures
|
26
|
+
[verify_result].flatten.compact.map do |vr|
|
27
|
+
vr.signatures
|
28
|
+
end.flatten.compact
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mail/part'
|
2
|
+
|
3
|
+
module Mail
|
4
|
+
module Gpg
|
5
|
+
class VersionPart < Mail::Part
|
6
|
+
VERSION_1 = 'Version: 1'
|
7
|
+
CONTENT_TYPE = 'application/pgp-encrypted'
|
8
|
+
CONTENT_DESC = 'PGP/MIME Versions Identification'
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
body VERSION_1
|
13
|
+
content_type CONTENT_TYPE
|
14
|
+
content_description CONTENT_DESC
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.is_version_part?(part)
|
18
|
+
part.mime_type == CONTENT_TYPE && part.body =~ /#{VERSION_1}/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|