Module: MailAuth::Dkim
- Defined in:
- lib/mailauth/dkim.rb,
lib/mailauth/dkim/signer.rb,
lib/mailauth/dkim/algorithm.rb,
lib/mailauth/dkim/public_key.rb,
lib/mailauth/dkim/canonicalization.rb
Overview
RFC 6376. Verifies the DKIM-Signature headers on a message against the public keys their domains publish, and signs outgoing messages with the private ones.
Defined Under Namespace
Modules: Algorithm, Canonicalization, PublicKey Classes: Malformed, Signer, Verification
Constant Summary collapse
- CRLF =
"\r\n".b
- HASHES =
RFC 8301 §3.1 retired rsa-sha1: a verifier must not consider one valid.
{ "sha256" => OpenSSL::Digest::SHA256 }.freeze
- SIGNING_ALGORITHMS =
%w[ rsa ed25519 ].freeze
- KEY_TYPES =
What OpenSSL calls a key, mapped to what a= calls it.
{ "rsaEncryption" => "rsa", "ED25519" => "ed25519" }.freeze
- CANONICALIZATIONS =
%w[ simple relaxed ].freeze
- REQUIRED_TAGS =
%w[ v a b bh d h s ].freeze
- TAG_NAME =
RFC 6376 §3.2: ALPHA *ALNUMPUNC.
/\A[a-zA-Z][a-zA-Z0-9_]*\z/n- MAXIMUM_SIGNATURES =
RFC 6376 §8.4. Each signature costs a lookup for a key its sender named, and headers are cheap to write, so only the first so many are answered.
10- MINIMUM_RSA_BITS =
Shorter RSA keys are factorable cheaply enough to prove nothing.
1024- ED25519_SPKI_PREFIX =
ed25519 keys travel as 32 raw bytes (RFC 8463 §3), so we put back the SubjectPublicKeyInfo wrapper OpenSSL insists on before reading one.
[ "302a300506032b6570032100" ].pack("H*")
Class Method Summary collapse
-
.parse_tags(text) ⇒ Object
RFC 6376 §3.2.
- .sign(raw, domain:, selector:, key:, headers: nil, oversign: Signer::OVERSIGNED_HEADERS, expires_in: nil, now: Time.now) ⇒ Object
- .verify(message, resolver: Resolver.new, now: Time.now) ⇒ Object
Class Method Details
.parse_tags(text) ⇒ Object
RFC 6376 §3.2. A malformed tag name, a repeated one, or an empty entry invalidates the whole list rather than the one tag: reading the rest would take a forged tag as read beside the one silently dropped. Values fold across lines, so callers strip whitespace themselves on the tags where it matters.
72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/mailauth/dkim.rb', line 72 def self.(text) specs = text.split(";", -1) specs.pop if specs.last&.strip&.empty? # the tag-list's optional trailing ";" specs.each_with_object({}) do |spec, | name, value = spec.split("=", 2) name = name.to_s.strip return nil unless value && name.match?(TAG_NAME) && !.key?(name) [name] = value.strip end end |
.sign(raw, domain:, selector:, key:, headers: nil, oversign: Signer::OVERSIGNED_HEADERS, expires_in: nil, now: Time.now) ⇒ Object
63 64 65 |
# File 'lib/mailauth/dkim.rb', line 63 def self.sign(raw, domain:, selector:, key:, headers: nil, oversign: Signer::OVERSIGNED_HEADERS, expires_in: nil, now: Time.now) Signer.new(raw, domain:, selector:, key:, headers:, oversign:, expires_in:, now:). end |
.verify(message, resolver: Resolver.new, now: Time.now) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/mailauth/dkim.rb', line 44 def self.verify(, resolver: Resolver.new, now: Time.now) fields = .fields_named("dkim-signature") signatures = fields.first(MAXIMUM_SIGNATURES).map do |field| Verification.new(, field, resolver:, now:).result end # The ones left over are reported rather than dropped: a verifier that # quietly ignores a signature says a message carried fewer than it did. if fields.empty? signatures << Signature.new(status: Status::NONE, comment: "message not signed") elsif fields.length > MAXIMUM_SIGNATURES signatures << Signature.new(status: Status::PERMERROR, comment: "#{fields.length - MAXIMUM_SIGNATURES} more signatures not evaluated") end DkimResult.new(signatures:) end |