pdkim 0.9

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d403271cbc6ebf4df82551821bb5f76915b0fa1e
4
+ data.tar.gz: 56f3878d604453ff22c7e4b750b78a751f94c468
5
+ SHA512:
6
+ metadata.gz: c3fe7d5aed35e7d20d2ad6def1caabea3303ebc2a15344fa9a6f303997f5a0b7b9a03a8db7bf966212b4215ee382751c3da0666cdb971db16b472a3fc68cf371
7
+ data.tar.gz: 9313bc15253e55ca032eb7bf41a379b272aca814eb6c343b3339aeab690ae909e638808053ae37f64ba58c3226edc8c656d89443debbb8c5be8ec85631302b33
@@ -0,0 +1,98 @@
1
+ --- !ruby/hash
2
+ :id: 1ZsMk0-0iglmo-E9
3
+ :time: '2015-10-31 03:22:04 +0000'
4
+ :local_port: '25'
5
+ :local_hostname: mail.tsarmail.org
6
+ :remote_port: 35184
7
+ :remote_hostname: mail-pa0-x232.google.com
8
+ :remote_ip: 2607:f8b0:400e:c03::232
9
+ :authenticated:
10
+ :encrypted: true
11
+ :ehlo:
12
+ :value: " mail-pa0-x232.google.com"
13
+ :domain: google.com
14
+ :ip: 173.194.115.32
15
+ :mailfrom:
16
+ :value: "<mjwelchphd@gmail.com>"
17
+ :accepted: true
18
+ :name:
19
+ :url: mjwelchphd@gmail.com
20
+ :local_part: mjwelchphd
21
+ :domain: gmail.com
22
+ :czarmailclient: false
23
+ :owner_id:
24
+ :mx:
25
+ - gmail-smtp-in.l.google.com
26
+ - alt1.gmail-smtp-in.l.google.com
27
+ - alt2.gmail-smtp-in.l.google.com
28
+ - alt3.gmail-smtp-in.l.google.com
29
+ - alt4.gmail-smtp-in.l.google.com
30
+ :ip: 173.194.199.27
31
+ :spamhaus: false
32
+ :barracuda: false
33
+ :live: true
34
+ :rcptto:
35
+ - :value: "<coco@tsarmail.org>"
36
+ :accepted: true
37
+ :name:
38
+ :url: coco@tsarmail.org
39
+ :local_part: coco
40
+ :domain: tsarmail.org
41
+ :czarmailclient: false
42
+ :owner_id:
43
+ :senderisfriend: false
44
+ :mail_type: X
45
+ :data:
46
+ :accepted: false
47
+ :value:
48
+ :text:
49
+ - 'Received: by pasz6 with SMTP id z6so90936190pas.2'
50
+ - " for <coco@tsarmail.org>; Fri, 30 Oct 2015 20:22:04 -0700 (PDT)"
51
+ - 'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;'
52
+ - " d=gmail.com; s=20120113;"
53
+ - " h=message-id:date:from:user-agent:mime-version:to:subject"
54
+ - " :content-type:content-transfer-encoding;"
55
+ - " bh=Z2jCW0avVIbUOQuL6QZlOhhVpMa2bHX2/SOpDUKV+Ec=;"
56
+ - " b=fdIuma9zlWqC1+tAAtXHXGybhB7Luki6veKE8SimmQL5BfWV5xU9NS7E0QApG2+rgp"
57
+ - " QQ6VRwy8M9ATjBn3BJvXN462HGsfihPL5DwIr8N1PWhy7wkVJxFvAI/mcbGZlYZE4p99"
58
+ - " CXVPAYt5lBKzeMFYNF79umSIjhc2HRyE0TFyLxzrH9DlryeQ6ngDJ6VKbPsWRpG0rLS2"
59
+ - " H8Xs2t6l5wOd22oDcE0k4x3aU6MDMw6/0enU6KuVgmlnjsIemFk0NvpTRVKcWlluDmjI"
60
+ - " OQ1x6oFNvwtxTpEC6OP89kXM6Fnc0mwn9PJSzzJ/gxmcxpNbmFZ25yHGy48TiA4jTC1y"
61
+ - " JLeA=="
62
+ - 'X-Received: by 10.66.147.136 with SMTP id tk8mr12368633pab.37.1446261724525;'
63
+ - " Fri, 30 Oct 2015 20:22:04 -0700 (PDT)"
64
+ - 'Return-Path: <mjwelchphd@gmail.com>'
65
+ - 'Received: from [192.168.1.2] ([96.251.113.83])'
66
+ - " by smtp.googlemail.com with ESMTPSA id or3sm10805021pbb.56.2015.10.30.20.22.02"
67
+ - " for <coco@tsarmail.org>"
68
+ - " (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);"
69
+ - " Fri, 30 Oct 2015 20:22:03 -0700 (PDT)"
70
+ - 'Message-ID: <563433D8.9020306@gmail.com>'
71
+ - 'Date: Fri, 30 Oct 2015 20:22:00 -0700'
72
+ - 'From: "Michael J. Welch, Ph.D." <mjwelchphd@gmail.com>'
73
+ - 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.2.0'
74
+ - 'MIME-Version: 1.0'
75
+ - 'To: coco@tsarmail.org'
76
+ - 'Subject: test tsar'
77
+ - 'Content-Type: text/plain; charset=utf-8; format=flowed'
78
+ - 'Content-Transfer-Encoding: 7bit'
79
+ - ''
80
+ - Coco is Sir Bitealot.
81
+ :headers:
82
+ :received: from [192.168.1.2] ([96.251.113.83]) by smtp.googlemail.com
83
+ with ESMTPSA id or3sm10805021pbb.56.2015.10.30.20.22.02 for <coco@tsarmail.org> (version=TLSv1.2
84
+ cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 30 Oct 2015 20:22:03
85
+ -0700 (PDT)
86
+ :dkim_signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=message-id:date:from:user-agent:mime-version:to:subject :content-type:content-transfer-encoding; bh=Z2jCW0avVIbUOQuL6QZlOhhVpMa2bHX2/SOpDUKV+Ec=; b=fdIuma9zlWqC1+tAAtXHXGybhB7Luki6veKE8SimmQL5BfWV5xU9NS7E0QApG2+rgp QQ6VRwy8M9ATjBn3BJvXN462HGsfihPL5DwIr8N1PWhy7wkVJxFvAI/mcbGZlYZE4p99 CXVPAYt5lBKzeMFYNF79umSIjhc2HRyE0TFyLxzrH9DlryeQ6ngDJ6VKbPsWRpG0rLS2 H8Xs2t6l5wOd22oDcE0k4x3aU6MDMw6/0enU6KuVgmlnjsIemFk0NvpTRVKcWlluDmjI OQ1x6oFNvwtxTpEC6OP89kXM6Fnc0mwn9PJSzzJ/gxmcxpNbmFZ25yHGy48TiA4jTC1y JLeA==
87
+ :x_received: by 10.66.147.136 with SMTP id tk8mr12368633pab.37.1446261724525; Fri,
88
+ 30 Oct 2015 20:22:04 -0700 (PDT)
89
+ :return_path: "<mjwelchphd@gmail.com>"
90
+ :message_id: "<563433D8.9020306@gmail.com>"
91
+ :date: Fri, 30 Oct 2015 20:22:00 -0700
92
+ :from: '"Michael J. Welch, Ph.D." <mjwelchphd@gmail.com>'
93
+ :user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.2.0
94
+ :mime_version: '1.0'
95
+ :to: coco@tsarmail.org
96
+ :subject: test tsar
97
+ :content_type: text/plain; charset=utf-8; format=flowed
98
+ :content_transfer_encoding: 7bit
@@ -0,0 +1,147 @@
1
+ #! /usr/bin/env ruby
2
+ require 'pdkim'
3
+ require 'yaml'
4
+
5
+ RSA_PRIVATE_KEY = <<EOT
6
+ -----BEGIN RSA PRIVATE KEY-----
7
+ MIICXQIBAAKBgQC5+utIbbfbpssvW0TboF73Seos+1ijdPFGwc/z8Yu12cpjBvRb
8
+ 5/qRJd83XCySRs0QkK1zWx4soPffbtyJ9TU5mO76M23lIuI5slJ4QLA0UznGxfHd
9
+ fXpK9qRnmG6A4HRHC9B93pjTo6iBksRhIeSsTL94EbUJ625i0Lqg4i6NVQIDAQAB
10
+ AoGBAIDGqJH/Pta9+GTzGovE0N0D9j1tUKPl/ocS/m4Ya7fgdQ36q8rTpyFICvan
11
+ QUmL8sQsmZ2Nkygt0VSJy/VOr6niQmoi97PY0egxvvK5mtc/nxePCGwYLOMpB6ql
12
+ 0UptotmvJU3tjyHZbksOf6LlzvpAgk7GnxLF1Cg/RJhH9ubBAkEA6b32mr52u3Uz
13
+ BjbVWez1XBcxgwHk8A4NF9yCpHtVRY3751FZbrhy7oa+ZvYokxS4dzrepZlB1dqX
14
+ IXaq7CgakQJBAMuwpG4N5x1/PfLODD7PYaJ5GSIx6BZoJObnx/42PrIm2gQdfs6W
15
+ 1aClscqMyj5vSBF+cWWqu1i7j6+qugSswIUCQA3T3BPZcqKyUztp4QM53mX9RUOP
16
+ yCBfZGzl8aCTXz8HIEDV8il3pezwcbEbnNjen+8Fv4giYd+p18j2ATSJRtECQGaE
17
+ lG3Tz4PYG/zN2fnu9KwKmSzNw4srhY82D0GSWcHerhIuKjmeTw0Y+EAC1nPQHIy5
18
+ gCd0Y/DIDgyTOCbML+UCQQClbgAoYnIpbTUklWH00Z1xr+Y95BOjb4lyjyIF98B2
19
+ FA0nM8cHuN/VLKjjcrJUK47lZEOsjLv+qTl0i0Lp6giq
20
+ -----END RSA PRIVATE KEY-----
21
+ EOT
22
+
23
+ RSA_PUBLIC_KEY_DKIM = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5+utIbbfbpssvW0TboF73Seos+1ijdPFGwc/z8Yu12cpjBvRb5/qRJd83XCySRs0QkK1zWx4soPffbtyJ9TU5mO76M23lIuI5slJ4QLA0UznGxfHdfXpK9qRnmG6A4HRHC9B93pjTo6iBksRhIeSsTL94EbUJ625i0Lqg4i6NVQIDAQAB;"
24
+
25
+ DOMAIN = "duncanthrax.net"
26
+ SELECTOR = "cheezburger"
27
+
28
+ PDKIM_RETURN_CODES = ["PDKIM_VERIFY_NONE", "PDKIM_VERIFY_INVALID", "PDKIM_VERIFY_FAIL", "PDKIM_VERIFY_PASS"]
29
+
30
+ TEST_MESSAGE = [
31
+ "From: Tom Kistner <tom@duncanthrax.net>",
32
+ "X-Folded-Header: line one",
33
+ "\tline two",
34
+ "To: PDKIM User",
35
+ "Subject: PDKIM Test",
36
+ "",
37
+ "Test 1, 2, 3, 4",
38
+ "This is a simple test of Ruby PDKIM."
39
+ ]
40
+
41
+ # the dkim header expected from this message is:
42
+ #DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple/simple; d=duncanthrax.net; s=cheezburger;
43
+ # h=Subject:To:From; bh=+oeSNE7b9Ka6Gdh9ItFGX3J6Wacjc/JxAUaId7ON0T0=;
44
+ # b=Ap6DcX3x2MEoj1E3KBow7NF/2g5CnUoqkt0hgqJ0DufuOsFAPLl0tYA+yYIoCp8Acn/BJkjVYY+WQ7mlSUJfrZEZYIq1P+BZgBdP+Z+g3vrK2zEJchvwpnP0+xKniIktxT2WRQOoH3HBb/5Z1AhtuNfPEoE+kZN22Gksto4bqdg=;
45
+
46
+ class PdkimTest
47
+
48
+ include PDKIM
49
+
50
+ def main
51
+ puts
52
+ puts
53
+ test1
54
+ puts
55
+ puts
56
+ test2
57
+ puts
58
+ puts
59
+ test3
60
+ puts
61
+ end
62
+
63
+ def display(name, unsigned_message, signed_message, signatures)
64
+ puts name.upcase
65
+ if !unsigned_message.nil?
66
+ puts "------------------------------------------------------------"
67
+ puts "---- The test email before signing. -"
68
+ puts "------------------------------------------------------------"
69
+ puts unsigned_message.join(CRLF)+CRLF
70
+ end
71
+ puts
72
+ puts "------------------------------------------------------------"
73
+ puts "---- The test email after signing. -"
74
+ puts "------------------------------------------------------------"
75
+ puts signed_message.join(CRLF)+CRLF
76
+ puts
77
+ case
78
+ when signatures.size==0
79
+ puts "------------------------------------------------------------"
80
+ puts "---- The no DKIM headers were found: -"
81
+ puts "------------------------------------------------------------"
82
+ when signatures.size==1
83
+ puts "------------------------------------------------------------"
84
+ puts "---- The signature of the DKIM header: -"
85
+ puts "------------------------------------------------------------"
86
+ signatures.each { |signature| puts "- %-36s%-20s -"%[signature[:domain], PDKIM_RETURN_CODES[signature[:verify_status]]] }
87
+ puts "------------------------------------------------------------"
88
+ else
89
+ puts "------------------------------------------------------------"
90
+ puts "---- The signatures of the DKIM headers: -"
91
+ puts "------------------------------------------------------------"
92
+ signatures.each { |signature| puts "%-38s%-20s -"%[signature[:domain], PDKIM_RETURN_CODES[signature[:verify_status]]] }
93
+ puts "------------------------------------------------------------"
94
+ end
95
+ end
96
+
97
+ def test1
98
+ # ---------- SIGN ----------
99
+ ctx = pdkim_init_sign(PDKIM_INPUT_NORMAL, DOMAIN, SELECTOR, RSA_PRIVATE_KEY)
100
+ # ok = pdkim_set_debug_stream(ctx, 2) # 2 --> STDERR
101
+ ok = pdkim_set_optional(ctx, nil, nil, PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE, -1, PDKIM_ALGO_RSA_SHA256, 0, 0)
102
+ TEST_MESSAGE.each do |line|
103
+ ok = pdkim_feed(ctx, line+CRLF, line.length+2)
104
+ end
105
+ signatures = pdkim_feed_finish(ctx)
106
+ signed_message = [signatures[0][:signature]]
107
+ signed_message.concat(TEST_MESSAGE)
108
+ pdkim_free_ctx(ctx)
109
+
110
+ # --------- VERIFY ---------
111
+ # the block on this call is mandatory, and handles the DNS lookup
112
+ # to get the domain's public key
113
+ ctx = pdkim_init_verify(PDKIM_INPUT_NORMAL) do |name|
114
+ RSA_PUBLIC_KEY_DKIM
115
+ end
116
+ signed_message.each do |line|
117
+ ok = pdkim_feed(ctx, line+CRLF, line.length+2)
118
+ end
119
+ signatures = pdkim_feed_finish(ctx)
120
+ pdkim_free_ctx(ctx)
121
+ display("TEST1 -- SIGNING/VERIFYING THE LONG WAY", TEST_MESSAGE, signed_message, signatures)
122
+ end
123
+
124
+ def test2
125
+ ok, signed_message = pdkim_sign_an_email(PDKIM_INPUT_NORMAL, DOMAIN, SELECTOR, RSA_PRIVATE_KEY, PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE, TEST_MESSAGE)
126
+ ok, signatures = pdkim_verify_an_email(PDKIM_INPUT_NORMAL, signed_message, :fake_domain_lookup)
127
+ display("TEST2 -- SIGNING/VERIFYING THE SHORT WAY", TEST_MESSAGE, signed_message, signatures)
128
+ end
129
+
130
+ def fake_domain_lookup(name)
131
+ RSA_PUBLIC_KEY_DKIM
132
+ end
133
+
134
+ # If Google ever removes the selector in this test email, this will cease to function
135
+ def test3
136
+ mail = YAML::load_file("bin/1ZsMk0-0iglmo-E9")
137
+ gmail = mail[:data][:text]
138
+ ok, signatures = pdkim_verify_an_email(PDKIM_INPUT_NORMAL, gmail)
139
+ display("TEST3 -- VERIFYING A GMAIL SIGNATURE", nil, gmail, signatures)
140
+ end
141
+
142
+ end
143
+
144
+ PdkimTest.new.main
145
+
146
+
147
+
Binary file
@@ -0,0 +1,394 @@
1
+ require 'resolv'
2
+ require_relative '../ext/pdkim/pdkimglue'
3
+
4
+ module PDKIM
5
+
6
+ CRLF = "\r\n"
7
+
8
+ # ctx = pdkim_init_sign(mode, domain, selector, rsa_privkey)
9
+ #
10
+ # Initialize context for signing.
11
+ #
12
+ # mode
13
+ # PDKIM_INPUT_NORMAL or PDKIM_INPUT_SMTP. When SMTP
14
+ # input is used, the lib will deflate double-dots at
15
+ # the start of atline to a single dot, and it will
16
+ # stop processing input when a line with and single
17
+ # dot is received (Excess input will simply be ignored).
18
+ #
19
+ # domain
20
+ # The domain to sign as. This value will land in the
21
+ # d= tag of the signature. For example, if the MAIL FROM
22
+ # address is joe@mail.example.com, the domain is
23
+ # example.com.
24
+ #
25
+ # selector
26
+ # The selector string to use. This value will land in
27
+ # the s= tag of the signature. For example, if the DNS DKIM TXT
28
+ # record contains 2015may._domainkey.example.com, the selector
29
+ # is 2015may.
30
+ #
31
+ # rsa_privkey
32
+ # The private RSA key, in ASCII armor. It MUST NOT be
33
+ # encrypted.
34
+ #
35
+ # Returns: A freshly allocated ctx (context)
36
+ #
37
+ def pdkim_init_sign(mode, domain, selector, rsa_privkey)
38
+ ruby_pdkim_init_sign(mode, domain, selector, rsa_privkey)
39
+ end
40
+
41
+ # ctx = pdkim_init_verify(mode) { |name| ...code to retrieve domain's public key... }
42
+ #
43
+ # Initialize context for verification.
44
+ #
45
+ # mode
46
+ # PDKIM_INPUT_NORMAL or PDKIM_INPUT_SMTP. When SMTP
47
+ # input is used, the lib will deflate double-dots at
48
+ # the start of atline to a single dot, and it will
49
+ # stop processing input when a line with and single
50
+ # dot is received (Excess input will simply be ignored).
51
+ #
52
+ # block
53
+ # Tom's pdkim lib does not include a DNS resolver, so one
54
+ # has been provided in this gem called "pdkim_dkim_public_key_lookup(name)."
55
+ # You may provide some other mechanism, however. This call, then, would be:
56
+ #
57
+ # ctx = pdkim_init_verify(mode) do |name|
58
+ # your_public_key_lookup(name)
59
+ # end
60
+ #
61
+ # NOTE: Although the block is on this call to pdkim_init_verify,
62
+ # the ACTUAL callbacks are made from pdkim_feed_finish as the
63
+ # DKIM signatures (there can be many) are being validated one by
64
+ # one. As each signature will have a different domain, a callback
65
+ # is used to do the lookup.
66
+ #
67
+ # Returns: A freshly allocated ctx (context)
68
+ #
69
+ def pdkim_init_verify(state)
70
+ raise ArgumentError.new("pdkim_init_verify missing block") if !block_given?
71
+ ruby_pdkim_init_verify(state) do |name|
72
+ r = yield(name)
73
+ end
74
+ end
75
+
76
+ # pdkim_set_debug_stream(ctx, file_name)
77
+ # pdkim_set_debug_stream(ctx, file_number)
78
+ #
79
+ # Set up debugging stream.
80
+ #
81
+ # When pdkim.c was compiled with DEBUG defined (which is the
82
+ # recommended default), you can set up a stream where it
83
+ # sends debug output to. If you don't set a debug
84
+ # stream, no debug output is generated.
85
+ # file_name
86
+ # If the first option is called, a file by the name
87
+ # file_name is opened, and debugging output is APPENDED to it.
88
+ # Ex: pdkim_set_debug_stream(ctx, "my_debug_log")
89
+ #
90
+ # file_number
91
+ # If the second option is choosen, a file by the number
92
+ # file_number is opened, and debugging output is APPENDED to it.
93
+ # Ex: pdkim_set_debug_stream(ctx, 2) # STDERR
94
+ #
95
+ # Returns: nil
96
+ #
97
+ def pdkim_set_debug_stream(ctx, id)
98
+ ruby_pdkim_set_debug_stream(ctx, id)
99
+ end
100
+
101
+ # ok = pdkim_set_optional(ctx, sign_headers, identity, canon_headers, \
102
+ # canon_body, bodylength, algo, created, expires)
103
+ #
104
+ # OPTIONAL: Set additional optional signing options. If you do
105
+ # not use this function, sensible defaults (see below) are used.
106
+ # Any strings you pass in are dup'ed, so you can safely release
107
+ # your copy even before calling pdkim_free() on your context.
108
+ #
109
+ # sign_headers (default nil)
110
+ # Colon-separated list of header names. Headers with
111
+ # a name matching the list will be included in the
112
+ # signature. When this is NULL, the list of headers
113
+ # recommended in RFC4781 will be used.
114
+ #
115
+ # identity (default nil)
116
+ # An identity string as described in RFC4781. It will
117
+ # be put into the i= tag of the signature.
118
+ #
119
+ # canon_headers (default PDKIM_CANON_SIMPLE)
120
+ # Canonicalization algorithm to use for headers. One
121
+ # of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED.
122
+ #
123
+ # canon_body (default PDKIM_CANON_SIMPLE)
124
+ # Canonicalization algorithm to use for the body. One
125
+ # of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED.
126
+ #
127
+ # bodylength (default -1)
128
+ # Amount of canonicalized body bytes to include in
129
+ # the body hash calculation. A value of 0 means that
130
+ # the body is not included in the signature. A value
131
+ # of -1 (the default) means that there is no limit.
132
+ #
133
+ # algo (default PDKIM_ALGO_RSA_SHA256)
134
+ # One of PDKIM_ALGO_RSA_SHA256 or PDKIM_ALGO_RSA_SHA1.
135
+ #
136
+ # created (default 0)
137
+ # Seconds since the epoch, describing when the signature
138
+ # was created. This is copied to the t= tag of the
139
+ # signature. Setting a value of 0 (the default) omits
140
+ # the tag from the signature.
141
+ #
142
+ # expires (default 0)
143
+ # Seconds since the epoch, describing when the signature
144
+ # expires. This is copied to the x= tag of the
145
+ # signature. Setting a value of 0 (the default) omits
146
+ # the tag from the signature.
147
+ #
148
+ # Returns: 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant
149
+ #
150
+ def pdkim_set_optional(ctx, sign_headers,
151
+ identity, canon_headers, canon_body, bodylength,
152
+ algo, created, expires)
153
+ ruby_pdkim_set_optional(ctx, sign_headers,
154
+ identity, canon_headers, canon_body, bodylength,
155
+ algo, created, expires)
156
+ end
157
+
158
+ # ok = pdkim_feed(ctx, data, data_len)
159
+ #
160
+ # (Repeatedly) feed data to the signing algorithm. The message
161
+ # data MUST use CRLF line endings (like SMTP uses on the
162
+ # wire). The data chunks do not need to be a "line" - you
163
+ # can split chunks at arbitrary locations.
164
+ #
165
+ # data (Ruby String which is also allowed to contain binary)
166
+ # Pointer to data to feed. Please note that despite
167
+ # the example given below, this is not necessarily a
168
+ # C string, i.e., NULL terminated.
169
+ #
170
+ # data_len
171
+ # Length of data being fed, in bytes.
172
+ #
173
+ # Returns: 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant
174
+ #
175
+ def pdkim_feed(ctx, data, len)
176
+ ruby_pdkim_feed(ctx, data, len)
177
+ end
178
+
179
+ # ok = pdkim_feed_finish(ctx)
180
+ #
181
+ # Signal end-of-message and retrieve the signature block.
182
+ #
183
+ # Returns:
184
+ # ok
185
+ # 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant
186
+ #
187
+ # signatures (An array of hashes of signatures.)
188
+ # If the function returns PDKIM_OK, it will return
189
+ # one or more signatures.
190
+ #
191
+ # Sign only returns 1 signature, but verify will return 1 signature
192
+ # for each DKIM header in the email being verified.
193
+ #
194
+ # Returns an array of hashes (only 1 for sign) with the signatures in them
195
+ # [
196
+ # {
197
+ # "error"=>0, # 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant
198
+ # "signature"=>nil,
199
+ # "version"=>1,
200
+ # "algo"=>0,
201
+ # "canon_headers"=>0,
202
+ # "canon_body"=>0,
203
+ # "querymethod"=>0,
204
+ # "selector"=>"cheezburger",
205
+ # "domain"=>"duncanthrax.net",
206
+ # "identity"=>nil,
207
+ # "created"=>0,
208
+ # "expires"=>0,
209
+ # "bodylength"=>-1,
210
+ # "headernames"=>"Subject:To:From",
211
+ # "copiedheaders"=>nil,
212
+ # "sigdata"=>"\xA1\xEDy\x16\xDF\xF1\xF8C\x18\x80\xF8\x1F@\xFCIV&\x0E\xA4\xD5 ...",
213
+ # "bodyhash"=>"M\x87\xE3_\xE5;T\xD4\x96\x90'I\xEA2\xBF\xCE\x8F\x17\xCD\xEF ...",
214
+ # "signature_header"=>nil,
215
+ # "verify_status"=>3,
216
+ # "verify_ext_status"=>0,
217
+ # "pubkey"=>{
218
+ # "version"=>"DKIM1",
219
+ # "granularity"=>"*",
220
+ # "hashes"=>nil,
221
+ # "keytype"=>"rsa",
222
+ # "srvtype"=>"*",
223
+ # "notes"=>nil,
224
+ # "key"=>"0\x81\x9F0\r\x06\t*\x86H\x86\xF7\r\x01\x01\x01\x05\x00\x03\x81\x8D ...",
225
+ # "testing"=>0,
226
+ # "no_subdomaining"=>0
227
+ # }
228
+ # }
229
+ # ]
230
+ #
231
+ def pdkim_feed_finish(ctx)
232
+ ruby_pdkim_feed_finish(ctx)
233
+ end
234
+
235
+ # pdkim_free_ctx(ctx)
236
+ #
237
+ # Free all allocated memory blocks referenced from
238
+ # the context, as well as the context itself.
239
+ #
240
+ # Don't forget to call this or your application will "leak" memory.
241
+ #
242
+ def pdkim_free_ctx(ctx)
243
+ ruby_pdkim_free_ctx(ctx)
244
+ end
245
+
246
+ # ok = pdkim_sign_an_email(mode, domain, selector, rsa_privkey, canon_headers, canon_body, unsigned_message)
247
+ #
248
+ # Call a single function to sign an email message.
249
+ #
250
+ # mode
251
+ # PDKIM_INPUT_NORMAL or PDKIM_INPUT_SMTP. When SMTP
252
+ # input is used, the lib will deflate double-dots at
253
+ # the start of atline to a single dot, and it will
254
+ # stop processing input when a line with and single
255
+ # dot is received (Excess input will simply be ignored).
256
+ #
257
+ # domain
258
+ # The domain to sign as. This value will land in the
259
+ # d= tag of the signature. For example, if the MAIL FROM
260
+ # address is joe@mail.example.com, the domain is
261
+ # example.com.
262
+ #
263
+ # selector
264
+ # The selector string to use. This value will land in
265
+ # the s= tag of the signature. For example, if the DNS DKIM TXT
266
+ # record contains 2015may._domainkey.example.com, the selector
267
+ # is 2015may.
268
+ #
269
+ # rsa_privkey
270
+ # The private RSA key, in ASCII armor. It MUST NOT be
271
+ # encrypted. For example, in the sample used for this gem,
272
+ # the private key is: RSA_PRIVKEY:
273
+ # -----BEGIN RSA PRIVATE KEY-----
274
+ # MIICXQIBAAKBgQC5+utIbbfbpssvW0TboF73Seos+1ijdPFGwc/z8Yu12cpjBvRb
275
+ # ...
276
+ # FA0nM8cHuN/VLKjjcrJUK47lZEOsjLv+qTl0i0Lp6giq
277
+ # -----END RSA PRIVATE KEY-----
278
+ #
279
+ # canon_headers (default PDKIM_CANON_SIMPLE)
280
+ # Canonicalization algorithm to use for headers. One
281
+ # of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED.
282
+ #
283
+ # canon_body (default PDKIM_CANON_SIMPLE)
284
+ # Canonicalization algorithm to use for the body. One
285
+ # of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED.
286
+ #
287
+ # unsigned_message
288
+ # An array of lines containing the email. The message
289
+ # data array MUST NOT use CRLF line endings, but each line
290
+ # is assumed to end with a CRLF (like SMTP uses on the
291
+ # wire). The lines may be of arbitrary length. A line oriented
292
+ # format was chosen because it's the "natural" way
293
+ # of formatting the data for Ruby.
294
+ #
295
+ # Returns: an array of 2 elements: [success_code, message]
296
+ # if successful, returns 0 (PDKIM_OK) and the signed_message
297
+ # if unsuccessful, returns a PDKIM_ERR_* constant and nil
298
+ #
299
+ def pdkim_sign_an_email(mode, domain, selector, rsa_privkey, canon_headers, canon_body, unsigned_message)
300
+ ctx = pdkim_init_sign(mode, domain, selector, rsa_privkey)
301
+ return [PDKIM_FAIL, verify_counts] if ctx==0
302
+ ok = pdkim_set_optional(ctx, nil, nil, canon_headers, canon_body, -1, PDKIM_ALGO_RSA_SHA256, 0, 0)
303
+ if ok!=PDKIM_OK
304
+ pdkim_free_ctx(ctx)
305
+ return [ok, nil]
306
+ end
307
+ unsigned_message.each do |line|
308
+ ok = pdkim_feed(ctx, line+CRLF, line.length+2)
309
+ if ok!=PDKIM_OK
310
+ pdkim_free_ctx(ctx)
311
+ return [ok, nil]
312
+ end
313
+ end
314
+ signatures = pdkim_feed_finish(ctx)
315
+ pdkim_free_ctx(ctx)
316
+ return [PDKIM_ERR_RSA_SIGNING, nil] if signatures.empty?
317
+ signature = signatures[0][:signature]
318
+ signed_message = [signature]
319
+ signed_message.concat(unsigned_message)
320
+ return [PDKIM_OK, signed_message]
321
+ end
322
+
323
+ # ok = verify_an_email(mode, signed_message, sym_domain_lookup)
324
+ #
325
+ # Call a single function to sign an email message.
326
+ #
327
+ # mode
328
+ # PDKIM_INPUT_NORMAL or PDKIM_INPUT_SMTP. When SMTP
329
+ # input is used, the lib will deflate double-dots at
330
+ # the start of atline to a single dot, and it will
331
+ # stop processing input when a line with and single
332
+ # dot is received (Excess input will simply be ignored).
333
+ #
334
+ # signed_message
335
+ # An array of lines containing the email preceeded by a
336
+ # DKIM header that was generated by a signing process. The message
337
+ # data array MUST NOT contain CRLF line endings; instead, each line
338
+ # will have a CRLF added here. The lines may be of arbitrary
339
+ # length. A line oriented format was chosen because it's
340
+ # the "natural" way of formatting the data for Ruby.
341
+ #
342
+ # sym_domain_lookup (OPTIONAL)
343
+ # A symbolic name of the method to use for private key lookups.
344
+ # If this parameter is not given, it defaults to :pdkim_dkim_public_key_lookup
345
+ # which is the built-in lookup function.
346
+ #
347
+ # Returns:
348
+ # ok
349
+ # 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant
350
+ #
351
+ # signatures (An array of hashes of signatures.)
352
+ # If the function returns PDKIM_OK, it will return
353
+ # one or more signatures.
354
+ #
355
+ def pdkim_verify_an_email(mode, signed_message, sym_domain_lookup=:pdkim_dkim_public_key_lookup)
356
+ ctx = pdkim_init_verify(mode) do |name|
357
+ send(sym_domain_lookup, name)
358
+ end
359
+ return [PDKIM_FAIL, verify_counts] if ctx==0
360
+ ok = PDKIM_FAIL
361
+ signed_message.each do |line|
362
+ ok = pdkim_feed(ctx, line+CRLF, line.length+2)
363
+ if ok!=PDKIM_OK
364
+ pdkim_free_ctx(ctx)
365
+ return [ok, verify_counts]
366
+ end
367
+ end
368
+ signatures = pdkim_feed_finish(ctx)
369
+ pdkim_free_ctx(ctx)
370
+ return ok, signatures
371
+ end
372
+
373
+ # key = pdkim_dkim_public_key_lookup(name)
374
+ #
375
+ # This method retrieves the public key from the domain's
376
+ # website's DNS records, if any. If it fails, it will return
377
+ # "nil" which will cause the validation to fail with
378
+ # PDKIM_VERIFY_FAIL.
379
+ #
380
+ # name
381
+ # The name to be looked up by the resolver. It has the form:
382
+ # selector._domainkey.domain.com (org, biz, us, gov, etc.)
383
+ # the name will be properly formatted if this method is
384
+ # called from the block in pdkim_init_verify.
385
+ #
386
+ # Returns: The DKIM public key for the domain in 'name' or nil
387
+ #
388
+ def pdkim_dkim_public_key_lookup(name)
389
+ records = [];
390
+ Resolv::DNS.open { |dns| records = dns.getresources(name, Resolv::DNS::Resource::IN::TXT) }
391
+ if records.empty? then nil else records[0].strings.join("") end
392
+ end
393
+
394
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdkim
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.9'
5
+ platform: ruby
6
+ authors:
7
+ - Tom Kistner
8
+ - Michael J. Welch, Ph.D.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-07-20 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: |-
15
+ PDKIM - a RFC4871 (DKIM) implementation
16
+ http://duncanthrax.net/pdkim/
17
+ Copyright (C) 2009 Tom Kistner <tom@duncanthrax.net>
18
+
19
+ Includes code from the PolarSSL project.
20
+ http://polarssl.org
21
+ Copyright (C) 2009 Paul Bakker <polarssl_maintainer@polarssl.org>
22
+ Copyright (C) 2006-2008 Christophe Devine
23
+
24
+ This gem (C) 2015-2017 Michael J. Welch, Ph.D. <mjwelchphd@gmail.com>
25
+ Source code can be found on GitHub: https://github.com/mjwelchphd/pdkim
26
+
27
+ Test by running pdkimgemtest after installing.
28
+
29
+ PDKIM is the pacakge that Exim4 uses for DKIM support.
30
+ email: mjwelchphd@gmail.com
31
+ executables:
32
+ - pdkimgemtest
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - bin/1ZsMk0-0iglmo-E9
37
+ - bin/pdkimgemtest
38
+ - ext/pdkim/pdkimglue.so
39
+ - lib/pdkim.rb
40
+ homepage: http://www.czarmail.com
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ - ext
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.4.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: A Ruby Gem wrapping Tom Kistner's PDKIM package.
65
+ test_files: []