diff --git a/package/yast2-registration.changes b/package/yast2-registration.changes
index df5cb2ef1..d662a391a 100644
--- a/package/yast2-registration.changes
+++ b/package/yast2-registration.changes
@@ -1,3 +1,12 @@
+-------------------------------------------------------------------
+Mon Feb 25 12:32:35 UTC 2019 - lslezak@suse.cz
+
+- Better handle the SSL certificates signed by an uknown CA
+ (bsc#1124992)
+ - Display details in a scrollable widget
+ - Display hints how to install the certificate manually
+- 4.1.18
+
-------------------------------------------------------------------
Mon Feb 18 13:11:12 UTC 2019 - lslezak@suse.cz
diff --git a/package/yast2-registration.spec b/package/yast2-registration.spec
index 45d128a0a..628f65a3b 100644
--- a/package/yast2-registration.spec
+++ b/package/yast2-registration.spec
@@ -17,7 +17,7 @@
Name: yast2-registration
-Version: 4.1.17
+Version: 4.1.18
Release: 0
BuildRoot: %{_tmppath}/%{name}-%{version}-build
@@ -84,6 +84,7 @@ source (mirror) automatically.
%files
%defattr(-,root,root)
+%{yast_ybindir}/*
%{yast_desktopdir}/*.desktop
%{yast_clientdir}/*.rb
%{yast_ydatadir}/registration
diff --git a/src/bin/install_ssl_certificates b/src/bin/install_ssl_certificates
new file mode 100755
index 000000000..76302e877
--- /dev/null
+++ b/src/bin/install_ssl_certificates
@@ -0,0 +1,48 @@
+#! /usr/bin/env ruby
+
+# ------------------------------------------------------------------------------
+# Copyright (c) 2019 SUSE LLC
+#
+# 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.
+#
+# ------------------------------------------------------------------------------
+#
+
+# This is a helper script which to import the SSL certificates into inst-sys
+# during installation. (But is should work also in installed system.)
+#
+# It is intended for user convenience, this script just call the YaST
+# functions, it not used by YaST itself.
+
+require "English"
+require "yast"
+require "registration/ssl_certificate"
+
+dir = Registration::SslCertificate::INSTSYS_CERT_DIR
+if Dir.empty?(dir)
+ puts "ERROR: Empty #{dir} directory, put your SSL certificate there."
+ exit 1
+end
+
+# in installed system just call the update-ca-certificates script
+if ENV["YAST_IS_RUNNING"] != "instsys"
+ puts "Updating the installed SSL certificates..."
+ system("/usr/sbin/update-ca-certificates")
+ puts $CHILD_STATUS.success? ? "Done" : "Failed!"
+ exit $CHILD_STATUS.exitstatus
+end
+
+# import into the inst-sys
+puts "Updating the inst-sys SSL certificates..."
+if Registration::SslCertificate.update_instsys_ca
+ puts "Done"
+else
+ puts "Failed!"
+ exit 1
+end
diff --git a/src/data/registration/certificate_error.erb b/src/data/registration/certificate_error.erb
index 0321f731c..18f056d1a 100644
--- a/src/data/registration/certificate_error.erb
+++ b/src/data/registration/certificate_error.erb
@@ -2,15 +2,48 @@
textdomain "registration"
%>
-<%# dialog heading %>
+<%# TRANSLATORS: dialog heading %>
<%= _("Secure Connection Error") %>
- <%# label followed by error details %>
- <%= _("Details:") %> <%= _(OPENSSL_ERROR_MESSAGES[Storage::SSLErrors.instance.ssl_error_code]) %>
+ <%# TRANSLATORS: label followed by error details %>
+ <%= _("Details:") %> <%= h(@url) %>: <%= h(@msg) %>
-<%# dialog sub-heading %>
+<%# display a special help with description how to install the certificate manually %>
+<% if error_code == SslErrorCodes::NO_LOCAL_ISSUER_CERTIFICATE %>
+
+
+ <%# TRANSLATORS: error description %>
+ <%= _("The issuer certificate cannot be found, "\
+ "it needs to be installed manually.") %>
+
+
+
+
+ -
+ <%# TRANSLATORS: %{file} is replaced by the certificate file name %>
+ <%= _("Save the server certificate in PEM format to file %{file}") %
+ { file: "#{h(SslCertificate::INSTSYS_SERVER_CERT_FILE)}" } %>
+
+
+ -
+ <%# TRANSLATORS: suggestion for user, followed by a command to run %>
+ <%= _("Run command:") %> <%= h(import_command) %>
+
+
+ -
+ <%# TRANSLATORS: suggestion to run the registration again %>
+ <%= _("Run registration again") %>
+
+
+
+
+
+
+<% end %>
+
+<%# TRANSLATORS: dialog sub-heading %>
<%= _("Failed Certificate Details") %>
<%= SslCertificateDetails.new(certificate).richtext_summary %>
\ No newline at end of file
diff --git a/src/data/registration/certificate_summary.erb b/src/data/registration/certificate_summary.erb
index be0b713d0..4e0ad874d 100644
--- a/src/data/registration/certificate_summary.erb
+++ b/src/data/registration/certificate_summary.erb
@@ -42,7 +42,7 @@ textdomain "registration"
<%# label followed by the certificate serial number (in HEX format, e.g. AB:CD:42:FF...) %>
<%= _("Serial Number: ") %><%= h(certificate.serial) %>
<%= _("SHA1 Fingerprint: ") %>
- <%= h(certificate.fingerprint(::Registration::Fingerprint::SHA1).value) %>
+ <%= h(certificate.fingerprint(::Registration::Fingerprint::SHA1).value) %>
<%= _("SHA256 Fingerprint: ") %>
<%= h(certificate.fingerprint(::Registration::Fingerprint::SHA256).value) %>
diff --git a/src/lib/registration/connect_helpers.rb b/src/lib/registration/connect_helpers.rb
index 758d3e5c9..5bf814c8a 100644
--- a/src/lib/registration/connect_helpers.rb
+++ b/src/lib/registration/connect_helpers.rb
@@ -25,14 +25,16 @@
require "suse/connect"
require "ui/text_helpers"
-require "registration/helpers"
require "registration/exceptions"
-require "registration/storage"
+require "registration/helpers"
require "registration/smt_status"
require "registration/ssl_certificate"
require "registration/ssl_certificate_details"
-require "registration/url_helpers"
+require "registration/ssl_error_codes"
+require "registration/storage"
require "registration/ui/import_certificate_dialog"
+require "registration/ui/failed_certificate_popup"
+require "registration/url_helpers"
module Registration
# FIXME: change to a module and include it in the clients
@@ -41,11 +43,6 @@ class ConnectHelpers
extend ::UI::TextHelpers
extend Yast::I18n
- # openSSL error codes for which the import SSL certificate dialog is shown,
- # for the other error codes just the error message is displayed
- # (importing the certificate would not help)
- IMPORT_ERROR_CODES = UI::ImportCertificateDialog::OPENSSL_ERROR_MESSAGES.keys
-
textdomain "registration"
Yast.import "Mode"
@@ -191,9 +188,9 @@ def self.handle_ssl_error(error, certificate_imported)
expected_cert_type = Storage::Config.instance.reg_server_cert_fingerprint_type
# in non-AutoYast mode ask the user to import the certificate
- if !Yast::Mode.autoinst && cert && IMPORT_ERROR_CODES.include?(error_code)
+ if !Yast::Mode.autoinst && cert && SslErrorCodes::IMPORT_ERROR_CODES.include?(error_code)
# retry after successfull import
- return true if ask_import_ssl_certificate(cert)
+ return true if ask_import_ssl_certificate(cert, error_code)
# in AutoYast mode check whether the certificate fingerprint match
# the configured value (if present)
elsif Yast::Mode.autoinst && cert && expected_cert_type && !expected_cert_type.empty?
@@ -207,28 +204,21 @@ def self.handle_ssl_error(error, certificate_imported)
return true
end
- report_ssl_error(error.message, cert)
+ report_ssl_error(error.message, cert, error_code)
else
# error message
Yast::Report.Error(_("Received SSL Certificate does not match " \
"the expected certificate."))
end
else
- report_ssl_error(error.message, cert)
+ report_ssl_error(error.message, cert, error_code)
end
false
end
- def self.ssl_error_details(cert)
- return "" if cert.nil?
-
- details = SslCertificateDetails.new(cert)
- details.summary
- end
-
- def self.ask_import_ssl_certificate(cert)
+ def self.ask_import_ssl_certificate(cert, error_code)
# run the import dialog, check the user selection
- if UI::ImportCertificateDialog.run(cert) != :import
+ if UI::ImportCertificateDialog.run(cert, error_code) != :import
log.info "Certificate import rejected"
return false
end
@@ -270,20 +260,8 @@ def self.import_ssl_certificate(cert)
result
end
- def self.report_ssl_error(message, cert)
- # try to use a translatable message first, if not found then use
- # the original error message from openSSL
- error_code = Storage::SSLErrors.instance.ssl_error_code
- msg = UI::ImportCertificateDialog::OPENSSL_ERROR_MESSAGES[error_code]
- msg = msg ? _(msg) : Storage::SSLErrors.instance.ssl_error_msg
- msg = message if msg.nil? || msg.empty?
-
- url = UrlHelpers.registration_url || SUSE::Connect::YaST::DEFAULT_URL
- msg = url + ": " + msg # workaround after string freeze
-
- Yast::Report.Error(
- error_with_details(_("Secure connection error: %s") % msg, ssl_error_details(cert))
- )
+ def self.report_ssl_error(message, cert, error_code)
+ UI::FailedCertificatePopup.show(message, cert, error_code)
end
# Check whether the registration server provides the old NCC API,
@@ -359,7 +337,7 @@ def self.add_update_hint(error_msg)
error_msg << msg
end
- private_class_method :report_error, :error_with_details, :ssl_error_details,
- :import_ssl_certificate, :report_ssl_error, :check_smt_api, :handle_network_error
+ private_class_method :report_error, :error_with_details, :import_ssl_certificate,
+ :report_ssl_error, :check_smt_api, :handle_network_error
end
end
diff --git a/src/lib/registration/ssl_error_codes.rb b/src/lib/registration/ssl_error_codes.rb
new file mode 100644
index 000000000..60a25a48f
--- /dev/null
+++ b/src/lib/registration/ssl_error_codes.rb
@@ -0,0 +1,56 @@
+# ------------------------------------------------------------------------------
+# Copyright (c) 2019 SUSE LLC
+#
+# 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.
+#
+# ------------------------------------------------------------------------------
+#
+
+require "yast"
+
+module Registration
+ # This class defines constants and translations for the most common OpenSSL errors
+ # @see https://www.openssl.org/docs/apps/verify.html
+ # @see https://github.com/openssl/openssl/blob/2c75f03b39de2fa7d006bc0f0d7c58235a54d9bb/include/openssl/x509_vfy.h#L99-L189
+ class SslErrorCodes
+ extend Yast::I18n
+ textdomain "registration"
+
+ # "certificate has expired"
+ EXPIRED = 10
+ # "self signed certificate"
+ SELF_SIGNED_CERT = 18
+ # "self signed certificate in certificate chain"
+ SELF_SIGNED_CERT_IN_CHAIN = 19
+ # "unable to get local issuer certificate"
+ NO_LOCAL_ISSUER_CERTIFICATE = 20
+
+ # openSSL error codes for which the import SSL certificate dialog is shown,
+ # for the other error codes just the error message is displayed
+ # (importing the certificate would not help)
+ IMPORT_ERROR_CODES = [
+ SELF_SIGNED_CERT,
+ SELF_SIGNED_CERT_IN_CHAIN
+ ].freeze
+
+ # error code => translatable error message
+ # @note the text messages need to be translated at runtime via _() call
+ # @note we do not translate every possible OpenSSL error message, just the most common ones
+ OPENSSL_ERROR_MESSAGES = {
+ # TRANSLATORS: SSL error message
+ EXPIRED => N_("Certificate has expired"),
+ # TRANSLATORS: SSL error message
+ SELF_SIGNED_CERT => N_("Self signed certificate"),
+ # TRANSLATORS: SSL error message
+ SELF_SIGNED_CERT_IN_CHAIN => N_("Self signed certificate in certificate chain"),
+ # TRANSLATORS: SSL error message
+ NO_LOCAL_ISSUER_CERTIFICATE => N_("Unable to get local issuer certificate")
+ }.freeze
+ end
+end
diff --git a/src/lib/registration/ui/failed_certificate_popup.rb b/src/lib/registration/ui/failed_certificate_popup.rb
new file mode 100644
index 000000000..1feed4ef4
--- /dev/null
+++ b/src/lib/registration/ui/failed_certificate_popup.rb
@@ -0,0 +1,87 @@
+# ------------------------------------------------------------------------------
+# Copyright (c) 2019 SUSE LLC
+#
+# 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.
+#
+# ------------------------------------------------------------------------------
+#
+
+require "erb"
+require "yast"
+
+require "registration/helpers"
+require "registration/ssl_certificate"
+require "registration/ssl_certificate_details"
+require "registration/ssl_error_codes"
+require "registration/url_helpers"
+
+module Registration
+ module UI
+ # This class displays a popup with a SSL certificate error
+ class FailedCertificatePopup
+ include Yast::I18n
+ include ERB::Util
+
+ attr_accessor :certificate, :error_code, :message
+
+ Yast.import "Report"
+ Yast.import "Stage"
+ Yast.import "Directory"
+
+ # create a display the error popup
+ # @param cert [Registration::SslCertitificate] certificate to display
+ def self.show(msg, cert, error_code)
+ popup = FailedCertificatePopup.new(msg, cert, error_code)
+ popup.show
+ end
+
+ # the constructor
+ # @param msg [String,nil] the original OpenSSL error message
+ # (used as a fallback when a translated message is not found)
+ # @param cert [Registration::SslCertitificate] certificate to display
+ # @param error_code [Integer] OpenSSL error code
+ def initialize(msg, cert, error_code)
+ textdomain "registration"
+
+ @certificate = cert
+ @message = msg
+ @error_code = error_code
+ end
+
+ # display the popup and wait for clicking the [OK] button
+ def show
+ # this uses a RichText message format
+ Yast::Report.LongError(ssl_error_message)
+ end
+
+ private
+
+ # Build the message displayed in the popup
+ # @return [String] message in RichText format
+ def ssl_error_message
+ # try to use a translatable message first, if not found then use
+ # the original error message from openSSL
+ @url = UrlHelpers.registration_url || SUSE::Connect::YaST::DEFAULT_URL
+ @msg = _(SslErrorCodes::OPENSSL_ERROR_MESSAGES[error_code]) || message
+
+ Helpers.render_erb_template("certificate_error.erb", binding)
+ end
+
+ # the command which needs to be called to import the SSL certificate
+ # @return [String] command
+ def import_command
+ if Yast::Stage.initial
+ File.join(Yast::Directory.bindir, "install_ssl_certificates")
+ else
+ "update-ca-certificates"
+ end
+ end
+ end
+ end
+end
diff --git a/src/lib/registration/ui/import_certificate_dialog.rb b/src/lib/registration/ui/import_certificate_dialog.rb
index fbf3b3035..7287cefb9 100644
--- a/src/lib/registration/ui/import_certificate_dialog.rb
+++ b/src/lib/registration/ui/import_certificate_dialog.rb
@@ -1,31 +1,23 @@
+require "erb"
require "yast"
require "registration/fingerprint"
require "registration/ssl_certificate_details"
+require "registration/ssl_error_codes"
+require "registration/url_helpers"
module Registration
module UI
# this class displays and runs the dialog for importing a SSL certificate
class ImportCertificateDialog
+ include ERB::Util
include Yast::Logger
include Yast::I18n
extend Yast::I18n
include Yast::UIShortcuts
- attr_accessor :certificate
-
- # error code => translatable error message
- # @see https://www.openssl.org/docs/apps/verify.html
- # @note the text messages need to be translated at runtime via _() call
- OPENSSL_ERROR_MESSAGES = {
- # SSL error message
- 10 => N_("Certificate has expired"),
- # SSL error message
- 18 => N_("Self signed certificate"),
- # SSL error message
- 19 => N_("Self signed certificate in certificate chain")
- }.freeze
+ attr_accessor :certificate, :error_code
Yast.import "UI"
Yast.import "Label"
@@ -33,16 +25,17 @@ class ImportCertificateDialog
# create a new dialog for importing a SSL certificate and run it
# @param cert [Registration::SslCertitificate] certificate to display
# @return [Symbol] user input (:import, :cancel)
- def self.run(cert)
- dialog = ImportCertificateDialog.new(cert)
+ def self.run(cert, error_code)
+ dialog = ImportCertificateDialog.new(cert, error_code)
dialog.run
end
# the constructor
# @param cert [Registration::SslCertitificate] certificate to display
- def initialize(cert)
+ def initialize(cert, error_code)
textdomain "registration"
@certificate = cert
+ @error_code = error_code
end
# display the dialog and wait for a button click
@@ -89,7 +82,7 @@ def import_dialog_content
hide_help = displayinfo["TextMode"] && displayinfo["Width"] < 105
window_height = displayinfo["Height"]
- window_height = 25 if window_height > 25
+ window_height = 26 if window_height > 26
HBox(
VSpacing(window_height),
@@ -111,8 +104,14 @@ def handle_dialog
# render Richtext description with the certificate details
def certificate_description
+ msg = _(SslErrorCodes::OPENSSL_ERROR_MESSAGES[error_code])
+ url = UrlHelpers.registration_url || SUSE::Connect::YaST::DEFAULT_URL
details = SslCertificateDetails.new(certificate)
- details.richtext_summary
+
+ "#{_("Secure Connection Error")}
\n" \
+ "#{_("Details:")} #{h(url)}: #{h(msg)}
\n" \
+ "#{_("Failed Certificate Details")}
\n" +
+ details.richtext_summary
end
# inline help text displayed in the import dialog
diff --git a/test/import_certificate_dialog_test.rb b/test/import_certificate_dialog_test.rb
index 12609a20e..e12778d97 100644
--- a/test/import_certificate_dialog_test.rb
+++ b/test/import_certificate_dialog_test.rb
@@ -5,6 +5,7 @@
describe Registration::UI::ImportCertificateDialog do
describe ".run" do
it "displays the certificate details and returns the user input" do
+ allow(Registration::UrlHelpers).to receive(:registration_url)
# generic UI mocks
expect(Yast::UI).to receive(:CloseDialog)
# "Cancel" button must be the default
@@ -25,7 +26,11 @@
end
cert = Registration::SslCertificate.load_file(fixtures_file("test.pem"))
- expect(Registration::UI::ImportCertificateDialog.run(cert)).to eq(:import)
+ expect(
+ Registration::UI::ImportCertificateDialog.run(
+ cert, Registration::SslErrorCodes::SELF_SIGNED_CERT
+ )
+ ).to eq(:import)
end
end
end
diff --git a/test/registration/ui/failed_certificate_popup_test.rb b/test/registration/ui/failed_certificate_popup_test.rb
new file mode 100644
index 000000000..be48313dc
--- /dev/null
+++ b/test/registration/ui/failed_certificate_popup_test.rb
@@ -0,0 +1,67 @@
+#!/usr/bin/env rspec
+# ------------------------------------------------------------------------------
+# Copyright (c) 2018 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.
+# ------------------------------------------------------------------------------
+
+require_relative "../../spec_helper"
+require "registration/ui/failed_certificate_popup"
+
+describe Registration::UI::FailedCertificatePopup do
+
+ let(:ssl_error) do
+ "SSL_connect returned=1 errno=0 state=error: certificate verify failed " \
+ "(unable to get local issuer certificate)"
+ end
+
+ let(:error_code) { Registration::SslErrorCodes::NO_LOCAL_ISSUER_CERTIFICATE }
+
+ let(:ssl_cert) do
+ Registration::SslCertificate.load_file(fixtures_file("test.pem"))
+ end
+
+ subject do
+ Registration::UI::FailedCertificatePopup.new(ssl_error, ssl_cert, error_code)
+ end
+
+ before do
+ allow(Yast::Report).to receive(:LongError)
+ allow(Yast::Stage).to receive(:initial).and_return(false)
+ end
+
+ # the instance method
+ describe "#show" do
+ it "displays the certificate details" do
+ expect(Yast::Report).to receive(:LongError).with(/Organization \(O\): .*WebYaST/)
+ subject.show
+ end
+
+ it "displays the certificate import hints" do
+ expect(Yast::Report).to receive(:LongError)
+ .with(/Save the server certificate in PEM format to file/)
+ subject.show
+ end
+
+ it "suggests to call the install_ssl_certificates script in inst-sys" do
+ expect(Yast::Stage).to receive(:initial).and_return(true)
+ expect(Yast::Report).to receive(:LongError)
+ .with(/install_ssl_certificates/)
+ subject.show
+ end
+ end
+
+ # the class method
+ describe ".show" do
+ it "displays the failed certificate popup" do
+ expect_any_instance_of(Registration::UI::FailedCertificatePopup).to receive(:show)
+ Registration::UI::FailedCertificatePopup.show(ssl_error, ssl_cert, error_code)
+ end
+ end
+end
diff --git a/test/registration/ui/not_installed_products_dialog.rb b/test/registration/ui/not_installed_products_dialog_test.rb
similarity index 100%
rename from test/registration/ui/not_installed_products_dialog.rb
rename to test/registration/ui/not_installed_products_dialog_test.rb