pyopenssl
pyopenssl copied to clipboard
TestX509StoreContext::test_verify_with_time FAILED
Whenever I try to build pyopenssl-17.2.0 on my gentoo system I get the following test failure:
tests/test_crypto.py::TestX509StoreContext::test_verify_with_time FAILED
[...]
================================================================== FAILURES ==================================================================
_________________________________________________ TestX509StoreContext.test_verify_with_time _________________________________________________
self = <tests.test_crypto.TestX509StoreContext object at 0x00007f71d67b9360>
def test_verify_with_time(self):
"""
`verify_certificate` raises error when the verification time is
set at notAfter.
"""
store = X509Store()
store.add_cert(self.root_cert)
store.add_cert(self.intermediate_cert)
expire_time = self.intermediate_server_cert.get_notAfter()
expire_datetime = datetime.strptime(
expire_time.decode('utf-8'), '%Y%m%d%H%M%SZ'
)
store.set_time(expire_datetime)
store_ctx = X509StoreContext(store, self.intermediate_server_cert)
with pytest.raises(X509StoreContextError) as exc:
> store_ctx.verify_certificate()
E Failed: DID NOT RAISE <class 'OpenSSL.crypto.X509StoreContextError'>
tests/test_crypto.py:3639: Failed
==================================================== 1 failed, 504 passed in 9.74 seconds ====================================================
* ERROR: dev-python/pyopenssl-17.2.0::gentoo failed (test phase):
openssl version is 1.0.2l
It happens with any python implementation on my system (I have python-2.7, python-3.5, pypy and pypy3)
Could we just…you know give expire_datetime 13h of leeway? 😇
c)
Run the whole test suite in UTC local, e.g. "TZ=UTC py.test -v"
This seems the most reasonable approach.
c) Run the whole test suite in UTC local, e.g. "TZ=UTC py.test -v"
This seems the most reasonable approach.
TZ=UTC worked for me on OpenIndiana 2019.04.
This appears to be a bug in X509Store.set_time(), caused by vfy_time.strftime("%s") (vfy_time is a datetime object) disregarding the timezone, therefore passing an incorrect timestamp to X509_VERIFY_PARAM_set_time().
The "%s" problem is a known python issue, see Issue 12750, but it's not very straightforward to fix here. On python >= 3.3, int(vfy_time.timestamp()) could be used instead of int(vfy_time.strftime("%s")), but that's not available on python 2.7.
On python 3, the issue can be verified easily:
>>> from datetime import datetime, timezone
>>> tn = datetime.strptime("19700101000000Z", "%Y%m%d%H%M%SZ") # tn will be naive (timezone-unaware)
>>> tn.timestamp()
-3600.0 # I'm in CET, which is 1 hour after UTC on 1st January
>>> tn.strftime("%s")
'-3600'
>>> ta = tn.replace(tzinfo=timezone.utc) # ta will be timezone-aware
>>> ta.timestamp()
0.0 # since ta is in UTC, the timestamp of 1970-01-01 00:00 UTC is zero
>>> ta.strftime("%s")
'-3600' # but strftime("%s") disregards the timezone in ta, and always uses the local timezone
... and apparently I'm not the first to figure this out. I just bumped into #798, which is basically the same issue, and #907 fixing them.
@orosam thats another PR that needs rebasing sadly. 😬
I was playing with that a little :smile:, strftime("%s") is evil... More about that at #798...
This issue is also fixed by #907, and documented in #952.
Perhaps this issue has been fixed, but I have two more failures with this test.
This one is on 32bit i586 (OpenSUSE/Tumbleweed):
[ 48s] __________________ TestX509StoreContext.test_verify_with_time __________________
[ 48s]
[ 48s] self = <tests.test_crypto.TestX509StoreContext object at 0xf61d3f2c>
[ 48s]
[ 48s] def test_verify_with_time(self):
[ 48s] """
[ 48s] `verify_certificate` raises error when the verification time is
[ 48s] set at notAfter.
[ 48s] """
[ 48s] store = X509Store()
[ 48s] store.add_cert(self.root_cert)
[ 48s] store.add_cert(self.intermediate_cert)
[ 48s]
[ 48s] expire_time = self.intermediate_server_cert.get_notAfter()
[ 48s] expire_datetime = datetime.strptime(
[ 48s] expire_time.decode("utf-8"), "%Y%m%d%H%M%SZ"
[ 48s] )
[ 48s] > store.set_time(expire_datetime)
[ 48s]
[ 48s] tests/test_crypto.py:4111:
[ 48s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[ 48s]
[ 48s] self = <OpenSSL.crypto.X509Store object at 0xf61d3fec>
[ 48s] vfy_time = datetime.datetime(2047, 12, 20, 17, 11, 20)
[ 48s]
[ 48s] def set_time(self, vfy_time):
[ 48s] """
[ 48s] Set the time against which the certificates are verified.
[ 48s]
[ 48s] Normally the current time is used.
[ 48s]
[ 48s] .. note::
[ 48s]
[ 48s] For example, you can determine if a certificate was valid at a given
[ 48s] time.
[ 48s]
[ 48s] .. versionadded:: 17.0.0
[ 48s]
[ 48s] :param datetime vfy_time: The verification time to set on this store.
[ 48s] :return: ``None`` if the verification time was successfully set.
[ 48s] """
[ 48s] param = _lib.X509_VERIFY_PARAM_new()
[ 48s] param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free)
[ 48s]
[ 48s] _lib.X509_VERIFY_PARAM_set_time(
[ 48s] > param, calendar.timegm(vfy_time.timetuple())
[ 48s] )
[ 48s] E OverflowError: integer 2460474680 does not fit '32-bit int'
[ 48s]
[ 48s] ../../BUILDROOT/python-pyOpenSSL-20.0.0-71.1.i386/usr/lib/python3.6/site-packages/OpenSSL/crypto.py:1680: OverflowError
On x86 (not x86_64) time_t is defined as a 32-bit value. Unfortunately this means verification past int32 max won't work. OpenSSL may have other APIs for this, but someone will need to do the research.