File 0001-Add-workaround-for-OSError-raised-by-Popen.communica.patch of Package python-keystoneclient
From c4c09a82794218c0ffb88be819165fc93171ffd0 Mon Sep 17 00:00:00 2001
From: Dirk Mueller <dirk@dmllr.de>
Date: Thu, 20 Jun 2013 18:49:26 +0200
Subject: [PATCH] Add workaround for OSError raised by Popen.communicate()
In Python 2.6 is a bug that causes OSError to be raised
(imho incorrectly) when too much data is written to STDIN
and the process died prematurely.
In the case of keystoneclient this happens quite often via
the first cms_verify() call that can fail when the CA or the
signing pem is not there already.
Add basic unit tests to cover all of the public methods from
keystone.common.cms, raising test coverage to 77%.
Add license header (hacking warning)
Change-Id: I6e650ab9494c605b4e41c78c87a9505e09d5fc29
---
keystoneclient/common/cms.py | 62 ++++++++++++++++++++++++++++++++-----
tests/test_auth_token_middleware.py | 53 ++++++++++++++++++++++++++++++-
2 files changed, 107 insertions(+), 8 deletions(-)
Index: python-keystoneclient-0.2.3/keystoneclient/common/cms.py
===================================================================
--- python-keystoneclient-0.2.3.orig/keystoneclient/common/cms.py
+++ python-keystoneclient-0.2.3/keystoneclient/common/cms.py
@@ -1,3 +1,18 @@
+# Copyright 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+# 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.
+
import hashlib
import logging
@@ -24,6 +39,22 @@ def _ensure_subprocess():
import subprocess
+def _fake_openssl_error(file_list):
+ # Come up with a meaningful error message for the
+ # outer logic
+ output = ''
+ err = 'Error while writing to pipe.'
+ for try_file in file_list:
+ try:
+ with open(try_file, 'r') as f:
+ pass
+ except IOError as e:
+ err = ("Hit OSError while launching openssl. "
+ "Likely due to %s: %s") % (try_file, e.strerror)
+
+ return output, err
+
+
def cms_verify(formatted, signing_cert_file_name, ca_file_name):
"""
verifies the signature of the contents IAW CMS syntax
@@ -38,8 +69,17 @@ def cms_verify(formatted, signing_cert_f
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
- output, err = process.communicate(formatted)
- retcode = process.poll()
+ try:
+ output, err = process.communicate(formatted)
+ except OSError:
+ # this shouldn't happen, but does when Python is old
+ # http://bugs.python.org/issue10963
+ output, err = _fake_openssl_error(
+ (signing_cert_file_name, ca_file_name))
+ retcode = -1
+ else:
+ retcode = process.poll()
+
if retcode:
LOG.error('Verify error: %s' % err)
# NOTE(dmllr): Python 2.6 compatibility:
@@ -132,21 +172,29 @@ def cms_sign_text(text, signing_cert_fil
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
- output, err = process.communicate(text)
- retcode = process.poll()
+ try:
+ output, err = process.communicate(text)
+ except OSError:
+ # this shouldn't happen, but does when Python is old
+ # http://bugs.python.org/issue10963
+ output, err = _fake_openssl_error(
+ (signing_cert_file_name, signing_key_file_name))
+ retcode = -1
+ else:
+ retcode = process.poll()
+
if retcode or "Error" in err:
LOG.error('Signing error: %s' % err)
raise subprocess.CalledProcessError(retcode, "openssl")
return output
-def cms_sign_token(text, signing_cert_file_name, signing_key_file_name):
- output = cms_sign_text(text, signing_cert_file_name, signing_key_file_name)
+def cms_sign_token(token, signing_cert_file_name, signing_key_file_name):
+ output = cms_sign_text(token, signing_cert_file_name, signing_key_file_name)
return cms_to_token(output)
def cms_to_token(cms_text):
-
start_delim = "-----BEGIN CMS-----"
end_delim = "-----END CMS-----"
signed_text = cms_text
Index: python-keystoneclient-0.2.3/tests/test_auth_token_middleware.py
===================================================================
--- python-keystoneclient-0.2.3.orig/tests/test_auth_token_middleware.py
+++ python-keystoneclient-0.2.3/tests/test_auth_token_middleware.py
@@ -18,6 +18,7 @@ import datetime
import iso8601
import os
import string
+import subprocess
import sys
import tempfile
import testtools
@@ -39,7 +40,7 @@ KEYDIR = test.rootdir('examples', 'pki',
CMSDIR = test.rootdir('examples', 'pki', 'cms')
SIGNING_CERT = os.path.join(CERTDIR, 'signing_cert.pem')
SIGNING_KEY = os.path.join(KEYDIR, 'signing_key.pem')
-CA = os.path.join(CERTDIR, 'ca.pem')
+CA = os.path.join(CERTDIR, 'cacert.pem')
REVOCATION_LIST = None
REVOKED_TOKEN = None
@@ -1351,3 +1352,49 @@ class TokenEncodingTest(testtools.TestCa
def test_quoted_token(self):
self.assertEqual('foo%20bar', auth_token.safe_quote('foo%20bar'))
+
+
+class CmsTest(testtools.TestCase):
+
+ """Unit tests for the keystoneclient.common.cms module."""
+
+ def test_token_to_cms_to_token(self):
+ with open(os.path.join(CMSDIR, 'auth_token_scoped.pem')) as f:
+ AUTH_TOKEN_SCOPED_CMS = f.read()
+
+ self.assertEqual(cms.token_to_cms(SIGNED_TOKEN_SCOPED),
+ AUTH_TOKEN_SCOPED_CMS)
+
+ tok = cms.cms_to_token(cms.token_to_cms(SIGNED_TOKEN_SCOPED))
+ self.assertEqual(tok, SIGNED_TOKEN_SCOPED)
+
+ def test_ans1_token(self):
+ self.assertTrue(cms.is_ans1_token(SIGNED_TOKEN_SCOPED))
+ self.assertFalse(cms.is_ans1_token("FOOBAR"))
+
+ def test_cms_sign_token_no_files(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ cms.cms_sign_token, SIGNED_TOKEN_SCOPED,
+ "/no/such/file", "/no/such/key")
+
+ def test_cms_sign_token_success(self):
+ self.assertTrue(
+ cms.cms_sign_token(SIGNED_TOKEN_SCOPED,
+ SIGNING_CERT, SIGNING_KEY))
+
+ def test_cms_verify_token_no_files(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ cms.cms_verify, SIGNED_TOKEN_SCOPED,
+ "/no/such/file", "/no/such/key")
+
+ def test_cms_verify_token_scoped(self):
+ cms_content = cms.token_to_cms(SIGNED_TOKEN_SCOPED)
+ self.assertTrue(cms.cms_verify(cms_content, SIGNING_CERT, CA))
+
+ def test_cms_verify_token_unscoped(self):
+ cms_content = cms.token_to_cms(SIGNED_TOKEN_UNSCOPED)
+ self.assertTrue(cms.cms_verify(cms_content, SIGNING_CERT, CA))
+
+ def test_cms_verify_token_v3_scoped(self):
+ cms_content = cms.token_to_cms(SIGNED_v3_TOKEN_SCOPED)
+ self.assertTrue(cms.cms_verify(cms_content, SIGNING_CERT, CA))