79e064f384
- Replace pyOpenSSL with python-cryptography
703 lines
21 KiB
Diff
703 lines
21 KiB
Diff
From 68157f21fe8051ebd7eace11012738d8d91a1812 Mon Sep 17 00:00:00 2001
|
|
From: Tomas Jelinek <tojeline@redhat.com>
|
|
Date: Tue, 2 Mar 2021 14:47:27 +0100
|
|
Subject: [PATCH 1/2] squash bz1927404: replace pyOpenSSL with
|
|
python-cryptography
|
|
|
|
python-cryptography requires new mypy
|
|
|
|
improve TLS certificate verification
|
|
|
|
Library methods are now used instead of running openssl processes. That
|
|
enabled support for Elliptic Curve certificates.
|
|
|
|
cleanup dependencies in spec file
|
|
---
|
|
.gitlab-ci.yml | 6 +-
|
|
README.md | 2 +-
|
|
mypy.ini | 7 +-
|
|
pcs.spec.in | 5 +-
|
|
pcs/common/ssl.py | 108 +++++++++++++++++-------------
|
|
pcs/daemon/ssl.py | 48 ++-----------
|
|
pcs/pcsd.py | 19 +++---
|
|
pcs/utils.py | 32 ---------
|
|
pcs_test/tier0/daemon/test_ssl.py | 64 +++++++++---------
|
|
pcsd/pcs.rb | 39 ++++-------
|
|
pcsd/remote.rb | 2 +-
|
|
requirements.txt | 2 +-
|
|
test/centos8/Dockerfile | 2 +-
|
|
test/fedora31/Dockerfile | 2 +-
|
|
test/fedora32/Dockerfile | 2 +-
|
|
15 files changed, 135 insertions(+), 205 deletions(-)
|
|
|
|
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
|
|
index 4cb4c14b..72668787 100644
|
|
--- a/.gitlab-ci.yml
|
|
+++ b/.gitlab-ci.yml
|
|
@@ -46,12 +46,12 @@ pylint:
|
|
script:
|
|
- "dnf install -y
|
|
python3
|
|
+ python3-cryptography
|
|
python3-dateutil
|
|
python3-distro
|
|
python3-lxml
|
|
python3-pip
|
|
python3-pycurl
|
|
- python3-pyOpenSSL
|
|
python3-pyparsing
|
|
findutils
|
|
make
|
|
@@ -66,12 +66,12 @@ mypy:
|
|
script:
|
|
- "dnf install -y
|
|
python3
|
|
+ python3-cryptography
|
|
python3-dateutil
|
|
python3-distro
|
|
python3-lxml
|
|
python3-pip
|
|
python3-pycurl
|
|
- python3-pyOpenSSL
|
|
python3-pyparsing
|
|
git
|
|
make
|
|
@@ -111,12 +111,12 @@ python_tier0_tests:
|
|
- "dnf install -y
|
|
make
|
|
python3
|
|
+ python3-cryptography
|
|
python3-dateutil
|
|
python3-distro
|
|
python3-lxml
|
|
python3-pip
|
|
python3-pycurl
|
|
- python3-pyOpenSSL
|
|
python3-pyparsing
|
|
which
|
|
"
|
|
diff --git a/README.md b/README.md
|
|
index fe6eeed6..a0c01c02 100644
|
|
--- a/README.md
|
|
+++ b/README.md
|
|
@@ -30,7 +30,7 @@ These are the runtime dependencies of pcs and pcsd:
|
|
* python3-lxml
|
|
* python3-pycurl
|
|
* python3-setuptools
|
|
-* python3-pyOpenSSL (python3-openssl)
|
|
+* python3-cryptography
|
|
* python3-pyparsing
|
|
* python3-tornado 6.1.0+
|
|
* python dataclasses (`pip install dataclasses`; required only for python 3.6,
|
|
diff --git a/mypy.ini b/mypy.ini
|
|
index 6d3d2ff9..e3198530 100644
|
|
--- a/mypy.ini
|
|
+++ b/mypy.ini
|
|
@@ -48,6 +48,10 @@ disallow_untyped_defs = True
|
|
# this is a temporary solution for legacy code
|
|
disallow_untyped_defs = False
|
|
|
|
+[mypy-pcs.common.ssl]
|
|
+disallow_untyped_defs = True
|
|
+disallow_untyped_calls = True
|
|
+
|
|
[mypy-pcs.common.types]
|
|
disallow_untyped_defs = True
|
|
disallow_untyped_calls = True
|
|
@@ -122,9 +126,6 @@ ignore_missing_imports = True
|
|
[mypy-distro]
|
|
ignore_missing_imports = True
|
|
|
|
-[mypy-OpenSSL]
|
|
-ignore_missing_imports = True
|
|
-
|
|
[mypy-pyagentx.*]
|
|
ignore_errors = True
|
|
|
|
diff --git a/pcs.spec.in b/pcs.spec.in
|
|
index db66c5b0..610fad50 100644
|
|
--- a/pcs.spec.in
|
|
+++ b/pcs.spec.in
|
|
@@ -139,6 +139,7 @@ BuildRequires: python3-setuptools_scm
|
|
|
|
BuildRequires: python3-devel
|
|
# for tier0 tests
|
|
+BuildRequires: python3-cryptography
|
|
BuildRequires: python3-pyparsing
|
|
|
|
# gcc for compiling custom rubygems
|
|
@@ -171,6 +172,7 @@ Requires: platform-python
|
|
Requires: platform-python-setuptools
|
|
%endif
|
|
|
|
+Requires: python3-cryptography
|
|
Requires: python3-lxml
|
|
Requires: python3-pycurl
|
|
Requires: python3-pyparsing
|
|
@@ -190,9 +192,6 @@ Requires: ruby >= 2.2.0
|
|
Requires: rubygems
|
|
# for killall
|
|
Requires: psmisc
|
|
-# for working with certificates (validation etc.)
|
|
-Requires: openssl
|
|
-Requires: python3-pyOpenSSL
|
|
# cluster stack and related packages
|
|
Requires: pacemaker >= 2.0.0
|
|
Requires: corosync >= 3.0
|
|
diff --git a/pcs/common/ssl.py b/pcs/common/ssl.py
|
|
index 852fea80..74ddd4ec 100644
|
|
--- a/pcs/common/ssl.py
|
|
+++ b/pcs/common/ssl.py
|
|
@@ -1,45 +1,63 @@
|
|
-import time
|
|
-from OpenSSL import crypto
|
|
-
|
|
-
|
|
-def cert_date_format(timestamp):
|
|
- return str.encode(time.strftime("%Y%m%d%H%M%SZ", time.gmtime(timestamp)))
|
|
-
|
|
-
|
|
-def generate_key(length=3072):
|
|
- key = crypto.PKey()
|
|
- key.generate_key(crypto.TYPE_RSA, length)
|
|
- return key
|
|
-
|
|
-
|
|
-def generate_cert(key, server_name):
|
|
- now = time.time()
|
|
- cert = crypto.X509()
|
|
-
|
|
- subject = cert.get_subject()
|
|
- subject.countryName = "US"
|
|
- subject.stateOrProvinceName = "MN"
|
|
- subject.localityName = "Minneapolis"
|
|
- subject.organizationName = "pcsd"
|
|
- subject.organizationalUnitName = "pcsd"
|
|
- subject.commonName = server_name
|
|
-
|
|
- cert.set_version(2)
|
|
- cert.set_serial_number(int(now * 1000))
|
|
- cert.set_notBefore(cert_date_format(now))
|
|
- cert.set_notAfter(
|
|
- cert_date_format(now + 60 * 60 * 24 * 365 * 10)
|
|
- ) # 10 years
|
|
- cert.set_issuer(subject)
|
|
- cert.set_pubkey(key)
|
|
- cert.sign(key, "sha256")
|
|
-
|
|
- return cert
|
|
-
|
|
-
|
|
-def dump_cert(certificate):
|
|
- return crypto.dump_certificate(crypto.FILETYPE_PEM, certificate)
|
|
-
|
|
-
|
|
-def dump_key(key):
|
|
- return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
|
|
+import datetime
|
|
+import ssl
|
|
+from typing import List
|
|
+
|
|
+from cryptography import x509
|
|
+from cryptography.x509.oid import NameOID
|
|
+from cryptography.hazmat.backends import default_backend
|
|
+from cryptography.hazmat.primitives import hashes, serialization
|
|
+from cryptography.hazmat.primitives.asymmetric import rsa
|
|
+
|
|
+
|
|
+def check_cert_key(cert_path: str, key_path: str) -> List[str]:
|
|
+ errors = []
|
|
+ try:
|
|
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
+ ssl_context.load_cert_chain(cert_path, key_path)
|
|
+ except ssl.SSLError as e:
|
|
+ errors.append(f"SSL certificate does not match the key: {e}")
|
|
+ except EnvironmentError as e:
|
|
+ errors.append(f"Unable to load SSL certificate and/or key: {e}")
|
|
+ return errors
|
|
+
|
|
+
|
|
+def generate_key(length: int = 3072) -> rsa.RSAPrivateKeyWithSerialization:
|
|
+ return rsa.generate_private_key(
|
|
+ public_exponent=65537, key_size=length, backend=default_backend()
|
|
+ )
|
|
+
|
|
+
|
|
+def generate_cert(key: rsa.RSAPrivateKey, server_name: str) -> x509.Certificate:
|
|
+ now = datetime.datetime.utcnow()
|
|
+ subject = x509.Name(
|
|
+ [
|
|
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
|
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "MN"),
|
|
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Minneapolis"),
|
|
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "pcsd"),
|
|
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "pcsd"),
|
|
+ x509.NameAttribute(NameOID.COMMON_NAME, server_name),
|
|
+ ]
|
|
+ )
|
|
+ return (
|
|
+ x509.CertificateBuilder()
|
|
+ .subject_name(subject)
|
|
+ .issuer_name(subject)
|
|
+ .public_key(key.public_key())
|
|
+ .serial_number(int(now.timestamp() * 1000))
|
|
+ .not_valid_before(now)
|
|
+ .not_valid_after(now + datetime.timedelta(days=3650))
|
|
+ .sign(key, hashes.SHA256(), default_backend())
|
|
+ )
|
|
+
|
|
+
|
|
+def dump_cert(certificate: x509.Certificate) -> bytes:
|
|
+ return certificate.public_bytes(serialization.Encoding.PEM)
|
|
+
|
|
+
|
|
+def dump_key(key: rsa.RSAPrivateKeyWithSerialization) -> bytes:
|
|
+ return key.private_bytes(
|
|
+ serialization.Encoding.PEM,
|
|
+ serialization.PrivateFormat.TraditionalOpenSSL,
|
|
+ serialization.NoEncryption(),
|
|
+ )
|
|
diff --git a/pcs/daemon/ssl.py b/pcs/daemon/ssl.py
|
|
index 40cca314..43865631 100644
|
|
--- a/pcs/daemon/ssl.py
|
|
+++ b/pcs/daemon/ssl.py
|
|
@@ -1,9 +1,8 @@
|
|
import os
|
|
import ssl
|
|
|
|
-from OpenSSL import crypto, SSL
|
|
-
|
|
from pcs.common.ssl import (
|
|
+ check_cert_key,
|
|
dump_cert,
|
|
dump_key,
|
|
generate_cert,
|
|
@@ -11,53 +10,15 @@ from pcs.common.ssl import (
|
|
)
|
|
|
|
|
|
-def check_cert_key(cert_path, key_path):
|
|
- errors = []
|
|
-
|
|
- def load(load_ssl_file, label, path):
|
|
- try:
|
|
- with open(path) as ssl_file:
|
|
- return load_ssl_file(crypto.FILETYPE_PEM, ssl_file.read())
|
|
- except EnvironmentError as e:
|
|
- errors.append(f"Unable to read SSL {label} '{path}': '{e}'")
|
|
- except crypto.Error as e:
|
|
- msg = ""
|
|
- if e.args and e.args[0] and e.args[0][0]:
|
|
- msg = f": '{':'.join(e.args[0][0])}'"
|
|
- errors.append(f"Invalid SSL {label} '{path}'{msg}")
|
|
-
|
|
- cert = load(crypto.load_certificate, "certificate", cert_path)
|
|
- key = load(crypto.load_privatekey, "key", key_path)
|
|
-
|
|
- if errors:
|
|
- return errors
|
|
-
|
|
- try:
|
|
- context = SSL.Context(SSL.TLSv1_METHOD)
|
|
- context.use_privatekey(key)
|
|
- context.use_certificate(cert)
|
|
- except SSL.Error as e:
|
|
- errors.append(f"Unable to load SSL certificate and/or key: {e}")
|
|
- # If we cannot load the files, do not confuse users with other error
|
|
- # messages.
|
|
- return errors
|
|
- try:
|
|
- context.check_privatekey()
|
|
- except (crypto.Error, SSL.Error) as e:
|
|
- errors.append(f"SSL certificate does not match the key: {e}")
|
|
-
|
|
- return errors
|
|
-
|
|
-
|
|
-def open_ssl_file_to_rewrite(path):
|
|
+def _open_ssl_file_to_rewrite(path):
|
|
return os.fdopen(os.open(path, os.O_CREAT | os.O_WRONLY, 0o600), "wb")
|
|
|
|
|
|
def regenerate_cert_key(server_name, cert_path, key_path, key_length=None):
|
|
key = generate_key(key_length) if key_length else generate_key()
|
|
- with open_ssl_file_to_rewrite(cert_path) as cert_file:
|
|
+ with _open_ssl_file_to_rewrite(cert_path) as cert_file:
|
|
cert_file.write(dump_cert(generate_cert(key, server_name)))
|
|
- with open_ssl_file_to_rewrite(key_path) as key_file:
|
|
+ with _open_ssl_file_to_rewrite(key_path) as key_file:
|
|
key_file.write(dump_key(key))
|
|
|
|
|
|
@@ -102,7 +63,6 @@ class PcsdSSL:
|
|
self.__ck_pair = CertKeyPair(cert_location, key_location)
|
|
|
|
def create_context(self) -> ssl.SSLContext:
|
|
- # pylint: disable=no-member
|
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
ssl_context.set_ciphers(self.__ssl_ciphers)
|
|
ssl_context.options = self.__ssl_options
|
|
diff --git a/pcs/pcsd.py b/pcs/pcsd.py
|
|
index f3e6bca3..d5ddb443 100644
|
|
--- a/pcs/pcsd.py
|
|
+++ b/pcs/pcsd.py
|
|
@@ -5,6 +5,7 @@ import sys
|
|
from pcs import settings
|
|
from pcs import utils
|
|
from pcs.cli.common.errors import CmdLineInputError
|
|
+import pcs.common.ssl
|
|
|
|
|
|
def pcsd_certkey(lib, argv, modifiers):
|
|
@@ -21,13 +22,13 @@ def pcsd_certkey(lib, argv, modifiers):
|
|
keyfile = argv[1]
|
|
|
|
try:
|
|
- with open(certfile, "r") as myfile:
|
|
+ with open(certfile, "rb") as myfile:
|
|
cert = myfile.read()
|
|
- with open(keyfile, "r") as myfile:
|
|
+ with open(keyfile, "rb") as myfile:
|
|
key = myfile.read()
|
|
except IOError as e:
|
|
utils.err(e)
|
|
- errors = utils.verify_cert_key_pair(cert, key)
|
|
+ errors = pcs.common.ssl.check_cert_key(certfile, keyfile)
|
|
if errors:
|
|
for err in errors:
|
|
utils.err(err, False)
|
|
@@ -43,12 +44,12 @@ def pcsd_certkey(lib, argv, modifiers):
|
|
|
|
try:
|
|
try:
|
|
- os.chmod(settings.pcsd_cert_location, 0o700)
|
|
+ os.chmod(settings.pcsd_cert_location, 0o600)
|
|
except OSError: # If the file doesn't exist, we don't care
|
|
pass
|
|
|
|
try:
|
|
- os.chmod(settings.pcsd_key_location, 0o700)
|
|
+ os.chmod(settings.pcsd_key_location, 0o600)
|
|
except OSError: # If the file doesn't exist, we don't care
|
|
pass
|
|
|
|
@@ -56,9 +57,9 @@ def pcsd_certkey(lib, argv, modifiers):
|
|
os.open(
|
|
settings.pcsd_cert_location,
|
|
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
|
|
- 0o700,
|
|
+ 0o600,
|
|
),
|
|
- "w",
|
|
+ "wb",
|
|
) as myfile:
|
|
myfile.write(cert)
|
|
|
|
@@ -66,9 +67,9 @@ def pcsd_certkey(lib, argv, modifiers):
|
|
os.open(
|
|
settings.pcsd_key_location,
|
|
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
|
|
- 0o700,
|
|
+ 0o600,
|
|
),
|
|
- "w",
|
|
+ "wb",
|
|
) as myfile:
|
|
myfile.write(key)
|
|
|
|
diff --git a/pcs/utils.py b/pcs/utils.py
|
|
index 97a04787..59d1b66e 100644
|
|
--- a/pcs/utils.py
|
|
+++ b/pcs/utils.py
|
|
@@ -2105,38 +2105,6 @@ def is_iso8601_date(var):
|
|
return retVal == 0
|
|
|
|
|
|
-def verify_cert_key_pair(cert, key):
|
|
- """
|
|
- Commandline options: no options
|
|
- """
|
|
- errors = []
|
|
- cert_modulus = ""
|
|
- key_modulus = ""
|
|
-
|
|
- output, retval = run(
|
|
- ["/usr/bin/openssl", "x509", "-modulus", "-noout"],
|
|
- string_for_stdin=cert,
|
|
- )
|
|
- if retval != 0:
|
|
- errors.append("Invalid certificate: {0}".format(output.strip()))
|
|
- else:
|
|
- cert_modulus = output.strip()
|
|
-
|
|
- output, retval = run(
|
|
- ["/usr/bin/openssl", "rsa", "-modulus", "-noout"], string_for_stdin=key
|
|
- )
|
|
- if retval != 0:
|
|
- errors.append("Invalid key: {0}".format(output.strip()))
|
|
- else:
|
|
- key_modulus = output.strip()
|
|
-
|
|
- if not errors and cert_modulus and key_modulus:
|
|
- if cert_modulus != key_modulus:
|
|
- errors.append("Certificate does not match the key")
|
|
-
|
|
- return errors
|
|
-
|
|
-
|
|
def err(errorText, exit_after_error=True):
|
|
sys.stderr.write("Error: %s\n" % errorText)
|
|
if exit_after_error:
|
|
diff --git a/pcs_test/tier0/daemon/test_ssl.py b/pcs_test/tier0/daemon/test_ssl.py
|
|
index e80f7a30..2b2edd36 100644
|
|
--- a/pcs_test/tier0/daemon/test_ssl.py
|
|
+++ b/pcs_test/tier0/daemon/test_ssl.py
|
|
@@ -1,8 +1,7 @@
|
|
import os
|
|
+import ssl
|
|
from unittest import mock, TestCase
|
|
|
|
-from OpenSSL import SSL
|
|
-
|
|
from pcs_test.tools.misc import get_tmp_dir
|
|
|
|
from pcs.daemon.ssl import PcsdSSL, CertKeyPair, SSLCertKeyException
|
|
@@ -19,19 +18,6 @@ class SslFilesMixin:
|
|
self.ssl_dir = get_tmp_dir("tier0_daemon_ssl")
|
|
self.cert_path = os.path.join(self.ssl_dir.name, "daemon.cert")
|
|
self.key_path = os.path.join(self.ssl_dir.name, "daemon.key")
|
|
- # various versions of OpenSSL / PyOpenSSL emit different messages
|
|
- self.DAMAGED_SSL_FILES_ERRORS_1 = (
|
|
- f"Invalid SSL certificate '{self.cert_path}':"
|
|
- " 'PEM routines:PEM_read_bio:no start line'",
|
|
- f"Invalid SSL key '{self.key_path}':"
|
|
- " 'PEM routines:PEM_read_bio:no start line'",
|
|
- )
|
|
- self.DAMAGED_SSL_FILES_ERRORS_2 = (
|
|
- f"Invalid SSL certificate '{self.cert_path}':"
|
|
- " 'PEM routines:get_name:no start line'",
|
|
- f"Invalid SSL key '{self.key_path}':"
|
|
- " 'PEM routines:get_name:no start line'",
|
|
- )
|
|
|
|
def tearDown(self):
|
|
# pylint cannot possibly know this is being mixed into TestCase classes
|
|
@@ -56,21 +42,31 @@ class Pair(SslFilesMixin, TestCase):
|
|
|
|
def test_error_if_files_with_bad_content(self):
|
|
self.damage_ssl_files()
|
|
- self.assertTrue(
|
|
- self.pair.check()
|
|
- in [
|
|
- list(self.DAMAGED_SSL_FILES_ERRORS_1),
|
|
- list(self.DAMAGED_SSL_FILES_ERRORS_2),
|
|
- ]
|
|
+ errors = self.pair.check()
|
|
+ self.assertEqual(len(errors), 1)
|
|
+ self.assertRegex(
|
|
+ errors[0],
|
|
+ r"^SSL certificate does not match the key: "
|
|
+ r"\[SSL\] PEM lib \(_ssl\.c:\d+\)",
|
|
)
|
|
|
|
- @mock.patch("pcs.daemon.ssl.SSL.Context.use_privatekey")
|
|
- def test_error_if_short_key(self, mock_use_key):
|
|
- mock_use_key.side_effect = SSL.Error("reason")
|
|
+ @mock.patch("pcs.daemon.ssl.ssl.SSLContext.load_cert_chain")
|
|
+ def test_error_if_short_key(self, mock_load_cert_chain):
|
|
+ mock_load_cert_chain.side_effect = ssl.SSLError(
|
|
+ # These are the real args of the exception.
|
|
+ 336245135,
|
|
+ "[SSL: EE_KEY_TOO_SMALL] ee key too small (_ssl.c:3542)",
|
|
+ )
|
|
+ # 512 cannot be used as we would get an error from FIPS and 1024 is
|
|
+ # long enough. So a mock must be used.
|
|
self.pair.regenerate(SERVER_NAME, 1024)
|
|
errors = self.pair.check()
|
|
self.assertEqual(
|
|
- errors, ["Unable to load SSL certificate and/or key: reason"]
|
|
+ errors,
|
|
+ [
|
|
+ "SSL certificate does not match the key: "
|
|
+ "[SSL: EE_KEY_TOO_SMALL] ee key too small (_ssl.c:3542)",
|
|
+ ],
|
|
)
|
|
|
|
def test_error_if_cert_does_not_match_key(self):
|
|
@@ -83,8 +79,10 @@ class Pair(SslFilesMixin, TestCase):
|
|
|
|
errors = self.pair.check()
|
|
self.assertEqual(len(errors), 1)
|
|
- self.assertTrue(
|
|
- errors[0].startswith("SSL certificate does not match the key:")
|
|
+ self.assertRegex(
|
|
+ errors[0],
|
|
+ r"SSL certificate does not match the key: "
|
|
+ r"\[X509: KEY_VALUES_MISMATCH\] key values mismatch \(_ssl\.c:\d+\)",
|
|
)
|
|
|
|
|
|
@@ -102,12 +100,12 @@ class PcsdSSLTest(SslFilesMixin, TestCase):
|
|
self.damage_ssl_files()
|
|
with self.assertRaises(SSLCertKeyException) as ctx_manager:
|
|
self.pcsd_ssl.guarantee_valid_certs()
|
|
- self.assertTrue(
|
|
- ctx_manager.exception.args
|
|
- in [
|
|
- self.DAMAGED_SSL_FILES_ERRORS_1,
|
|
- self.DAMAGED_SSL_FILES_ERRORS_2,
|
|
- ]
|
|
+ errors = ctx_manager.exception.args
|
|
+ self.assertEqual(len(errors), 1)
|
|
+ self.assertRegex(
|
|
+ errors[0],
|
|
+ r"SSL certificate does not match the key: "
|
|
+ r"\[SSL\] PEM lib \(_ssl\.c:\d+\)",
|
|
)
|
|
|
|
def test_context_uses_given_options(self):
|
|
diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
|
|
index bce8e39e..89c26f33 100644
|
|
--- a/pcsd/pcs.rb
|
|
+++ b/pcsd/pcs.rb
|
|
@@ -12,6 +12,7 @@ require 'fileutils'
|
|
require 'backports/latest'
|
|
require 'base64'
|
|
require 'ethon'
|
|
+require 'openssl'
|
|
|
|
require 'config.rb'
|
|
require 'cfgsync.rb'
|
|
@@ -1170,39 +1171,23 @@ def read_file_lock(path, binary=false)
|
|
end
|
|
end
|
|
|
|
-def verify_cert_key_pair(cert, key)
|
|
+def verify_cert_key_pair(cert_data, key_data)
|
|
errors = []
|
|
- cert_modulus = nil
|
|
- key_modulus = nil
|
|
|
|
- stdout, stderr, retval = run_cmd_options(
|
|
- PCSAuth.getSuperuserAuth(),
|
|
- {
|
|
- 'stdin' => cert,
|
|
- },
|
|
- '/usr/bin/openssl', 'x509', '-modulus', '-noout'
|
|
- )
|
|
- if retval != 0
|
|
- errors << "Invalid certificate: #{stderr.join}"
|
|
- else
|
|
- cert_modulus = stdout.join.strip
|
|
+ begin
|
|
+ cert = OpenSSL::X509::Certificate.new(cert_data)
|
|
+ rescue OpenSSL::X509::CertificateError => e
|
|
+ errors << "Invalid certificate: #{e}"
|
|
end
|
|
|
|
- stdout, stderr, retval = run_cmd_options(
|
|
- PCSAuth.getSuperuserAuth(),
|
|
- {
|
|
- 'stdin' => key,
|
|
- },
|
|
- '/usr/bin/openssl', 'rsa', '-modulus', '-noout'
|
|
- )
|
|
- if retval != 0
|
|
- errors << "Invalid key: #{stderr.join}"
|
|
- else
|
|
- key_modulus = stdout.join.strip
|
|
+ begin
|
|
+ key = OpenSSL::PKey.read(key_data)
|
|
+ rescue OpenSSL::PKey::PKeyError => e
|
|
+ errors << "Invalid key: #{e}"
|
|
end
|
|
|
|
- if errors.empty? and cert_modulus and key_modulus
|
|
- if cert_modulus != key_modulus
|
|
+ if errors.empty?
|
|
+ if not cert.check_private_key(key)
|
|
errors << 'Certificate does not match the key'
|
|
end
|
|
end
|
|
diff --git a/pcsd/remote.rb b/pcsd/remote.rb
|
|
index 3361b3f6..c43e3116 100644
|
|
--- a/pcsd/remote.rb
|
|
+++ b/pcsd/remote.rb
|
|
@@ -694,7 +694,7 @@ def set_certs(params, request, auth_user)
|
|
if !ssl_cert.empty? and !ssl_key.empty?
|
|
ssl_errors = verify_cert_key_pair(ssl_cert, ssl_key)
|
|
if ssl_errors and !ssl_errors.empty?
|
|
- return [400, ssl_errors.join]
|
|
+ return [400, ssl_errors.join('; ')]
|
|
end
|
|
begin
|
|
write_file_lock(CRT_FILE, 0600, ssl_cert)
|
|
diff --git a/requirements.txt b/requirements.txt
|
|
index eb42ce40..2f62b1c3 100644
|
|
--- a/requirements.txt
|
|
+++ b/requirements.txt
|
|
@@ -2,7 +2,7 @@
|
|
astroid==2.4.2
|
|
pylint==2.6.0
|
|
tornado>=6.1.0
|
|
-mypy==0.790
|
|
+mypy==0.812
|
|
dacite
|
|
# temporarily stick to previous version until it's convinient to reformat code
|
|
black==20.8b1
|
|
diff --git a/test/centos8/Dockerfile b/test/centos8/Dockerfile
|
|
index 910d7652..00c17ffe 100644
|
|
--- a/test/centos8/Dockerfile
|
|
+++ b/test/centos8/Dockerfile
|
|
@@ -7,11 +7,11 @@ RUN dnf install -y \
|
|
--enablerepo=PowerTools \
|
|
# python
|
|
python3 \
|
|
+ python3-cryptography \
|
|
python3-lxml \
|
|
python3-mock \
|
|
python3-pip \
|
|
python3-pycurl \
|
|
- python3-pyOpenSSL \
|
|
python3-pyparsing \
|
|
# ruby
|
|
ruby \
|
|
diff --git a/test/fedora31/Dockerfile b/test/fedora31/Dockerfile
|
|
index cc94bee2..8d0a0672 100644
|
|
--- a/test/fedora31/Dockerfile
|
|
+++ b/test/fedora31/Dockerfile
|
|
@@ -5,11 +5,11 @@ ARG src_path
|
|
RUN dnf install -y \
|
|
# python
|
|
python3 \
|
|
+ python3-cryptography \
|
|
python3-lxml \
|
|
python3-mock \
|
|
python3-pip \
|
|
python3-pycurl \
|
|
- python3-pyOpenSSL \
|
|
python3-pyparsing \
|
|
# ruby
|
|
ruby \
|
|
diff --git a/test/fedora32/Dockerfile b/test/fedora32/Dockerfile
|
|
index 82bdff74..750ff979 100644
|
|
--- a/test/fedora32/Dockerfile
|
|
+++ b/test/fedora32/Dockerfile
|
|
@@ -5,12 +5,12 @@ ARG src_path
|
|
RUN dnf install -y \
|
|
# python
|
|
python3 \
|
|
+ python3-cryptography \
|
|
python3-distro \
|
|
python3-lxml \
|
|
python3-mock \
|
|
python3-pip \
|
|
python3-pycurl \
|
|
- python3-pyOpenSSL \
|
|
python3-pyparsing \
|
|
# ruby
|
|
ruby \
|
|
--
|
|
2.26.2
|
|
|