Linux Unified Key Setup: Ruby parsing library

Linux Unified Key Setup (LUKS) is a format specification for storing disk encryption parameters and up to 8 user keys (which can unlock the master key).

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Linux Unified Key Setup using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = Luks.from_file("path/to/local/file.Linux Unified Key Setup")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = Luks.new(Kaitai::Struct::Stream.new(bytes))

After that, one can get various attributes from the structure by invoking getter methods like:

data.partition_header # => get partition header

Ruby source code to parse Linux Unified Key Setup

luks.rb

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
  raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end


##
# Linux Unified Key Setup (LUKS) is a format specification for storing disk
# encryption parameters and up to 8 user keys (which can unlock the master key).
# @see https://gitlab.com/cryptsetup/cryptsetup/wikis/LUKS-standard/on-disk-format.pdf Source
class Luks < Kaitai::Struct::Struct
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @partition_header = PartitionHeader.new(@_io, self, @_root)
    self
  end
  class PartitionHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @magic = @_io.read_bytes(6)
      raise Kaitai::Struct::ValidationNotEqualError.new([76, 85, 75, 83, 186, 190].pack('C*'), magic, _io, "/types/partition_header/seq/0") if not magic == [76, 85, 75, 83, 186, 190].pack('C*')
      @version = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 1].pack('C*'), version, _io, "/types/partition_header/seq/1") if not version == [0, 1].pack('C*')
      @cipher_name_specification = (@_io.read_bytes(32)).force_encoding("ASCII")
      @cipher_mode_specification = (@_io.read_bytes(32)).force_encoding("ASCII")
      @hash_specification = (@_io.read_bytes(32)).force_encoding("ASCII")
      @payload_offset = @_io.read_u4be
      @number_of_key_bytes = @_io.read_u4be
      @master_key_checksum = @_io.read_bytes(20)
      @master_key_salt_parameter = @_io.read_bytes(32)
      @master_key_iterations_parameter = @_io.read_u4be
      @uuid = (@_io.read_bytes(40)).force_encoding("ASCII")
      @key_slots = Array.new(8)
      (8).times { |i|
        @key_slots[i] = KeySlot.new(@_io, self, @_root)
      }
      self
    end
    class KeySlot < Kaitai::Struct::Struct

      KEY_SLOT_STATES = {
        57005 => :key_slot_states_disabled_key_slot,
        11301363 => :key_slot_states_enabled_key_slot,
      }
      I__KEY_SLOT_STATES = KEY_SLOT_STATES.invert
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @state_of_key_slot = Kaitai::Struct::Stream::resolve_enum(KEY_SLOT_STATES, @_io.read_u4be)
        @iteration_parameter = @_io.read_u4be
        @salt_parameter = @_io.read_bytes(32)
        @start_sector_of_key_material = @_io.read_u4be
        @number_of_anti_forensic_stripes = @_io.read_u4be
        self
      end
      def key_material
        return @key_material unless @key_material.nil?
        _pos = @_io.pos
        @_io.seek((start_sector_of_key_material * 512))
        @key_material = @_io.read_bytes((_parent.number_of_key_bytes * number_of_anti_forensic_stripes))
        @_io.seek(_pos)
        @key_material
      end
      attr_reader :state_of_key_slot
      attr_reader :iteration_parameter
      attr_reader :salt_parameter
      attr_reader :start_sector_of_key_material
      attr_reader :number_of_anti_forensic_stripes
    end
    attr_reader :magic
    attr_reader :version
    attr_reader :cipher_name_specification
    attr_reader :cipher_mode_specification
    attr_reader :hash_specification
    attr_reader :payload_offset
    attr_reader :number_of_key_bytes
    attr_reader :master_key_checksum
    attr_reader :master_key_salt_parameter
    attr_reader :master_key_iterations_parameter
    attr_reader :uuid
    attr_reader :key_slots
  end
  def payload
    return @payload unless @payload.nil?
    _pos = @_io.pos
    @_io.seek((partition_header.payload_offset * 512))
    @payload = @_io.read_bytes_full
    @_io.seek(_pos)
    @payload
  end
  attr_reader :partition_header
end