diff --git a/src/lib/y2partitioner/actions/controllers/encryption.rb b/src/lib/y2partitioner/actions/controllers/encryption.rb index 9f1aa8df1..06bfe8a41 100644 --- a/src/lib/y2partitioner/actions/controllers/encryption.rb +++ b/src/lib/y2partitioner/actions/controllers/encryption.rb @@ -151,7 +151,7 @@ def secure_key # # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] def online_apqns - @online_apqns ||= Y2Storage::EncryptionProcesses::Apqn.online + @online_apqns ||= Y2Storage::EncryptionProcesses::Apqn.online.select(&:master_key_pattern) end # Finds an online APQN by its name @@ -234,9 +234,29 @@ def initial_pbkdf # Currently used APQNs when the device is encrypted with pervasive encryption # # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] - def initial_apqns + def initial_pervasive_key process = encryption&.encryption_process + master_key = process_pervasive_key(process) if process + return master_key if master_key + + pervasive_keys.first + end + + def process_pervasive_key(process) + return nil unless process.respond_to?(:apqns) + + apqn = process.apqns.first + return nil unless apqn + + apqn.master_key_pattern + end + + # Currently used APQNs when the device is encrypted with pervasive encryption + # + # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] + def initial_apqns + process = encryption&.encryption_process return [] unless process.respond_to?(:apqns) process.apqns diff --git a/src/lib/y2partitioner/widgets/apqn_selector.rb b/src/lib/y2partitioner/widgets/apqn_selector.rb index 68c8676e1..a70c3d64f 100644 --- a/src/lib/y2partitioner/widgets/apqn_selector.rb +++ b/src/lib/y2partitioner/widgets/apqn_selector.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2020] SUSE LLC +# Copyright (c) [2020-2024] SUSE LLC # # All Rights Reserved. # @@ -17,68 +17,127 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. -require "yast2/popup" +require "yast" require "cwm" -require "y2partitioner/actions/controllers/encryption" +require "yast2/popup" +require "y2partitioner/widgets/encrypt_password" +require "y2partitioner/widgets/encrypt_label" +require "y2partitioner/widgets/pbkdf_selector" +require "y2partitioner/widgets/apqn_selector" module Y2Partitioner module Widgets - # Widget to select APQNs for generating a new secure key for pervasive encryption - class ApqnSelector < CWM::MultiSelectionBox + # Widget to select the APQNs for pervasive encryption + class ApqnSelector < CWM::ReplacePoint + # Internal widget used when there are several candidate APQNs for the given key + class ApqnMultiSelector < CWM::MultiSelectionBox + # Constructor + def initialize(id, all_apqns, selected_apqns) + super() + textdomain "storage" + + self.widget_id = id + @apqns = all_apqns + @selected_apqns = selected_apqns + end + + # @return [String] + def label + _("Available APQNs:") + end + + # @return [Array<String, String>] + def items + @apqns.map { |a| [a, a] } + end + + def init + self.value = @selected_apqns + end + end + # Constructor - # - # @param controller [Actions::Controllers::Encryption] - # @param enable [Boolean] whether the widget should be enabled on init - def initialize(controller, enable: true) - super() + def initialize(apqns_by_key, initial_key, initial_apqns, enable: true) textdomain "storage" - @controller = controller + @apqns_by_key = apqns_by_key + @initial_key = initial_key + @initial_apqns = initial_apqns @enable_on_init = enable - end - # @return [String] - def label - _("Available APQNs:") + super(id: "apqn_selector", widget: widgets_by_key[initial_key]) end # @macro seeAbstractWidget def init + super enable_on_init ? enable : disable - self.value = controller.apqns end - # @return [Array<String, String>] - def items - controller.online_apqns.map { |d| [d.name, d.name] } + # @macro seeAbstractWidget + # + # Note its internal widget needs to be enabled. + def enable + super + + widgets_by_key.each_value { |w| w.enable if w.respond_to?(:enable) } end - # All selected APQNs + # @macro seeAbstractWidget # - # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] - def value - super.map { |d| controller.find_apqn(d) }.compact + # Note its internal widget needs to be disabled. + def disable + super + + all_widgets.each_value { |w| w.disable if w.respond_to?(:disable) } end - # Sets selected APQNs + # Redraws the widget to show the options for given master key # - # @param apqns [Array<Y2Storage::EncryptionProcesses::Apqn>] - def value=(apqns) - super(apqns.map(&:name)) + # @param key [String] + def refresh(key) + replace(widgets_by_key[key]) end - # Saves the selected APQNs into the controller - def store - controller.apqns = value + # All selected APQNs, if there are several possible ones + # + # @return [Array<Y2Storage::EncryptionProcesses::Apqn>, nil] nil if there is only one possible APQN + def value + return unless @widget.respond_to?(:value) + + @widget.value end private - # @return [Actions::Controllers::Encryption] - attr_reader :controller - # @return [Boolean] attr_reader :enable_on_init + + # @return [Hash] list of possible APQNs for each configured master key + attr_reader :apqns_by_key + + # @return [Hash{String => CWM::AbstractWidget}] + def widgets_by_key + return @widgets_by_key if @widgets_by_key + + @widgets_by_key = {} + apqns_by_key.each do |key, apqns| + selected = key == @initial_key ? @initial_apqns : apqns + @widgets_by_key[key] = widget_for(key, apqns, selected) + end + + @widgets_by_key + end + + # Returns either a selector or an empty widget (if there is only one possible APQN) + def widget_for(key, apqns, selected) + widget_id = "Details#{key}" + if apqns.size > 1 + ApqnMultiSelector.new(widget_id, apqns.map(&:name), selected.map(&:name)) + else + CWM::Empty.new(widget_id) + end + end end end end diff --git a/src/lib/y2partitioner/widgets/encrypt_method_options.rb b/src/lib/y2partitioner/widgets/encrypt_method_options.rb index 9c97e1e26..87c7e19bd 100644 --- a/src/lib/y2partitioner/widgets/encrypt_method_options.rb +++ b/src/lib/y2partitioner/widgets/encrypt_method_options.rb @@ -23,7 +23,10 @@ require "y2partitioner/widgets/encrypt_password" require "y2partitioner/widgets/encrypt_label" require "y2partitioner/widgets/pbkdf_selector" +require "y2partitioner/widgets/pervasive_key" +require "y2partitioner/widgets/pervasive_key_selector" require "y2partitioner/widgets/apqn_selector" +require "y2partitioner/widgets/pervasive_key_type_selector" module Y2Partitioner module Widgets @@ -210,36 +213,70 @@ def label_widget # Internal widget to display the pervasive encryption options class PervasiveOptions < LuksOptions + def initialize(controller, enable: true) + super + textdomain "storage" + self.handle_all_events = true + end + + # Handles the events coming from UI, forcing to refresh the encrypt + # options each time the encryption method is changed. + # + # @macro seeCustomWidget + def handle(event) + if select_master_key? && event["ID"] == master_key_widget.widget_id + full_key_widget.refresh(master_key_widget.value) + apqn_widget.refresh(master_key_widget.value) if select_apqns? + key_type_widget.refresh(candidate_apqns.first.name) + end + + nil + end + # @return [Boolean] def validate validate_secure_key_generation end + # Saves the selected APQNs into the controller + def store + controller.apqns = selected_apqns + end + private # @see LuksOptions#widgets def widgets widgets = super - widgets << apqn_widget if select_apqns? + return widgets if exist_secure_key? + if select_master_key? + widgets << master_key_widget + widgets << full_key_widget + end + widgets << apqn_widget if select_apqns? + widgets << key_type_widget widgets end - # Widget to allow the APQNs selection + # APQNs that can be chosen, based on the master key currently selected (implicitly or + # explicitly) at the UI # - # @return [Widgets::ApqnSelector] - def apqn_widget - @apqn_widget ||= Widgets::ApqnSelector.new(@controller, enable: enable_on_init) + # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] + def candidate_apqns + return apqns_by_key.values.first unless select_master_key? + + apqns_by_key[master_key_widget.value] end - # Whether it is possible to select APQNs + # Set of APQNs selected at the UI (implicitly or explicitly) # - # APQNs can be selected when there are more than one APQN available and the device has not have an - # associated secure key yet. - # - # @return [Boolean] - def select_apqns? - !exist_secure_key? && several_apqns? + # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] + def selected_apqns + candidate = candidate_apqns + return candidate if candidate.size == 1 + + apqn_widget.value.map { |a| controller.find_apqn(a) }.compact end # Whether there is an secure key for the device @@ -249,11 +286,95 @@ def exist_secure_key? !controller.secure_key.nil? end - # Whether there are several available APQNs + # Whether the AES master key can be chosen + # + # The master key can be chosen when there are several keys available and the device does not + # have an associated secure key yet. # # @return [Boolean] - def several_apqns? - controller.online_apqns.size > 1 + def select_master_key? + apqns_by_key.keys.size > 1 + end + + # Whether there is any possibility to define the APQNs + # + # @return [Boolean] + def select_apqns? + apqns_by_key.any? { |i| i.last.size > 1 } + end + + # @return [String] + def initial_key + @initial_key ||= + if controller.apqns.empty? + pervasive_keys.first + else + find_key_for(controller.apqns.first) + end + end + + # Master key configured at the given APQN + # + # @param apqn [Y2Storage::EncryptionProcesses::Apqn] + # @return [String] + def find_key_for(apqn) + apqns_by_key.each do |key, apqns| + return key if apqns.include?(apqn) + end + end + + # @return [Array<Y2Storage::EncryptionProcesses::Apqn>] + def initial_apqns + @initial_apqns ||= + if controller.apqns.empty? + apqns_by_key[initial_key] + else + controller.apqns + end + end + + # All existing master keys + # + # @return [Array<String>] + def pervasive_keys + @pervasive_keys ||= apqns_by_key.keys.sort + end + + def apqns_by_key + @apqns_by_key ||= controller.online_apqns.group_by(&:master_key_pattern) + end + + # Widget to allow the master key selection + # + # @return [Widgets::PervasiveKeySelector] + def master_key_widget + @master_key_widget ||= + Widgets::PervasiveKeySelector.new(apqns_by_key, initial_key, enable: enable_on_init) + end + + # Widget to allow the APQNs selection + # + # @return [Widgets::ApqnSelector] + def apqn_widget + @apqn_widget ||= + Widgets::ApqnSelector.new(apqns_by_key, initial_key, initial_apqns, enable: enable_on_init) + end + + # Read-only widget to display the full verification pattern of the chosen master key, + # if needed + # + # @return [Widgets::PervasiveKey] + def full_key_widget + @full_key_widget ||= Widgets::PervasiveKey.new(initial_key) + end + + # Widget to choose the type of CCA key to use (AES Data vs AES Cipher) + # + # @return [Widgets::PervasiveKeyTypeSelector] + def key_type_widget + @key_type_widget ||= PervasiveKeyTypeSelector.new( + @controller, initial_apqns.first.name, enable: enable_on_init + ) end # Checks whether the secure key can be generated @@ -262,10 +383,8 @@ def several_apqns? # # @return [Boolean] def validate_secure_key_generation - apqns = select_apqns? ? apqn_widget.value : [] - + apqns = selected_apqns command_error_message = controller.test_secure_key_generation(apqns: apqns) - return true unless command_error_message error = _("The secure key cannot be generated.\n") diff --git a/src/lib/y2partitioner/widgets/pervasive_key.rb b/src/lib/y2partitioner/widgets/pervasive_key.rb new file mode 100644 index 000000000..d351b1686 --- /dev/null +++ b/src/lib/y2partitioner/widgets/pervasive_key.rb @@ -0,0 +1,73 @@ +# Copyright (c) [2020-2024] 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 "yast" +require "cwm" + +module Y2Partitioner + module Widgets + # Widget to display the full verification pattern for the selected master key, in case the + # pattern is too long to be displayed at the corresponding selector + class PervasiveKey < CWM::ReplacePoint + # Internal widget showing the full verification pattern split into several lines + class Label < CWM::CustomWidget + # Constructor + def initialize(widget_id, key) + super() + self.widget_id = widget_id + @key = key + end + + # @macro seeCustomWidget + def contents + lines = [@key[0..33], " #{@key[34..-1]}"] + Left(Label(lines.join("\n"))) + end + end + + # Constructor + # + # @param initial_key [String] + def initialize(initial_key) + textdomain "storage" + + super(id: "pervasive_key", widget: widget_for(initial_key)) + end + + # Redraws the widget to show the new key, if needed + # + # @param key [String] + def refresh(key) + replace(widget_for(key)) + end + + private + + # Empty widget or multi-line label + def widget_for(key) + widget_id = "FullKey#{key}" + if key.size > 20 + Label.new(widget_id, key) + else + CWM::Empty.new(widget_id) + end + end + end + end +end diff --git a/src/lib/y2partitioner/widgets/pervasive_key_selector.rb b/src/lib/y2partitioner/widgets/pervasive_key_selector.rb new file mode 100644 index 000000000..0d153e0b4 --- /dev/null +++ b/src/lib/y2partitioner/widgets/pervasive_key_selector.rb @@ -0,0 +1,103 @@ +# Copyright (c) [2021] 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 "yast" +require "cwm" +require "y2storage/pbkd_function" + +module Y2Partitioner + module Widgets + # Master key selector for a {Y2Storage::Encryption} device using pervasive encryption + class PervasiveKeySelector < CWM::ComboBox + # Constructor + # + # @param apqns_by_key [Hash] + # @param initial_key [String] + # @param enable [Boolean] whether the widget should be enabled on init + def initialize(apqns_by_key, initial_key, enable: true) + super() + textdomain "storage" + + @apqns_by_key = apqns_by_key + @initial_key = initial_key + @enable_on_init = enable + end + + # @macro seeAbstractWidget + def label + _("Master Key Verification Pattern") + end + + def opt + [:notify] + end + + # Sets the initial value + def init + enable_on_init ? enable : disable + self.value = initial_key + end + + # @macro seeItemsSelection + def items + apqns_by_key.keys.sort.map { |k| [k, key_label(k)] } + end + + # @see #items + def key_label(key) + apqns = apqns_by_key[key] + if apqns.first.ep11? + # TRANSLATORS: this string is used to display a subset of a key verification pattern. + # %{start} is replaced by the first 10 characters of the pattern; %{ending} by the final 10. + key_string = format(_("%{start}...%{ending}"), start: key[0..9], ending: key[-10..-1]) + if apqns.size > 1 + # TRANSLATORS: Related to encryption using a CryptoExpress adapter in EP11 mode, %s is + # replaced by a subset of the key verification pattern + format(_("EP11: %s (several APQNs)"), key_string) + else + # TRANSLATORS: Related to encryption using a CryptoExpress adapter in EP11 mode. + # %{key} is replaced by a subset of the key verification pattern; + # %{apqn} by the name of an APQN + format(_("EP11: %{key} (APQN %{apqn})"), key: key_string, apqn: apqns.first.name) + end + elsif apqns.size > 1 + # TRANSLATORS: Related to encryption using a CryptoExpress adapter in CCA mode, %s is + # replaced by a key verification pattern + format(_("CCA: %s (several APQNs)"), key) + else + # TRANSLATORS: Related to encryption using a CryptoExpress adapter in CCA mode. + # %{key} is replaced by a key verification pattern; + # %{apqn} by the name of an APQN + format(_("CCA: %{key} (APQN %{apqn})"), key: key, apqn: apqns.first.name) + end + end + + private + + # @return [Boolean] whether the widget should be enabled on init + attr_reader :enable_on_init + + # @return [Hash] All APQNs objects grouped by their master key + attr_reader :apqns_by_key + + # @return [String] Master key initially selected + attr_reader :initial_key + end + end +end diff --git a/src/lib/y2partitioner/widgets/pervasive_key_type_selector.rb b/src/lib/y2partitioner/widgets/pervasive_key_type_selector.rb new file mode 100644 index 000000000..e6d3e5acd --- /dev/null +++ b/src/lib/y2partitioner/widgets/pervasive_key_type_selector.rb @@ -0,0 +1,121 @@ +# Copyright (c) [2024] 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 "yast" +require "cwm" + +module Y2Partitioner + module Widgets + # Widget to select the key variant for pervasive encryption + class PervasiveKeyTypeSelector < CWM::ReplacePoint + # Widget to select the key variant for a CCA key + class CcaTypeSelector < CWM::ComboBox + # Constructor + def initialize + super + textdomain "storage" + end + + # @macro seeAbstractWidget + def label + # TRANSLATORS: title of the widget to select the variant of a CCA key (AES data vs AES cipher) + _("Key Type") + end + + # @macro seeItemsSelection + def items + [ + ["CCA-AESCIPHER", _("CCA AESCIPHER (more secure)")], + ["CCA-AESDATA", _("CCA AESDATA (allows to export the keys)")] + ] + end + end + + # Constructor + def initialize(controller, initial_apqn, enable: true) + textdomain "storage" + + @controller = controller + @initial_apqn = initial_apqn + @enable_on_init = enable + + super(id: "key_type", widget: widget_for(initial_apqn)) + end + + # @macro seeAbstractWidget + def init + super + enable_on_init ? enable : disable + end + + # @macro seeAbstractWidget + def enable + super + + @widget.enable if @widget.respond_to?(:enable) + end + + # @macro seeAbstractWidget + def disable + super + + @widget.enable if @widget.respond_to?(:enable) + end + + # Redraws to show the appropriate widget + def refresh(apqn) + new_widget = widget_for(apqn) + replace(new_widget) if new_widget != @widget + end + + # Selected key variant + # + # @return [String] + def value + return "ep11" unless @widget.respond_to?(:value) + + @widget.value + end + + private + + # @return [Boolean] + attr_reader :enable_on_init + + # @return [Actions::Controllers::Encryption] + attr_reader :controller + + # @return [Y2Storage::EncryptionProcesses::Apqn] + attr_reader :initial_apqn + + # Empty widget or selector for the CCA key variant + def widget_for(apqn_name) + apqn = controller.find_apqn(apqn_name) + return cca_selector unless apqn.ep11? + + CWM::Empty.new("empty_#{widget_id}") + end + + # @return [CcaTypeSelector] + def cca_selector + @cca_selector ||= CcaTypeSelector.new + end + end + end +end diff --git a/src/lib/y2storage/encryption_method/pervasive_luks2.rb b/src/lib/y2storage/encryption_method/pervasive_luks2.rb index 84f0371e7..cc221b5af 100644 --- a/src/lib/y2storage/encryption_method/pervasive_luks2.rb +++ b/src/lib/y2storage/encryption_method/pervasive_luks2.rb @@ -51,6 +51,7 @@ def used_for?(encryption) # @see Base#available? def available? + # return true EncryptionProcesses::SecureKey.available? end diff --git a/src/lib/y2storage/encryption_processes/apqn.rb b/src/lib/y2storage/encryption_processes/apqn.rb index 80e9bcf64..d03d0b864 100644 --- a/src/lib/y2storage/encryption_processes/apqn.rb +++ b/src/lib/y2storage/encryption_processes/apqn.rb @@ -53,8 +53,7 @@ def online def read_apqns apqns_data.map do |data| name, type, mode, status, = data - - new(name, type, mode, status) + new(name, type, mode, status).tap(&:read_master_keys) end end @@ -84,10 +83,12 @@ def apqns_data # "01 CEX5C CCA-Coproc online 1\n" \ # "01.0001 CEX5C CCA-Coproc online 1\n" \ # "01.0004 CEX5C CCA-Coproc online 0\n" \ - # "01.0005 CEX5C CCA-Coproc online 0" + # "03 CEX7P EP11-Coproc online 0\n" \ + # "03.0003 CEX7P EP11-Coproc online 0" # # @return [String] def execute_lszcrypt + # return File.read("/home/ags/projects/yast/yast-storage-ng/pervasive/lszcrypt.out") Yast::Execute.locally!(LSZCRYPT, stdout: :capture) rescue Cheetah::ExecutionFailed "" @@ -119,6 +120,11 @@ def execute_lszcrypt # @return [String] e.g., "online", "offline" attr_reader :status + # Verification pattern of the master key configured on the APQN, if any + # + # @return [String, nil] + attr_reader :master_key_pattern + # Constructor # # @param name [String] @@ -146,6 +152,45 @@ def name def online? status == "online" end + + # Whether the coprocessor associated to this APQN is configured in EP11 mode + def ep11? + mode =~ /EP11/ + end + + # Fills {#master_key_pattern} with information from the current system + def read_master_keys + @master_key_pattern = master_key_from_file + end + + private + + # Verification pattern for the master key that is associated to this APQN and can be used to + # encrypt devices. Read from the corresponding file at /sys. + # + # @return [String, nil] Nil if no usable master key is configured or it could not be read + def master_key_from_file + content = File.read(master_key_file) + return nil if content&.empty? + + entry = content.lines.grep(master_key_regexp).first + return nil unless entry + + entry.split.last + rescue SystemCallError + nil + end + + # @see #master_key_from_file + def master_key_regexp + ep11? ? /^WK CUR: valid/ : /^AES CUR: valid/ + end + + # @see #master_key_from_file + def master_key_file + # return "/home/ags/projects/yast/yast-storage-ng/pervasive/cat.#{card}.#{domain}.out" + "/sys/bus/ap/devices/card#{card}/#{name}/mkvps" + end end end end diff --git a/src/lib/y2storage/encryption_processes/secure_key.rb b/src/lib/y2storage/encryption_processes/secure_key.rb index 84c41fe89..3c03655f2 100644 --- a/src/lib/y2storage/encryption_processes/secure_key.rb +++ b/src/lib/y2storage/encryption_processes/secure_key.rb @@ -323,6 +323,7 @@ def new_from_zkey(string) # # @return [Array<SecureKey>] def all + # output = File.read("/home/ags/projects/yast/yast-storage-ng/pervasive/zkey-list1.out") output = Yast::Execute.locally(ZKEY, "list", stdout: :capture) return [] if output&.empty?