Skip to content

Commit 59de319

Browse files
authored
Revert "Don't create Date or Calendar objects for ASN.1 dates unless needed. (#1176)" (#1200)
Reason: Unexpected behaviour change which rejects certificates with invalid date files using UTCTIME with an offset, of which it turns out there are many in circulation. Also added a regression test to ensure we don't accidentally start rejecting them again.
1 parent 74c3c1a commit 59de319

File tree

7 files changed

+155
-37
lines changed

7 files changed

+155
-37
lines changed

common/src/jni/main/cpp/conscrypt/native_crypto.cc

+63-19
Original file line numberDiff line numberDiff line change
@@ -4904,20 +4904,6 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass
49044904
return joa.release();
49054905
}
49064906

4907-
/*
4908-
* Converts an ASN1_TIME to epoch time in milliseconds.
4909-
*/
4910-
static jlong ASN1_TIME_convert_to_posix(JNIEnv* env, const ASN1_TIME* time) {
4911-
int64_t retval;
4912-
if (!ASN1_TIME_to_posix(time, &retval)) {
4913-
JNI_TRACE("ASN1_TIME_convert_to_posix(%p) => Invalid date value", time);
4914-
conscrypt::jniutil::throwParsingException(env, "Invalid date value");
4915-
return 0;
4916-
}
4917-
// ASN1_TIME_to_posix can only return years from 0000 to 9999, so this won't overflow.
4918-
return static_cast<jlong>(retval * 1000);
4919-
}
4920-
49214907
static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref,
49224908
CONSCRYPT_UNUSED jobject holder) {
49234909
CHECK_ERROR_QUEUE_ON_RETURN;
@@ -4932,7 +4918,7 @@ static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref,
49324918

49334919
ASN1_TIME* notBefore = X509_get_notBefore(x509);
49344920
JNI_TRACE("X509_get_notBefore(%p) => %p", x509, notBefore);
4935-
return ASN1_TIME_convert_to_posix(env, notBefore);
4921+
return reinterpret_cast<uintptr_t>(notBefore);
49364922
}
49374923

49384924
static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref,
@@ -4949,7 +4935,7 @@ static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref,
49494935

49504936
ASN1_TIME* notAfter = X509_get_notAfter(x509);
49514937
JNI_TRACE("X509_get_notAfter(%p) => %p", x509, notAfter);
4952-
return ASN1_TIME_convert_to_posix(env, notAfter);
4938+
return reinterpret_cast<uintptr_t>(notAfter);
49534939
}
49544940

49554941
// NOLINTNEXTLINE(runtime/int)
@@ -5585,7 +5571,7 @@ static jlong NativeCrypto_get_X509_REVOKED_revocationDate(JNIEnv* env, jclass,
55855571

55865572
JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked,
55875573
X509_REVOKED_get0_revocationDate(revoked));
5588-
return ASN1_TIME_convert_to_posix(env, X509_REVOKED_get0_revocationDate(revoked));
5574+
return reinterpret_cast<uintptr_t>(X509_REVOKED_get0_revocationDate(revoked));
55895575
}
55905576

55915577
#ifdef __GNUC__
@@ -5679,7 +5665,7 @@ static jlong NativeCrypto_X509_CRL_get_lastUpdate(JNIEnv* env, jclass, jlong x50
56795665

56805666
ASN1_TIME* lastUpdate = X509_CRL_get_lastUpdate(crl);
56815667
JNI_TRACE("X509_CRL_get_lastUpdate(%p) => %p", crl, lastUpdate);
5682-
return ASN1_TIME_convert_to_posix(env, lastUpdate);
5668+
return reinterpret_cast<uintptr_t>(lastUpdate);
56835669
}
56845670

56855671
static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -5696,7 +5682,7 @@ static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x50
56965682

56975683
ASN1_TIME* nextUpdate = X509_CRL_get_nextUpdate(crl);
56985684
JNI_TRACE("X509_CRL_get_nextUpdate(%p) => %p", crl, nextUpdate);
5699-
return ASN1_TIME_convert_to_posix(env, nextUpdate);
5685+
return reinterpret_cast<uintptr_t>(nextUpdate);
57005686
}
57015687

57025688
static jbyteArray NativeCrypto_i2d_X509_REVOKED(JNIEnv* env, jclass, jlong x509RevokedRef) {
@@ -5720,6 +5706,63 @@ static jint NativeCrypto_X509_supported_extension(JNIEnv* env, jclass, jlong x50
57205706
return X509_supported_extension(ext);
57215707
}
57225708

5709+
static inline bool decimal_to_integer(const char* data, size_t len, int* out) {
5710+
int ret = 0;
5711+
for (size_t i = 0; i < len; i++) {
5712+
ret *= 10;
5713+
if (data[i] < '0' || data[i] > '9') {
5714+
return false;
5715+
}
5716+
ret += data[i] - '0';
5717+
}
5718+
*out = ret;
5719+
return true;
5720+
}
5721+
5722+
static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef,
5723+
jobject calendar) {
5724+
CHECK_ERROR_QUEUE_ON_RETURN;
5725+
ASN1_TIME* asn1Time = reinterpret_cast<ASN1_TIME*>(static_cast<uintptr_t>(asn1TimeRef));
5726+
JNI_TRACE("ASN1_TIME_to_Calendar(%p, %p)", asn1Time, calendar);
5727+
5728+
if (asn1Time == nullptr) {
5729+
conscrypt::jniutil::throwNullPointerException(env, "asn1Time == null");
5730+
return;
5731+
}
5732+
5733+
if (!ASN1_TIME_check(asn1Time)) {
5734+
conscrypt::jniutil::throwParsingException(env, "Invalid date format");
5735+
return;
5736+
}
5737+
5738+
bssl::UniquePtr<ASN1_GENERALIZEDTIME> gen(ASN1_TIME_to_generalizedtime(asn1Time, nullptr));
5739+
if (gen.get() == nullptr) {
5740+
conscrypt::jniutil::throwParsingException(env,
5741+
"ASN1_TIME_to_generalizedtime returned null");
5742+
return;
5743+
}
5744+
5745+
if (ASN1_STRING_length(gen.get()) < 14 || ASN1_STRING_get0_data(gen.get()) == nullptr) {
5746+
conscrypt::jniutil::throwNullPointerException(env, "gen->length < 14 || gen->data == null");
5747+
return;
5748+
}
5749+
5750+
int year, mon, mday, hour, min, sec;
5751+
const char* data = reinterpret_cast<const char*>(ASN1_STRING_get0_data(gen.get()));
5752+
if (!decimal_to_integer(data, 4, &year) ||
5753+
!decimal_to_integer(data + 4, 2, &mon) ||
5754+
!decimal_to_integer(data + 6, 2, &mday) ||
5755+
!decimal_to_integer(data + 8, 2, &hour) ||
5756+
!decimal_to_integer(data + 10, 2, &min) ||
5757+
!decimal_to_integer(data + 12, 2, &sec)) {
5758+
conscrypt::jniutil::throwParsingException(env, "Invalid date format");
5759+
return;
5760+
}
5761+
5762+
env->CallVoidMethod(calendar, conscrypt::jniutil::calendar_setMethod, year, mon - 1, mday, hour,
5763+
min, sec);
5764+
}
5765+
57235766
// A CbsHandle is a structure used to manage resources allocated by asn1_read-*
57245767
// functions so that they can be freed properly when finished. This struct owns
57255768
// all objects pointed to by its members.
@@ -11219,6 +11262,7 @@ static JNINativeMethod sNativeCryptoMethods[] = {
1121911262
CONSCRYPT_NATIVE_METHOD(X509_REVOKED_dup, "(J)J"),
1122011263
CONSCRYPT_NATIVE_METHOD(i2d_X509_REVOKED, "(J)[B"),
1122111264
CONSCRYPT_NATIVE_METHOD(X509_supported_extension, "(J)I"),
11265+
CONSCRYPT_NATIVE_METHOD(ASN1_TIME_to_Calendar, "(JLjava/util/Calendar;)V"),
1122211266
CONSCRYPT_NATIVE_METHOD(asn1_read_init, "([B)J"),
1122311267
CONSCRYPT_NATIVE_METHOD(asn1_read_sequence, "(J)J"),
1122411268
CONSCRYPT_NATIVE_METHOD(asn1_read_next_tag_is, "(JI)Z"),

common/src/main/java/org/conscrypt/NativeCrypto.java

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.security.cert.CertificateParsingException;
3434
import java.util.ArrayList;
3535
import java.util.Arrays;
36+
import java.util.Calendar;
3637
import java.util.HashSet;
3738
import java.util.List;
3839
import java.util.Set;
@@ -637,6 +638,10 @@ static native long X509_CRL_get_nextUpdate(long x509CrlCtx, OpenSSLX509CRL holde
637638

638639
static native int X509_supported_extension(long x509ExtensionRef);
639640

641+
// --- ASN1_TIME -----------------------------------------------------------
642+
643+
static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) throws ParsingException;
644+
640645
// --- ASN1 Encoding -------------------------------------------------------
641646

642647
/**

common/src/main/java/org/conscrypt/OpenSSLX509CRL.java

+17-6
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@
3434
import java.security.cert.X509Certificate;
3535
import java.util.ArrayList;
3636
import java.util.Arrays;
37+
import java.util.Calendar;
3738
import java.util.Date;
3839
import java.util.HashSet;
3940
import java.util.List;
4041
import java.util.Set;
42+
import java.util.TimeZone;
43+
4144
import javax.crypto.BadPaddingException;
4245
import javax.crypto.IllegalBlockSizeException;
4346
import javax.security.auth.x500.X500Principal;
@@ -48,15 +51,23 @@
4851
*/
4952
final class OpenSSLX509CRL extends X509CRL {
5053
private volatile long mContext;
51-
private final long thisUpdate;
52-
private final long nextUpdate;
54+
private final Date thisUpdate;
55+
private final Date nextUpdate;
5356

5457
private OpenSSLX509CRL(long ctx) throws ParsingException {
5558
mContext = ctx;
5659
// The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so
5760
// parse them here because this is the only time we're allowed to throw ParsingException
58-
thisUpdate = NativeCrypto.X509_CRL_get_lastUpdate(mContext, this);
59-
nextUpdate = NativeCrypto.X509_CRL_get_nextUpdate(mContext, this);
61+
thisUpdate = toDate(NativeCrypto.X509_CRL_get_lastUpdate(mContext, this));
62+
nextUpdate = toDate(NativeCrypto.X509_CRL_get_nextUpdate(mContext, this));
63+
}
64+
65+
// Package-visible because it's also used by OpenSSLX509CRLEntry
66+
static Date toDate(long asn1time) throws ParsingException {
67+
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
68+
calendar.set(Calendar.MILLISECOND, 0);
69+
NativeCrypto.ASN1_TIME_to_Calendar(asn1time, calendar);
70+
return calendar.getTime();
6071
}
6172

6273
static OpenSSLX509CRL fromX509DerInputStream(InputStream is) throws ParsingException {
@@ -268,12 +279,12 @@ public X500Principal getIssuerX500Principal() {
268279

269280
@Override
270281
public Date getThisUpdate() {
271-
return new Date(thisUpdate);
282+
return (Date) thisUpdate.clone();
272283
}
273284

274285
@Override
275286
public Date getNextUpdate() {
276-
return new Date(nextUpdate);
287+
return (Date) nextUpdate.clone();
277288
}
278289

279290
@Override

common/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
*/
3232
final class OpenSSLX509CRLEntry extends X509CRLEntry {
3333
private final long mContext;
34-
private final long revocationDate;
34+
private final Date revocationDate;
3535

3636
OpenSSLX509CRLEntry(long ctx) throws ParsingException {
3737
mContext = ctx;
3838
// The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so
3939
// parse them here because this is the only time we're allowed to throw ParsingException
40-
revocationDate = NativeCrypto.get_X509_REVOKED_revocationDate(mContext);
40+
revocationDate = OpenSSLX509CRL.toDate(NativeCrypto.get_X509_REVOKED_revocationDate(mContext));
4141
}
4242

4343
@Override
@@ -112,7 +112,7 @@ public BigInteger getSerialNumber() {
112112

113113
@Override
114114
public Date getRevocationDate() {
115-
return new Date(revocationDate);
115+
return (Date) revocationDate.clone();
116116
}
117117

118118
@Override

common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java

+25-8
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@
3939
import java.security.spec.X509EncodedKeySpec;
4040
import java.util.ArrayList;
4141
import java.util.Arrays;
42+
import java.util.Calendar;
4243
import java.util.Collection;
4344
import java.util.Collections;
4445
import java.util.Date;
4546
import java.util.HashSet;
4647
import java.util.List;
4748
import java.util.Set;
49+
import java.util.TimeZone;
50+
4851
import javax.crypto.BadPaddingException;
4952
import javax.security.auth.x500.X500Principal;
5053
import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
@@ -59,15 +62,29 @@ public final class OpenSSLX509Certificate extends X509Certificate {
5962
private transient volatile long mContext;
6063
private transient Integer mHashCode;
6164

62-
private final long notBefore;
63-
private final long notAfter;
65+
private final Date notBefore;
66+
private final Date notAfter;
6467

6568
OpenSSLX509Certificate(long ctx) throws ParsingException {
6669
mContext = ctx;
6770
// The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so
6871
// parse them here because this is the only time we're allowed to throw ParsingException
69-
notBefore = NativeCrypto.X509_get_notBefore(mContext, this);
70-
notAfter = NativeCrypto.X509_get_notAfter(mContext, this);
72+
notBefore = toDate(NativeCrypto.X509_get_notBefore(mContext, this));
73+
notAfter = toDate(NativeCrypto.X509_get_notAfter(mContext, this));
74+
}
75+
76+
// A non-throwing constructor used when we have already parsed the dates
77+
private OpenSSLX509Certificate(long ctx, Date notBefore, Date notAfter) {
78+
mContext = ctx;
79+
this.notBefore = notBefore;
80+
this.notAfter = notAfter;
81+
}
82+
83+
private static Date toDate(long asn1time) throws ParsingException {
84+
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
85+
calendar.set(Calendar.MILLISECOND, 0);
86+
NativeCrypto.ASN1_TIME_to_Calendar(asn1time, calendar);
87+
return calendar.getTime();
7188
}
7289

7390
public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
@@ -244,12 +261,12 @@ public void checkValidity(Date date) throws CertificateExpiredException,
244261
CertificateNotYetValidException {
245262
if (getNotBefore().compareTo(date) > 0) {
246263
throw new CertificateNotYetValidException("Certificate not valid until "
247-
+ getNotBefore() + " (compared to " + date + ")");
264+
+ getNotBefore().toString() + " (compared to " + date.toString() + ")");
248265
}
249266

250267
if (getNotAfter().compareTo(date) < 0) {
251268
throw new CertificateExpiredException("Certificate expired at "
252-
+ getNotAfter() + " (compared to " + date + ")");
269+
+ getNotAfter().toString() + " (compared to " + date.toString() + ")");
253270
}
254271
}
255272

@@ -275,12 +292,12 @@ public Principal getSubjectDN() {
275292

276293
@Override
277294
public Date getNotBefore() {
278-
return new Date(notBefore);
295+
return (Date) notBefore.clone();
279296
}
280297

281298
@Override
282299
public Date getNotAfter() {
283-
return new Date(notAfter);
300+
return (Date) notAfter.clone();
284301
}
285302

286303
@Override

common/src/test/java/org/conscrypt/HostnameVerifierTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ public void subjectAltNameWithToplevelWildcard() throws Exception {
598598
//
599599
// Certificate generated using:-
600600
// openssl req -x509 -nodes -days 36500 -subj "/CN=Google Inc" \
601-
// -addext "subjectAltName=DNS:*.com" -
601+
// -addext "subjectAltName=DNS:*.com" -newkey rsa:512
602602
SSLSession session = session(""
603603
+ "-----BEGIN CERTIFICATE-----\n"
604604
+ "MIIBlTCCAT+gAwIBAgIUe1RB6C61ZW/SEQpKiywSEJOEOUMwDQYJKoZIhvcNAQEL\n"

common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java

+41
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.junit.Assert.assertArrayEquals;
2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertNotNull;
2122
import static org.junit.Assert.assertNull;
2223
import static org.junit.Assert.assertThrows;
2324
import static org.junit.Assert.assertTrue;
@@ -350,6 +351,26 @@ public class X509CertificateTest {
350351
+ "mmi08cueFV7mHzJSYV51yRQ=\n"
351352
+ "-----END CERTIFICATE-----\n";
352353

354+
private static final String UTCTIME_WITH_OFFSET = "-----BEGIN CERTIFICATE-----\n" +
355+
"MIIDPzCCAicCAgERMA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNVBAYTAlVTMRMwEQYD\n" +
356+
"VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MR8wHQYDVQQK\n" +
357+
"DBZHb29nbGUgQXV0b21vdGl2ZSBMaW5rMCYXETE0MDcwNDAwMDAwMC0wNzAwFxE0\n" +
358+
"ODA4MDExMDIxMjMtMDcwMDBnMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZv\n" +
359+
"cm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEeMBwGA1UECgwVQW5kcm9pZC1B\n" +
360+
"dXRvLUludGVybmFsMQswCQYDVQQLDAIwMTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" +
361+
"ADCCAQoCggEBAOWghAac2eJLbi/ijgZGRB6/MuaBVfOImkQddBJUhXbnskTJB/JI\n" +
362+
"12Ea22E5GeVN8CkWULAZT28yDWqsKMyq9BzpjpsHc9TKxMYqrIn0HP7mIJcBu5z7\n" +
363+
"K8DoXqc86encncJlkGeuQkUA68yyp7RG7eQ6XoBHEjNmyvX13Y8NY5sPUHfLfmp6\n" +
364+
"A2n+Jdmecq3L0GS84ctdNtnp2zSopTy0L1Gp6+lrnuOPAYZeV+Ei2jAvhycvuSoB\n" +
365+
"yV6rT9wvREvC2TDncurMwR6ws44+ZStqkhnvDLhV04ray5aPplQwwB9GELFCYSRk\n" +
366+
"56sm57uYSJj/LlmOMcvyBmUHVJ7MLxgtlykCAwEAATANBgkqhkiG9w0BAQsFAAOC\n" +
367+
"AQEA1Bs8v6HuAIiBdhGDGHzZJDwO6lW0LheBqsGLG9KsVvIVrTMPP9lpdTPjStGn\n" +
368+
"en1RIce4R4l3YTBwxOadLMkf8rymAE5JNjPsWlBue7eI4TFFw/cvnKxcTQ61bC4i\n" +
369+
"2uosyDI5VfrXm38zYcZoK4TFtMhNyx6aYSEClWB9MjHa+n6eR3dLBCg1kMGqGdZ/\n" +
370+
"AoK0UEkyI3UFU8sW86iaS4dvPSaQ+z0tmfUzbrc5ZSk4hYCeUYvuyd2ShxjKmxvD\n" +
371+
"0K8A7gKLY0jP8Zp+6rYBcpxc7cylWMbdlhFTHAGiKI+XeQ/9u+RPeocZsn5jGlDt\n" +
372+
"K3ftMoWFce+baNq/WcMzRj04AA==\n" +
373+
"-----END CERTIFICATE-----\n";
353374
private static Date dateFromUTC(int year, int month, int day, int hour, int minute, int second)
354375
throws Exception {
355376
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
@@ -715,4 +736,24 @@ public void test(Provider p, String algorithm) throws Exception {
715736
}
716737
});
717738
}
739+
740+
// Ensure we don't reject certificates with UTCTIME fields with offsets for now: b/311260068
741+
@Test
742+
public void utcTimeWithOffset() throws Exception {
743+
ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
744+
tester.skipProvider("SUN") // Sun and BC interpret the offset, Conscrypt just drops it...
745+
.skipProvider("BC")
746+
.run(new ServiceTester.Test() {
747+
@Override
748+
public void test(Provider p, String algorithm) throws Exception {
749+
X509Certificate c = certificateFromPEM(p, UTCTIME_WITH_OFFSET);
750+
assertDatesEqual(
751+
dateFromUTC(2014, Calendar.JULY, 4, 0, 0, 0),
752+
c.getNotBefore());
753+
assertDatesEqual(
754+
dateFromUTC(2048, Calendar.AUGUST, 1, 10, 21, 23),
755+
c.getNotAfter());
756+
}
757+
});
758+
}
718759
}

0 commit comments

Comments
 (0)