From 5b890cd81b652584a151ba418478074e34f61c41 Mon Sep 17 00:00:00 2001
From: Peter Hartmann <peter.hartmann@nokia.com>
Date: Wed, 4 Aug 2010 11:59:49 +0200
Subject: QSslCertificate: support expiration dates > 2049

X509 has two time formats: UTC, where the year is in two-digit format,
and generalized time with four-digit years.
This patch allows dates specified generalized time.

Reviewed-by: Thiago Macieira
Task-number: QTBUG-12489
(cherry picked from commit a77dbcdbb7022cc754ba87aea9a4fc471d1e4495)
---
 src/network/ssl/qsslsocket_openssl_symbols.cpp     | 121 ++++++++++++---------
 .../cert-large-expiration-date.pem                 |  15 +++
 tests/auto/qsslcertificate/tst_qsslcertificate.cpp |  15 +++
 3 files changed, 101 insertions(+), 50 deletions(-)
 create mode 100644 tests/auto/qsslcertificate/more-certificates/cert-large-expiration-date.pem

diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index f4842aa..b5347ad 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -755,74 +755,95 @@ bool q_resolveOpenSslSymbols()
 //==============================================================================
 QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime)
 {
-    char lBuffer[24];
-    char *pBuffer = lBuffer;
-
     size_t lTimeLength = aTime->length;
     char *pString = (char *) aTime->data;
 
     if (aTime->type == V_ASN1_UTCTIME) {
+
+        char lBuffer[24];
+        char *pBuffer = lBuffer;
+
         if ((lTimeLength < 11) || (lTimeLength > 17))
             return QDateTime();
 
         memcpy(pBuffer, pString, 10);
         pBuffer += 10;
         pString += 10;
-    } else {
-        if (lTimeLength < 13)
-            return QDateTime();
-
-        memcpy(pBuffer, pString, 12);
-        pBuffer += 12;
-        pString += 12;
-    }
 
-    if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) {
-        *pBuffer++ = '0';
-        *pBuffer++ = '0';
-    } else {
-        *pBuffer++ = *pString++;
-        *pBuffer++ = *pString++;
-        // Skip any fractional seconds...
-        if (*pString == '.') {
-            pString++;
-            while ((*pString >= '0') && (*pString <= '9'))
+        if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) {
+            *pBuffer++ = '0';
+            *pBuffer++ = '0';
+        } else {
+            *pBuffer++ = *pString++;
+            *pBuffer++ = *pString++;
+            // Skip any fractional seconds...
+            if (*pString == '.') {
                 pString++;
+                while ((*pString >= '0') && (*pString <= '9'))
+                    pString++;
+            }
         }
-    }
 
-    *pBuffer++ = 'Z';
-    *pBuffer++ = '\0';
+        *pBuffer++ = 'Z';
+        *pBuffer++ = '\0';
 
-    time_t lSecondsFromUCT;
-    if (*pString == 'Z') {
-        lSecondsFromUCT = 0;
-    } else {
-        if ((*pString != '+') && (*pString != '-'))
-            return QDateTime();
+        time_t lSecondsFromUCT;
+        if (*pString == 'Z') {
+            lSecondsFromUCT = 0;
+        } else {
+            if ((*pString != '+') && (*pString != '-'))
+                return QDateTime();
+
+            lSecondsFromUCT = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60;
+            lSecondsFromUCT += (pString[3] - '0') * 10 + (pString[4] - '0');
+            lSecondsFromUCT *= 60;
+            if (*pString == '-')
+                lSecondsFromUCT = -lSecondsFromUCT;
+        }
+
+        tm lTime;
+        lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0');
+        lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0');
+        lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0');
+        lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0');
+        lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1;
+        lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0');
+        if (lTime.tm_year < 50)
+            lTime.tm_year += 100; // RFC 2459
+
+        QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday);
+        QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec);
+
+        QDateTime result(resDate, resTime, Qt::UTC);
+        result = result.addSecs(lSecondsFromUCT);
+        return result;
+
+    } else if (aTime->type == V_ASN1_GENERALIZEDTIME) {
 
-        lSecondsFromUCT = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60;
-        lSecondsFromUCT += (pString[3] - '0') * 10 + (pString[4] - '0');
-        lSecondsFromUCT *= 60;
-        if (*pString == '-')
-            lSecondsFromUCT = -lSecondsFromUCT;
+        if (lTimeLength < 15)
+            return QDateTime(); // hopefully never triggered
+
+        // generalized time is always YYYYMMDDHHMMSSZ (RFC 2459, section 4.1.2.5.2)
+        tm lTime;
+        lTime.tm_sec = ((pString[12] - '0') * 10) + (pString[13] - '0');
+        lTime.tm_min = ((pString[10] - '0') * 10) + (pString[11] - '0');
+        lTime.tm_hour = ((pString[8] - '0') * 10) + (pString[9] - '0');
+        lTime.tm_mday = ((pString[6] - '0') * 10) + (pString[7] - '0');
+        lTime.tm_mon = (((pString[4] - '0') * 10) + (pString[5] - '0'));
+        lTime.tm_year = ((pString[0] - '0') * 1000) + ((pString[1] - '0') * 100) +
+                        ((pString[2] - '0') * 10) + (pString[3] - '0');
+
+        QDate resDate(lTime.tm_year, lTime.tm_mon, lTime.tm_mday);
+        QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec);
+
+        QDateTime result(resDate, resTime, Qt::UTC);
+        return result;
+
+    } else {
+        qWarning("unsupported date format detected");
+        return QDateTime();
     }
 
-    tm lTime;
-    lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0');
-    lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0');
-    lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0');
-    lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0');
-    lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1;
-    lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0');
-    if (lTime.tm_year < 50)
-        lTime.tm_year += 100; // RFC 2459
-
-    QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday);
-    QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec);
-    QDateTime result(resDate, resTime, Qt::UTC);
-    result = result.addSecs(lSecondsFromUCT);
-    return result;
 }
 
 QT_END_NAMESPACE
diff --git a/tests/auto/qsslcertificate/more-certificates/cert-large-expiration-date.pem b/tests/auto/qsslcertificate/more-certificates/cert-large-expiration-date.pem
new file mode 100644
index 0000000..416dd4d
--- /dev/null
+++ b/tests/auto/qsslcertificate/more-certificates/cert-large-expiration-date.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcOgAwIBAgIJAM7bMShFxAVAMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwIBcNMTAwODA0MDk1MzQxWhgPMjA1MTA4MjkwOTUzNDFa
+MEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ
+bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
+AoGBAM2q22/WNMmn8cC+5EEYGeICySLmp9W6Ay6eKHr0Xxp3X3epETuPfvAuxp7r
+OtkS18EMUegkUj8jw0IMEcbyHKFC/rTCaYOt93CxGBXMIChiMPAsFeYzGa/D6xzA
+kfcRaJRQ+Ek3CDLXPnXfo7xpABXezYcPXAJrgsgBfWrwHdxzAgMBAAGjUDBOMB0G
+A1UdDgQWBBSKbhnnl5uP2X+zuwFP6GovUpUN2TAfBgNVHSMEGDAWgBSKbhnnl5uP
+2X+zuwFP6GovUpUN2TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKF0
+jl02liwFfupmzC5oxz2T3IyjEa21fm7QBMQJvQr5OTuX1/C7DAl7g/6/h7BH6JS3
+qpx5rXGet8SZmG8dODL4o3U4xOXnNzch7HtQixWwlx4XnFAXPMHflFX7YC5QQNHq
+I8Y8IW+XjAYWpYJywWMUZIbr1/9y9gn1beYEE3pq
+-----END CERTIFICATE-----
diff --git a/tests/auto/qsslcertificate/tst_qsslcertificate.cpp b/tests/auto/qsslcertificate/tst_qsslcertificate.cpp
index d25bb0b..201c0d4 100644
--- a/tests/auto/qsslcertificate/tst_qsslcertificate.cpp
+++ b/tests/auto/qsslcertificate/tst_qsslcertificate.cpp
@@ -110,6 +110,7 @@ private slots:
     void nulInCN();
     void nulInSan();
     void largeSerialNumber();
+    void largeExpirationDate();
 // ### add tests for certificate bundles (multiple certificates concatenated into a single
 //     structure); both PEM and DER formatted
 #endif
@@ -799,6 +800,20 @@ void tst_QSslCertificate::largeSerialNumber()
     QCOMPARE(cert.serialNumber(), QByteArray("01:02:03:04:05:06:07:08:09:10:aa:bb:cc:dd:ee:ff:17:18:19:20"));
 }
 
+void tst_QSslCertificate::largeExpirationDate() // QTBUG-12489
+{
+    QList<QSslCertificate> certList =
+        QSslCertificate::fromPath(SRCDIR "more-certificates/cert-large-expiration-date.pem");
+
+    QCOMPARE(certList.size(), 1);
+
+    const QSslCertificate &cert = certList.at(0);
+    QVERIFY(!cert.isNull());
+    QCOMPARE(cert.effectiveDate().toUTC(), QDateTime(QDate(2010, 8, 4), QTime(9, 53, 41), Qt::UTC));
+    // if the date is larger than 2049, then the generalized time format is used
+    QCOMPARE(cert.expiryDate().toUTC(), QDateTime(QDate(2051, 8, 29), QTime(9, 53, 41), Qt::UTC));
+}
+
 #endif // QT_NO_OPENSSL
 
 QTEST_MAIN(tst_QSslCertificate)
-- 
cgit v0.12