Repository: pocoproject/poco (2026-03-25T11:59:00Z)

Issue:
X509Certificate validFrom method throws exception for timestamps ending with Z
**Describe the bug**
Issue is in PocoCrypto X509Certificate class, methods validFrom and expiresOn.

Example of string being parsed: 260325082414**Z**
Result: Exception "Invalid DateTimeString: 260325082414Z unexpected trailing characters". 
Exception was added in [this commit](https://github.com/pocoproject/poco/commit/8410eb1a6f85090e69f6180b41871641101960d3#diff-9ac0fdd12665bd393200d28af9b972f161fab0b38c5f73c03e9689ae3200b4d9R314).

**To Reproduce**
```
   Poco::Net::X509Certificate certificate("certificate.pem");
   const auto validFrom = certificate.validFrom();
```

Certificate:
```
-----BEGIN CERTIFICATE-----
MIIDdDCCAlygAwIBAgIUZtXjEd/ct7FWvWhCTIhxP7V/9egwDQYJKoZIhvcNAQEL
BQAwJDENMAsGA1UECgwEVGVzdDETMBEGA1UEAwwKdGVzdGRldmljZTAeFw0yNjAz
MjUwODI0MTRaFw0yNjA2MjMwODI0MTRaMCQxDTALBgNVBAoMBFRlc3QxEzARBgNV
BAMMCnRlc3RkZXZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQ
tP8m+2dydb0521hQrkEz0PADyfz557SLqE4wnodVnj13MFC+sPlWKq9MEtg3uzWu
rtItN2vTzfoSRmyXGbhzKCzHZ6aGMYs8swBrhLVSW7APnJenxxBdJcUhGJHegyC2
9gdgUROos3kS8TGxGZjaNuPb2Zh6MzKGqwi5kcLf18iWCJAhbB4PAPF2xjKsVa15
ZDalUqFc67ks9696M5UUI6OypyzHgN88H+AAi6abkzqlAaSRz5mSmO5H3y9HvTTG
Srn+zMOoo8Xub7pqA04joze/YxNwr02EQFZzqFkQQC6nDFTsIHL4Pb2nFQM23Cu4
/740qh+humAtmI+IpmJjAgMBAAGjgZ0wgZowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
7UKWi2qtcWj6mU+fjJchvVaSSFMwCwYDVR0PBAQDAgL0MB0GA1UdJQQWMBQGCCsG
AQUFBwMCBggrBgEFBQcDATAhBgNVHREEGjAYggp0ZXN0ZGV2aWNlhwQKQAOYhwTA
qGRkMB8GA1UdIwQYMBaAFO1ClotqrXFo+plPn4yXIb1WkkhTMA0GCSqGSIb3DQEB
CwUAA4IBAQC/QGO2LtzY1/kfmrE8YWB5sAO9ecnxOXl5tvzDiIxJy5LV0YPIMtxi
8TfBuU1DlKq3p9saTsqpyNFY0S7iVYZwM4edIqJRaNOqCKqLPNtKAldZnO3r6a8m
p84uqIqH2NJwdElEijUaaChZa2Talvd957k0LM1ymQfM1lltECTAuOjgRW6ugBj9
zgxPHfVTmUZhR9wSrG3F/Qh239XZeviA1tyaopfhxqBREoJfDUF7KPoJ2KhlTKsU
iRSSK+vMCCZ5hcnTQyZYl3s1h28GUL+kdh4OmDy45HdgeXcWkPeE2E4Ix23HHot6
/6vYUulv9/oWU9deh3/dzwRrutJqivA2
-----END CERTIFICATE-----
```

**Expected behavior**
Z at the end should be accepted.

**Please add relevant environment information:**
 - OS Type and Version: Windows 11
 - POCO Version: 1.15.0



Discussion:

Patch:
diff --git a/Crypto/src/X509Certificate.cpp b/Crypto/src/X509Certificate.cpp
index 3cd3e5ae21..bff1d91497 100644
--- a/Crypto/src/X509Certificate.cpp
+++ b/Crypto/src/X509Certificate.cpp
@@ -32,6 +32,16 @@
 namespace Poco::Crypto {
 
 
+namespace {
+
+// ASN1 UTCTime format: YYMMDDHHMMSSZ (RFC 5280 Section 4.1.2.5.1)
+const std::string ASN1_UTCTIME_FORMAT("%y%m%d%H%M%S%Z");
+// ASN1 GeneralizedTime format: YYYYMMDDHHMMSSZ (RFC 5280 Section 4.1.2.5.2)
+const std::string ASN1_GENERALIZEDTIME_FORMAT("%Y%m%d%H%M%S%Z");
+
+} // namespace
+
+
 X509Certificate::X509Certificate(std::istream& istr) : _pCert(nullptr)
 {
 	load(istr);
@@ -301,11 +311,11 @@ Poco::DateTime X509Certificate::validFrom() const
 	int tzd;
 	if (certTimeType == V_ASN1_UTCTIME)
 	{
-		return DateTimeParser::parse("%y%m%d%H%M%S", dateTime, tzd);
+		return DateTimeParser::parse(ASN1_UTCTIME_FORMAT, dateTime, tzd);
 	}
 	else if (certTimeType == V_ASN1_GENERALIZEDTIME)
 	{
-		return DateTimeParser::parse("%Y%m%d%H%M%S", dateTime, tzd);
+		return DateTimeParser::parse(ASN1_GENERALIZEDTIME_FORMAT, dateTime, tzd);
 	}
 	else
 	{
@@ -322,11 +332,11 @@ Poco::DateTime X509Certificate::expiresOn() const
 	int tzd;
 	if (certTimeType == V_ASN1_UTCTIME)
 	{
-		return DateTimeParser::parse("%y%m%d%H%M%S", dateTime, tzd);
+		return DateTimeParser::parse(ASN1_UTCTIME_FORMAT, dateTime, tzd);
 	}
 	else if (certTimeType == V_ASN1_GENERALIZEDTIME)
 	{
-		return DateTimeParser::parse("%Y%m%d%H%M%S", dateTime, tzd);
+		return DateTimeParser::parse(ASN1_GENERALIZEDTIME_FORMAT, dateTime, tzd);
 	}
 	else
 	{


Test patch:
diff --git a/Crypto/testsuite/src/CryptoTest.cpp b/Crypto/testsuite/src/CryptoTest.cpp
index 214395b9ba..ff85737546 100644
--- a/Crypto/testsuite/src/CryptoTest.cpp
+++ b/Crypto/testsuite/src/CryptoTest.cpp
@@ -363,6 +363,17 @@ void CryptoTest::testCertificate()
 	const auto fingerprint = cert.fingerprint();
 	assertTrue (Poco::DigestEngine::digestToHex(fingerprint) == "ac84e4eb72c861ccb20f2900f3f17a9ac11f6579");
 
+	// verify validFrom/expiresOn parse ASN1 timestamps with Z suffix (GH #5263)
+	Poco::DateTime validFrom = cert.validFrom();
+	assertTrue (validFrom.year() == 2009);
+	assertTrue (validFrom.month() == 5);
+	assertTrue (validFrom.day() == 7);
+
+	Poco::DateTime expiresOn = cert.expiresOn();
+	assertTrue (expiresOn.year() == 2029);
+	assertTrue (expiresOn.month() == 5);
+	assertTrue (expiresOn.day() == 2);
+
 	// fails with recent OpenSSL versions:
 	// assert (cert.issuedBy(cert));