schleuder 4.0.2 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|