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 =
{ "sha1" => OpenSSL::Digest::SHA1, "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
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

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.



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/mailauth/dkim.rb', line 57

def self.parse_tags(text)
  specs = text.split(";", -1)
  specs.pop if specs.last&.strip&.empty?   # the tag-list's optional trailing ";"

  specs.each_with_object({}) do |spec, tags|
    name, value = spec.split("=", 2)
    name = name.to_s.strip

    return nil unless value && name.match?(TAG_NAME) && !tags.key?(name)

    tags[name] = value.strip
  end
end

.sign(raw, domain:, selector:, key:, headers: nil, oversign: Signer::OVERSIGNED_HEADERS, expires_in: nil, now: Time.now) ⇒ Object



48
49
50
# File 'lib/mailauth/dkim.rb', line 48

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:).signed_message
end

.verify(message, resolver: Resolver.new, now: Time.now) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/mailauth/dkim.rb', line 39

def self.verify(message, resolver: Resolver.new, now: Time.now)
  signatures = message.fields_named("dkim-signature").map do |field|
    Verification.new(message, field, resolver:, now:).result
  end
  signatures << Signature.new(status: Status::NONE, comment: "message not signed") if signatures.empty?

  DkimResult.new(signatures:)
end