Skip to content

Commit

Permalink
SSL improvements (bsc#1124992)
Browse files Browse the repository at this point in the history
- Display details in a scrollable widget
- Display hints how to install the certificate manually
- 4.1.18
  • Loading branch information
lslezak committed Feb 27, 2019
1 parent 91fd728 commit 9e29a43
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 62 deletions.
9 changes: 9 additions & 0 deletions 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

Expand Down
3 changes: 2 additions & 1 deletion package/yast2-registration.spec
Expand Up @@ -17,7 +17,7 @@


Name: yast2-registration
Version: 4.1.17
Version: 4.1.18
Release: 0

BuildRoot: %{_tmppath}/%{name}-%{version}-build
Expand Down Expand Up @@ -84,6 +84,7 @@ source (mirror) automatically.

%files
%defattr(-,root,root)
%{yast_ybindir}/*
%{yast_desktopdir}/*.desktop
%{yast_clientdir}/*.rb
%{yast_ydatadir}/registration
Expand Down
48 changes: 48 additions & 0 deletions 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
41 changes: 37 additions & 4 deletions src/data/registration/certificate_error.erb
Expand Up @@ -2,15 +2,48 @@
textdomain "registration"
%>
<%# dialog heading %>
<%# TRANSLATORS: dialog heading %>
<h2><%= _("Secure Connection Error") %></h2>

<p>
<%# 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) %>
</p>

<%# dialog sub-heading %>
<%# display a special help with description how to install the certificate manually %>
<% if error_code == SslErrorCodes::NO_LOCAL_ISSUER_CERTIFICATE %>

<p>
<%# TRANSLATORS: error description %>
<%= _("The issuer certificate cannot be found, "\
"it needs to be installed manually.") %>
</p>

<p>
<ul>
<li>
<%# TRANSLATORS: %{file} is replaced by the certificate file name %>
<%= _("Save the server certificate in PEM format to file %{file}") %
{ file: "<tt>#{h(SslCertificate::INSTSYS_SERVER_CERT_FILE)}</tt>" } %>
</li>

<li>
<%# TRANSLATORS: suggestion for user, followed by a command to run %>
<%= _("Run command:") %> <tt><%= h(import_command) %></tt>
</li>

<li>
<%# TRANSLATORS: suggestion to run the registration again %>
<%= _("Run registration again") %>
</li>
</ul>
</p>

<hr>

<% end %>
<%# TRANSLATORS: dialog sub-heading %>
<h3><%= _("Failed Certificate Details") %></h3>

<%= SslCertificateDetails.new(certificate).richtext_summary %>
2 changes: 1 addition & 1 deletion src/data/registration/certificate_summary.erb
Expand Up @@ -42,7 +42,7 @@ textdomain "registration"
<%# label followed by the certificate serial number (in HEX format, e.g. AB:CD:42:FF...) %>
<b><%= _("Serial Number: ") %></b><%= h(certificate.serial) %><br>
<b><%= _("SHA1 Fingerprint: ") %></b>
<%= h(certificate.fingerprint(::Registration::Fingerprint::SHA1).value) %>
<%= h(certificate.fingerprint(::Registration::Fingerprint::SHA1).value) %><br>
<b><%= _("SHA256 Fingerprint: ") %></b>
<%= h(certificate.fingerprint(::Registration::Fingerprint::SHA256).value) %>
</p>
52 changes: 15 additions & 37 deletions src/lib/registration/connect_helpers.rb
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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?
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
56 changes: 56 additions & 0 deletions 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
87 changes: 87 additions & 0 deletions 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

0 comments on commit 9e29a43

Please sign in to comment.