Skip to content

Commit 506e840

Browse files
swankjessesquarejesse
authored andcommitted
Implement DER constraints on date formats (#6213)
The time zone must be 'Z', and cannot be an offset like '+0000'. That is allowed by ASN.1 but not by DER. Closes: #6210
1 parent a70e992 commit 506e840

File tree

2 files changed

+60
-47
lines changed
  • okhttp-tls/src

2 files changed

+60
-47
lines changed

okhttp-tls/src/main/kotlin/okhttp3/tls/internal/der/Adapters.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ internal object Adapters {
163163

164164
internal fun parseUtcTime(string: String): Long {
165165
val utc = TimeZone.getTimeZone("GMT")
166-
val dateFormat = SimpleDateFormat("yyMMddHHmmssXX").apply {
166+
val dateFormat = SimpleDateFormat("yyMMddHHmmss'Z'").apply {
167167
timeZone = utc
168168
set2DigitYearStart(Date(-631152000000L)) // 1950-01-01T00:00:00Z.
169169
}
@@ -174,7 +174,7 @@ internal object Adapters {
174174

175175
internal fun formatUtcTime(date: Long): String {
176176
val utc = TimeZone.getTimeZone("GMT")
177-
val dateFormat = SimpleDateFormat("yyMMddHHmmssXX").apply {
177+
val dateFormat = SimpleDateFormat("yyMMddHHmmss'Z'").apply {
178178
timeZone = utc
179179
set2DigitYearStart(Date(-631152000000L)) // 1950-01-01T00:00:00Z.
180180
}
@@ -229,7 +229,7 @@ internal object Adapters {
229229

230230
internal fun parseGeneralizedTime(string: String): Long {
231231
val utc = TimeZone.getTimeZone("GMT")
232-
val dateFormat = SimpleDateFormat("yyyyMMddHHmmssXX").apply {
232+
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss'Z'").apply {
233233
timeZone = utc
234234
}
235235

@@ -239,7 +239,7 @@ internal object Adapters {
239239

240240
internal fun formatGeneralizedTime(date: Long): String {
241241
val utc = TimeZone.getTimeZone("GMT")
242-
val dateFormat = SimpleDateFormat("yyyyMMddHHmmssXX").apply {
242+
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss'Z'").apply {
243243
timeZone = utc
244244
}
245245

okhttp-tls/src/test/java/okhttp3/tls/internal/der/DerTest.kt

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@
1515
*/
1616
package okhttp3.tls.internal.der
1717

18-
import java.math.BigInteger
19-
import java.net.InetAddress
20-
import java.net.ProtocolException
21-
import java.text.SimpleDateFormat
22-
import java.util.Date
23-
import java.util.TimeZone
2418
import okhttp3.tls.internal.der.CertificateAdapters.generalNameDnsName
2519
import okhttp3.tls.internal.der.CertificateAdapters.generalNameIpAddress
2620
import okhttp3.tls.internal.der.ObjectIdentifiers.basicConstraints
@@ -33,7 +27,15 @@ import okio.ByteString.Companion.encodeUtf8
3327
import okio.ByteString.Companion.toByteString
3428
import org.assertj.core.api.Assertions.assertThat
3529
import org.junit.Assert.fail
30+
import org.junit.Ignore
3631
import org.junit.Test
32+
import java.math.BigInteger
33+
import java.net.InetAddress
34+
import java.net.ProtocolException
35+
import java.text.ParseException
36+
import java.text.SimpleDateFormat
37+
import java.util.Date
38+
import java.util.TimeZone
3739

3840
internal class DerTest {
3941
@Test fun `decode tag and length`() {
@@ -683,59 +685,60 @@ internal class DerTest {
683685
assertThat(string).isEqualTo("hi")
684686
}
685687

686-
@Test fun `encode ia5`() {
687-
val byteString = Adapters.IA5_STRING.toDer("hi")
688-
assertThat(byteString).isEqualTo("16026869".decodeHex())
689-
}
690-
691-
@Test fun `decode printable string`() {
692-
val string = Adapters.PRINTABLE_STRING.fromDer("13026869".decodeHex())
693-
assertThat(string).isEqualTo("hi")
694-
}
695-
696-
@Test fun `encode printable string`() {
697-
val byteString = Adapters.PRINTABLE_STRING.toDer("hi")
698-
assertThat(byteString).isEqualTo("13026869".decodeHex())
699-
}
700-
701-
@Test fun `decode utc time`() {
702-
val time = Adapters.UTC_TIME.fromDer("17113139313231353139303231302d30383030".decodeHex())
703-
assertThat(time).isEqualTo(date("2019-12-16T03:02:10.000+0000").time)
688+
@Test fun `cannot decode utc time with offset`() {
689+
try {
690+
Adapters.UTC_TIME.fromDer("17113139313231353139303231302d30383030".decodeHex())
691+
fail()
692+
} catch (expected: ParseException) {
693+
}
704694
}
705695

706696
@Test fun `encode utc time`() {
707697
val byteString = Adapters.UTC_TIME.toDer(date("2019-12-16T03:02:10.000+0000").time)
708698
assertThat(byteString).isEqualTo("170d3139313231363033303231305a".decodeHex())
709699
}
710700

711-
@Test fun `decode generalized time`() {
712-
val time = Adapters.GENERALIZED_TIME
713-
.fromDer("181332303139313231353139303231302d30383030".decodeHex())
714-
assertThat(time).isEqualTo(date("2019-12-16T03:02:10.000+0000").time)
701+
@Test fun `cannot decode malformed utc time`() {
702+
val bytes = "170d3139313231362333303231305a".decodeHex()
703+
try {
704+
Adapters.UTC_TIME.fromDer(bytes)
705+
fail()
706+
} catch (expected: ParseException) {
707+
}
708+
}
709+
710+
@Test fun `cannot decode generalized time with offset`() {
711+
try {
712+
Adapters.GENERALIZED_TIME.fromDer("181332303139313231353139303231302d30383030".decodeHex())
713+
fail()
714+
} catch (expected: ParseException) {
715+
}
715716
}
716717

717718
@Test fun `encode generalized time`() {
718719
val byteString = Adapters.GENERALIZED_TIME.toDer(date("2019-12-16T03:02:10.000+0000").time)
719720
assertThat(byteString).isEqualTo("180f32303139313231363033303231305a".decodeHex())
720721
}
721722

723+
@Test fun `parse utc time`() {
724+
assertThat(Adapters.parseUtcTime("920521000000Z"))
725+
.isEqualTo(date("1992-05-21T00:00:00.000+0000").time)
726+
assertThat(Adapters.parseUtcTime("920622123421Z"))
727+
.isEqualTo(date("1992-06-22T12:34:21.000+0000").time)
728+
assertThat(Adapters.parseUtcTime("920722132100Z"))
729+
.isEqualTo(date("1992-07-22T13:21:00.000+0000").time)
730+
}
731+
722732
@Test fun `decode utc time two digit year cutoff is 1950`() {
723-
assertThat(Adapters.parseUtcTime("500101000000-0000"))
733+
assertThat(Adapters.parseUtcTime("500101000000Z"))
724734
.isEqualTo(date("1950-01-01T00:00:00.000+0000").time)
725-
assertThat(Adapters.parseUtcTime("500101000000-0100"))
735+
assertThat(Adapters.parseUtcTime("500101010000Z"))
726736
.isEqualTo(date("1950-01-01T01:00:00.000+0000").time)
727737

728-
assertThat(Adapters.parseUtcTime("491231235959+0100"))
738+
assertThat(Adapters.parseUtcTime("491231225959Z"))
729739
.isEqualTo(date("2049-12-31T22:59:59.000+0000").time)
730-
assertThat(Adapters.parseUtcTime("491231235959-0000"))
740+
assertThat(Adapters.parseUtcTime("491231235959Z"))
731741
.isEqualTo(date("2049-12-31T23:59:59.000+0000").time)
732-
733-
// Note that time zone offsets aren't honored by Java's two-digit offset boundary! A savvy time
734-
// traveler could exploit this to get a certificate that expires 100 years later than expected.
735-
assertThat(Adapters.parseUtcTime("500101000000+0100"))
736-
.isEqualTo(date("2049-12-31T23:00:00.000+0000").time)
737-
assertThat(Adapters.parseUtcTime("491231235959-0100"))
738-
.isEqualTo(date("2050-01-01T00:59:59.000+0000").time)
739742
}
740743

741744
@Test fun `encode utc time two digit year cutoff is 1950`() {
@@ -746,14 +749,24 @@ internal class DerTest {
746749
}
747750

748751
@Test fun `parse generalized time`() {
749-
assertThat(Adapters.parseGeneralizedTime("18990101000000-0000"))
752+
assertThat(Adapters.parseGeneralizedTime("18990101000000Z"))
750753
.isEqualTo(date("1899-01-01T00:00:00.000+0000").time)
751-
assertThat(Adapters.parseGeneralizedTime("19500101000000-0000"))
754+
assertThat(Adapters.parseGeneralizedTime("19500101000000Z"))
752755
.isEqualTo(date("1950-01-01T00:00:00.000+0000").time)
753-
assertThat(Adapters.parseGeneralizedTime("20500101000000-0000"))
756+
assertThat(Adapters.parseGeneralizedTime("20500101000000Z"))
754757
.isEqualTo(date("2050-01-01T00:00:00.000+0000").time)
755-
assertThat(Adapters.parseGeneralizedTime("20990101000000-0000"))
758+
assertThat(Adapters.parseGeneralizedTime("20990101000000Z"))
756759
.isEqualTo(date("2099-01-01T00:00:00.000+0000").time)
760+
assertThat(Adapters.parseGeneralizedTime("19920521000000Z"))
761+
.isEqualTo(date("1992-05-21T00:00:00.000+0000").time)
762+
assertThat(Adapters.parseGeneralizedTime("19920622123421Z"))
763+
.isEqualTo(date("1992-06-22T12:34:21.000+0000").time)
764+
}
765+
766+
@Ignore("fractional seconds are not implemented")
767+
@Test fun `parse generalized time with fractional seconds`() {
768+
assertThat(Adapters.parseGeneralizedTime("19920722132100.3Z"))
769+
.isEqualTo(date("1992-07-22T13:21:00.300+0000").time)
757770
}
758771

759772
@Test fun `format generalized time`() {

0 commit comments

Comments
 (0)