Class: Parallel::Serializer::Hmac

Inherits:
Object
  • Object
show all
Defined in:
lib/parallel/serializer.rb

Overview

Wraps any inner serializer with a length-prefixed HMAC-SHA256 frame keyed on a per-worker secret generated before fork. Forged frames from a pipe-injector fail verification.

Constant Summary collapse

LENGTH_FORMAT =

32-bit big-endian unsigned int

'N'
LENGTH_BYTES =
4
MAC_BYTES =

SHA256

32

Instance Method Summary collapse

Constructor Details

#initialize(inner: Marshal, secret: SecureRandom.bytes(32)) ⇒ Hmac

Returns a new instance of Hmac.



22
23
24
25
# File 'lib/parallel/serializer.rb', line 22

def initialize(inner: Marshal, secret: SecureRandom.bytes(32))
  @inner = inner
  @secret = secret
end

Instance Method Details

#dump(data, io) ⇒ Object



27
28
29
30
31
# File 'lib/parallel/serializer.rb', line 27

def dump(data, io)
  payload = @inner.dump(data)
  mac = OpenSSL::HMAC.digest('SHA256', @secret, payload)
  io.write([payload.bytesize].pack(LENGTH_FORMAT), mac, payload)
end

#load(io) ⇒ Object

Raises:

  • (SecurityError)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/parallel/serializer.rb', line 33

def load(io)
  # nil at frame boundary = clean EOF (worker died / pipe closed between messages)
  header = io.read(LENGTH_BYTES) || raise(EOFError) # eof stops worker
  raise SecurityError, "truncated frame header" if header.bytesize != LENGTH_BYTES

  length = header.unpack1(LENGTH_FORMAT)
  mac = io.read(MAC_BYTES)
  raise SecurityError, "truncated frame mac" if mac.nil? || mac.bytesize != MAC_BYTES

  payload = io.read(length)
  raise SecurityError, "truncated frame payload" if payload.nil? || payload.bytesize != length

  expected = OpenSSL::HMAC.digest('SHA256', @secret, payload)
  raise SecurityError, "HMAC mismatch on worker pipe" unless OpenSSL.fixed_length_secure_compare(mac, expected)

  @inner.load(payload)
end