mailkite 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mailkite.rb +74 -1
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42cf7ba9f50876399828e00154066736c0210841c052f13c985d55fdce4f1d44
4
- data.tar.gz: 56fc0377973778fd28f55386045df4df9b160813e4f601e86694d5393eef91c5
3
+ metadata.gz: cf95721d75808a7648a0d5c5528a5b24cebe649e4e7b4f2aa7cf96309492c4df
4
+ data.tar.gz: fd40da8370ad8ad24b538613a29a8b623226901a8db8e5bc6a4b0b3ebd1ec709
5
5
  SHA512:
6
- metadata.gz: d9682dccd33207a1fdf67ac6ec85461c897bdf245c1f8bae43c3363c231e431434bd75c433ffa2e8f66ee5619b878e5672af3b598d32732989222173e0105391
7
- data.tar.gz: cff4c8b4683f22ef86abe586238a6d44367658667f6dded3a7257900760fb202a68e6fec2fc81c8d0b483b657d93004962a2d6162fc3ed23a30f56cba2558002
6
+ metadata.gz: 96e4a3c87afd08886338448d1edf8bfd83d7433733bba220cb892647249c923d46dde87df440ce78c84f1301b734fec480b89d9ee2e54f8eef2e528fec4af4dc
7
+ data.tar.gz: d7b4ccb09e09a382e7d9bef520115c50aa06c635646763b73cea18bc133326b6080371a56037b3f05ccb5461b53b9d2369d4f5bfde84dedd1e5770d0fe0443d2
data/lib/mailkite.rb CHANGED
@@ -11,10 +11,11 @@ require "net/http"
11
11
  require "uri"
12
12
  require "json"
13
13
  require "openssl"
14
+ require "base64"
14
15
  require "cgi"
15
16
 
16
17
  module Mailkite
17
- VERSION = "0.3.0"
18
+ VERSION = "0.4.0"
18
19
  DEFAULT_BASE_URL = "https://api.mailkite.dev"
19
20
  # Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
20
21
  DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
@@ -54,6 +55,65 @@ module Mailkite
54
55
  diff.zero?
55
56
  end
56
57
 
58
+ # The canonical body to return from a webhook handler so MailKite marks the
59
+ # delivery acknowledged. Returns the exact string `{"status":"ok"}`.
60
+ def self.reply_ok
61
+ '{"status":"ok"}'
62
+ end
63
+
64
+ # Encrypt a UTF-8 string to a customer RSA public key (SPKI/PEM), producing the
65
+ # MailKite at-rest envelope as a compact JSON string. Hybrid scheme: a fresh
66
+ # AES-256-GCM content key encrypts the data, then the raw key is wrapped with
67
+ # RSA-OAEP (SHA-256). Local only — no network.
68
+ def self.encrypt(plaintext, public_key)
69
+ pub = OpenSSL::PKey.read(public_key)
70
+ fp = OpenSSL::Digest::SHA256.hexdigest(pub.to_der)
71
+
72
+ raw_key = OpenSSL::Random.random_bytes(32)
73
+ iv = OpenSSL::Random.random_bytes(12)
74
+
75
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
76
+ cipher.encrypt
77
+ cipher.key = raw_key
78
+ cipher.iv = iv
79
+ body = cipher.update(plaintext.to_s.dup.force_encoding("UTF-8")) + cipher.final
80
+ ciphertext = body + cipher.auth_tag # GCM ct with 16-byte tag appended
81
+
82
+ wrapped = pub.encrypt(raw_key, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
83
+
84
+ JSON.generate({
85
+ "v" => 1,
86
+ "keyAlg" => "RSA-OAEP-256",
87
+ "fp" => fp,
88
+ "enc" => "A256GCM",
89
+ "iv" => Base64.strict_encode64(iv),
90
+ "wrappedKey" => Base64.strict_encode64(wrapped),
91
+ "ciphertext" => Base64.strict_encode64(ciphertext),
92
+ })
93
+ end
94
+
95
+ # Decrypt a MailKite at-rest envelope (JSON string) with the matching RSA
96
+ # private key (PEM), returning the original UTF-8 plaintext. Local only.
97
+ def self.decrypt(envelope, private_key)
98
+ env = envelope.is_a?(String) ? JSON.parse(envelope) : envelope
99
+ priv = OpenSSL::PKey.read(private_key)
100
+
101
+ iv = Base64.strict_decode64(env["iv"])
102
+ wrapped = Base64.strict_decode64(env["wrappedKey"])
103
+ ct = Base64.strict_decode64(env["ciphertext"])
104
+ body = ct[0...-16]
105
+ tag = ct[-16..]
106
+
107
+ raw_key = priv.decrypt(wrapped, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
108
+
109
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
110
+ cipher.decrypt
111
+ cipher.key = raw_key
112
+ cipher.iv = iv
113
+ cipher.auth_tag = tag
114
+ (cipher.update(body) + cipher.final).force_encoding("UTF-8")
115
+ end
116
+
57
117
  class Error < StandardError
58
118
  attr_reader :status, :body
59
119
 
@@ -204,5 +264,18 @@ module Mailkite
204
264
  def verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS)
205
265
  Mailkite.verify_webhook(signature, payload, secret, tolerance_ms)
206
266
  end
267
+
268
+ # Instance wrappers around the module-level crypto helpers. No network call.
269
+ def reply_ok
270
+ Mailkite.reply_ok
271
+ end
272
+
273
+ def encrypt(plaintext, public_key)
274
+ Mailkite.encrypt(plaintext, public_key)
275
+ end
276
+
277
+ def decrypt(envelope, private_key)
278
+ Mailkite.decrypt(envelope, private_key)
279
+ end
207
280
  end
208
281
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailkite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MailKite
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-24 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Send and manage email over your own authenticated domain with MailKite.
14
14
  email: