yast/yast-storage-ng

View on GitHub
src/lib/y2storage/encryption_processes/pervasive.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2019-2023] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2storage/encryption_type"
require "y2storage/encryption_processes/base"
require "y2storage/encryption_processes/secure_key"
require "yast2/execute"
require "yast"

Yast.import "Mode"

module Y2Storage
  module EncryptionProcesses
    # Encryption process that allows to create and identify a volume encrypted
    # with Pervasive Encryption.
    #
    # For more information, see
    # https://www.ibm.com/support/knowledgecenter/linuxonibm/liaaf/lnz_r_dccnt.html
    class Pervasive < Base
      # Location of the zkey command
      ZKEY = "/usr/bin/zkey".freeze
      private_constant :ZKEY

      attr_reader :apqns

      # Creates an encryption layer over the given block device
      #
      # @param blk_device [Y2Storage::BlkDevice]
      # @param dm_name [String]
      # @param apqns [Array<Apqn>] APQNs to use for generating the secure key
      #
      # @return [Encryption]
      def create_device(blk_device, dm_name, apqns: [])
        @secure_key = SecureKey.for_device(blk_device)
        @apqns = apqns

        if @secure_key
          # Should we discard the key if it's not a LUKS2 one?
          # Or maybe we should modify the secure key in that case?
          name_from_key = @secure_key.dm_name(blk_device)
          dm_name = name_from_key if name_from_key
        end

        super(blk_device, dm_name)
      end

      # @see Base#pre_commit
      #
      # If there is no secure key to be used for this device, a new key is
      # generated.
      #
      # @param device [Encryption] encryption that will be created in the system
      def pre_commit(device)
        # For the time being, we will always generate a new key for each device
        # if there was not a preexisting one. In the future we can extend the
        # API to allow sharing a new key among several volumes.
        @secure_key ||= generate_secure_key(device)

        master_key_file = @secure_key.filename
        sector_size = sector_size_for(device.blk_device)

        # NOTE: The options cipher and key-size could also be influenced by setting
        # Encryption#cipher and Encryption#key_size. If those attributes have any value, the
        # correspoding format options are prepended to Encryption#format_options by libstorage-ng.
        device.format_options = "--master-key-file #{master_key_file.shellescape} --key-size 1024 "\
                                "--cipher paes-xts-plain64"
        device.format_options += " --sector-size #{sector_size}" if sector_size
        device.format_options += " --pbkdf pbkdf2"
      end

      # @see Base#post_commit
      #
      # Adds the device to the secure key if needed and executes the
      # extra commands reported by a call to "zkey cryptsetup".
      #
      # @param device [Encryption] encryption that has just been created in the system
      def post_commit(device)
        @secure_key.add_device_and_write(device) unless @secure_key.for_device?(device)

        zkey_cryptsetup_output = execute_zkey_cryptsetup(device)
        commands = zkey_cryptsetup_output[1..-1]
        return if commands.nil?

        commands.each do |command|
          args = command.split

          if args.any? { |arg| "setvp".casecmp(arg) == 0 }
            args += ["--key-file", "-"]
            Yast::Execute.locally(*args, stdin: device.password, recorder: cheetah_recorder)
          else
            Yast::Execute.locally(*args)
          end
        end
      end

      # @see Base#finish_installation
      #
      # Copies the keys from the zkey repository of the inst-sys to the
      # repository of the target system.
      def finish_installation
        secure_key.copy_to_repository(Yast::Installation.destdir)
      end

      # Class to prevent Yast::Execute from leaking to the logs the password
      # provided via stdin
      class Recorder < Cheetah::DefaultRecorder
        # To prevent leaking stdin, just do nothing
        def record_stdin(_stdin); end
      end

      # @see Base#encryption_type
      def encryption_type
        EncryptionType::LUKS2
      end

      private

      # @return [SecureKey] master key used to encrypt the device
      attr_reader :secure_key

      # @return [Array<String>] lines of the output of the "zkey cryptsetup"
      #   command executed during the pre-commit phase
      attr_reader :zkey_cryptsetup_output

      # Custom Cheetah recorder to prevent leaking the password to the logs
      #
      # @return [Recorder]
      def cheetah_recorder
        Recorder.new(Yast::Y2Logger.instance)
      end

      # Generates a new secure key for the given encryption device and
      # registers it into the keys database of the system. The secure
      # does not include the volume since that may not exist yet.
      #
      # @param device [Encryption]
      # @return [SecureKey]
      def generate_secure_key(device)
        key_name = "YaST_#{device.dm_table_name}"
        key = SecureKey.generate(
          key_name,
          sector_size: sector_size_for(device.blk_device),
          apqns:       apqns
        )
        log.info "Generated secure key #{key.name}"

        key
      end

      # Executes the "zkey cryptsetup" used to get the list of commands that
      # must be executed during the pervasive encryption process
      #
      # @return [Array<String>]
      def execute_zkey_cryptsetup(device)
        name = secure_key.plain_name(device)
        command = [ZKEY, "cryptsetup", "--volumes", name]
        Yast::Execute.locally(*command, stdout: :capture)&.lines&.map(&:strip) || []
      end
    end
  end
end