Repository: pyca/cryptography

Issue:
Error generating certificate with not_valid_after = 9999-12-31 23:59:59
Hi, 
While trying to generate a certificate with not_valid_after = 9999-12-31 23:59:59, I'm getting the following error: 
cryptography.exceptions.InternalError: Unknown OpenSSL error. (...) ([_OpenSSLErrorWithText(code=218992813L, lib=13, func=217, reason=173, reason_text='error:0D0D90AD:asn1 encoding routines:ASN1_TIME_adj:error getting time')]).

I'm using Windows 8, 64-bit with Python 2.7.12, cryptography(1.5.2)
The same code works in Ubuntu.

After doing some further testing, I noticed it will work for year values below 3000. 
Any hints/workarounds?



Discussion:
@jmcfcosta thanks for the report. This is, unfortunately, a platform limitation due to the way OpenSSL gets time on a platform basis. When calling `OPENSSL_gmtime` it will use the platform's native code to obtain information, and on Windows requesting a value like `9999-12-31 23:59:59` fails and returns NULL. There's nothing we can do to work around this to my knowledge.

@reaperhulk we should make this return a clean error, instead of InternalError


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -840,14 +840,16 @@ def create_x509_certificate(self, builder, private_key, algorithm):
             self._lib.X509_get_notBefore(x509_cert),
             calendar.timegm(builder._not_valid_before.timetuple())
         )
-        self.openssl_assert(res != self._ffi.NULL)
+        if res == self._ffi.NULL:
+            self._raise_time_set_error()
 
         # Set the "not after" time.
         res = self._lib.ASN1_TIME_set(
             self._lib.X509_get_notAfter(x509_cert),
             calendar.timegm(builder._not_valid_after.timetuple())
         )
-        self.openssl_assert(res != self._ffi.NULL)
+        if res == self._ffi.NULL:
+            self._raise_time_set_error()
 
         # Add extensions.
         self._create_x509_extensions(
@@ -878,6 +880,17 @@ def create_x509_certificate(self, builder, private_key, algorithm):
 
         return _Certificate(self, x509_cert)
 
+    def _raise_time_set_error(self):
+        errors = self._consume_errors()
+        self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_ASN1)
+        self.openssl_assert(
+            errors[0][3] == self._lib.ASN1_R_ERROR_GETTING_TIME
+        )
+        raise ValueError(
+            "Invalid time. This error can occur if you set a time too far in "
+            "the future on Windows."
+        )
+
     def create_x509_crl(self, builder, private_key, algorithm):
         if not isinstance(builder, x509.CertificateRevocationListBuilder):
             raise TypeError('Builder type mismatch.')


Test patch:
diff --git a/tests/test_x509.py b/tests/test_x509.py
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -8,6 +8,7 @@
 import datetime
 import ipaddress
 import os
+import sys
 import warnings
 
 from pyasn1.codec.der import decoder
@@ -1524,6 +1525,35 @@ def test_encode_nonstandard_aia(self, backend):
 
         builder.sign(private_key, hashes.SHA256(), backend)
 
+    @pytest.mark.skipif(sys.platform != "win32", reason="Requires windows")
+    @pytest.mark.parametrize(
+        ("not_valid_before", "not_valid_after"),
+        [
+            [datetime.datetime(1999, 1, 1), datetime.datetime(9999, 1, 1)],
+            [datetime.datetime(9999, 1, 1), datetime.datetime(9999, 12, 31)],
+        ]
+    )
+    @pytest.mark.requires_backend_interface(interface=RSABackend)
+    @pytest.mark.requires_backend_interface(interface=X509Backend)
+    def test_invalid_time_windows(self, not_valid_before, not_valid_after,
+                                  backend):
+        private_key = RSA_KEY_2048.private_key(backend)
+        builder = x509.CertificateBuilder().subject_name(x509.Name([
+            x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
+        ])).issuer_name(x509.Name([
+            x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
+        ])).public_key(
+            private_key.public_key()
+        ).serial_number(
+            777
+        ).not_valid_before(
+            not_valid_before
+        ).not_valid_after(
+            not_valid_after
+        )
+        with pytest.raises(ValueError):
+            builder.sign(private_key, hashes.SHA256(), backend)
+
     @pytest.mark.requires_backend_interface(interface=RSABackend)
     @pytest.mark.requires_backend_interface(interface=X509Backend)
     def test_no_subject_name(self, backend):