diff --git a/keystoneclient/common/cms.py b/keystoneclient/common/cms.py index 8bc24f97d..da0402941 100644 --- a/keystoneclient/common/cms.py +++ b/keystoneclient/common/cms.py @@ -12,10 +12,20 @@ # License for the specific language governing permissions and limitations # under the License. -import hashlib +"""Certificate signing functions. +Call set_subprocess() with the subprocess module. Either Python's +subprocess or eventlet.green.subprocess can be used. + +If set_subprocess() is not called, this module will pick Python's subprocess +or eventlet.green.subprocess based on if os module is patched by eventlet. +""" + +import hashlib import logging +from keystoneclient import exceptions + subprocess = None LOG = logging.getLogger(__name__) @@ -38,10 +48,20 @@ def _ensure_subprocess(): import subprocess # noqa +def set_subprocess(_subprocess=None): + """Set subprocess module to use. + The subprocess could be eventlet.green.subprocess if using eventlet, + or Python's subprocess otherwise. + """ + global subprocess + subprocess = _subprocess + + def cms_verify(formatted, signing_cert_file_name, ca_file_name): """Verifies the signature of the contents IAW CMS syntax. :raises: subprocess.CalledProcessError + :raises: CertificateConfigError if certificate is not configured properly. """ _ensure_subprocess() process = subprocess.Popen(["openssl", "cms", "-verify", @@ -55,9 +75,23 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name): stderr=subprocess.PIPE) output, err = process.communicate(formatted) retcode = process.poll() - if retcode: - # Do not log errors, as some happen in the positive thread - # instead, catch them in the calling code and log them there. + + # Do not log errors, as some happen in the positive thread + # instead, catch them in the calling code and log them there. + + # When invoke the openssl with not exist file, return code 2 + # and error msg will be returned. + # You can get more from + # http://www.openssl.org/docs/apps/cms.html#EXIT_CODES + # + # $ openssl cms -verify -certfile not_exist_file -CAfile \ + # not_exist_file -inform PEM -nosmimecap -nodetach \ + # -nocerts -noattr + # Error opening certificate file not_exist_file + # + if retcode == 2: + raise exceptions.CertificateConfigError(err) + elif retcode: # NOTE(dmllr): Python 2.6 compatibility: # CalledProcessError did not have output keyword argument e = subprocess.CalledProcessError(retcode, "openssl") diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py index 2bd6c37c4..a03ef8096 100644 --- a/keystoneclient/exceptions.py +++ b/keystoneclient/exceptions.py @@ -20,3 +20,12 @@ Exception definitions. #flake8: noqa from keystoneclient.apiclient.exceptions import * + + +class CertificateConfigError(Exception): + """Error reading the certificate""" + def __init__(self, output): + self.output = output + msg = ("Unable to load certificate. " + "Ensure your system is configured properly.") + super(CertificateConfigError, self).__init__(msg) diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py index 332f8ac58..2a3de1e09 100644 --- a/keystoneclient/middleware/auth_token.py +++ b/keystoneclient/middleware/auth_token.py @@ -157,6 +157,7 @@ import netaddr import six from keystoneclient.common import cms +from keystoneclient import exceptions from keystoneclient.middleware import memcache_crypt from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import memorycache @@ -1158,7 +1159,7 @@ class AuthProtocol(object): try: output = cms.cms_verify(data, self.signing_cert_file_name, self.signing_ca_file_name) - except cms.subprocess.CalledProcessError as err: + except exceptions.CertificateConfigError as err: if self.cert_file_missing(err.output, self.signing_cert_file_name): self.fetch_signing_cert() @@ -1167,8 +1168,10 @@ class AuthProtocol(object): self.signing_ca_file_name): self.fetch_ca_cert() continue + raise + except cms.subprocess.CalledProcessError as err: self.LOG.warning('Verify error: %s' % err) - raise err + raise return output def verify_signed_token(self, signed_text): @@ -1266,8 +1269,10 @@ class AuthProtocol(object): with open(self.signing_cert_file_name, 'w') as certfile: certfile.write(data) + if response.status_code != 200: + raise exceptions.CertificateConfigError(response.text) + try: - #todo check response try: write_cert_file(response.text) except IOError: @@ -1282,8 +1287,10 @@ class AuthProtocol(object): path = self.auth_admin_prefix.rstrip('/') + '/v2.0/certificates/ca' response = self._http_request('GET', path) + if response.status_code != 200: + raise exceptions.CertificateConfigError(response.text) + try: - #todo check response with open(self.signing_ca_file_name, 'w') as certfile: certfile.write(response.text) except (AssertionError, KeyError): diff --git a/keystoneclient/tests/test_auth_token_middleware.py b/keystoneclient/tests/test_auth_token_middleware.py index ff6b11503..40a23976a 100644 --- a/keystoneclient/tests/test_auth_token_middleware.py +++ b/keystoneclient/tests/test_auth_token_middleware.py @@ -31,6 +31,7 @@ import mock import webob from keystoneclient.common import cms +from keystoneclient import exceptions from keystoneclient.middleware import auth_token from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import memorycache @@ -886,7 +887,7 @@ class CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest): httpretty.register_uri(httpretty.GET, "%s/v2.0/certificates/signing" % BASE_URI, status=404) - self.assertRaises(cms.subprocess.CalledProcessError, + self.assertRaises(exceptions.CertificateConfigError, self.middleware.verify_signed_token, client_fixtures.SIGNED_TOKEN_SCOPED) diff --git a/keystoneclient/tests/test_cms.py b/keystoneclient/tests/test_cms.py new file mode 100644 index 000000000..8f9cb73bf --- /dev/null +++ b/keystoneclient/tests/test_cms.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from keystoneclient.common import cms +from keystoneclient import exceptions +from keystoneclient.tests import utils + + +class CMSTest(utils.TestCase): + def test_cms_verify(self): + self.assertRaises(exceptions.CertificateConfigError, + cms.cms_verify, + 'data', + 'no_exist_cert_file', + 'no_exist_ca_file')